在现代分布式系统中,资源竞争问题就像早高峰的地铁闸机——多个乘客(线程/进程)同时争夺有限的通行权限。传统单机锁就像车站只安排一名检票员,而分布式锁则需要协调多个车站的检票系统协同工作。Redis凭借其高性能、原子性操作和丰富的数据结构,成为实现分布式锁的首选方案之一。
我经历过从早期基于SETNX的简陋实现,到逐步引入超时机制、唯一标识、可重入性等特性的完整演进过程。在这个过程中踩过的坑包括:
最基础的实现方案只需要两个命令:
bash复制SETNX lock_key 1 # 尝试获取锁
DEL lock_key # 释放锁
这种方案存在三个致命缺陷:
我在2016年曾用这种方案实现商品秒杀,结果因为某个节点网络抖动导致锁未释放,整个系统死锁了45分钟。这个教训让我意识到分布式锁绝不是简单的键值操作。
针对第一代的问题,改进方案引入了过期时间和唯一标识:
bash复制SET lock_key $uuid NX PX 30000 # 原子性设置锁和过期时间
释放锁时使用Lua脚本保证原子性:
lua复制if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
这个方案解决了前代的核心问题,但仍有改进空间:
Redisson的分布式锁实现可以看作是一个精密的瑞士手表,主要包含这些组件:
以Redisson 3.17.7版本为例,核心加锁逻辑在RedissonLock.tryLockInnerAsync方法:
java复制<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit,
long threadId, RedisStrictCommand<T> command) {
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);",
Collections.singletonList(getRawName()),
unit.toMillis(leaseTime), getLockName(threadId));
}
这段Lua脚本实现了:
看门狗线程是Redisson最精妙的设计之一,其工作流程如下:
实际使用中发现,在GC停顿时间较长(超过看门狗检查间隔)的情况下,可能导致锁意外释放。建议对延迟敏感的业务显式指定leaseTime。
| 参数 | 默认值 | 建议值 | 说明 |
|---|---|---|---|
| lockWatchdogTimeout | 30000ms | 根据业务调整 | 看门狗续期时间间隔 |
| failedAttempts | 3 | 根据网络状况调整 | RedLock尝试次数 |
| retryInterval | 1500ms | 500-3000ms | 获取锁重试间隔 |
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 获取锁耗时波动大 | Redis节点负载不均 | 检查Redis监控,考虑集群扩容 |
| 锁提前释放 | GC停顿超过看门狗间隔 | 适当增加leaseTime或优化JVM参数 |
| 死锁 | 客户端崩溃前未释放锁 | 合理设置过期时间,添加监控告警 |
对于金融级场景,建议采用多活架构:
在AWS c5.xlarge机型(4vCPU 8GB内存)的测试环境中:
| 场景 | QPS | 平均延迟 | 99线 |
|---|---|---|---|
| 单节点锁 | 12,000 | 2.3ms | 8ms |
| RedLock(3节点) | 3,800 | 6.7ms | 22ms |
| 锁等待(10线程) | 9,500 | 15ms | 45ms |
| 维度 | Redis | ZooKeeper |
|---|---|---|
| 性能 | 高 | 中 |
| 一致性 | 最终一致 | 强一致 |
| 实现复杂度 | 低 | 中 |
| 适用场景 | 高频短时锁 | 低频长时锁 |
在实际电商库存系统中,我发现分层加锁能显著提升并发性:
这种设计使得北京和上海的订单可以并行处理,只在涉及同一仓库时才需要互斥。