在分布式系统中,协调多个节点对共享资源的访问是个经典难题。去年我们电商平台就遇到过这样的场景:大促期间库存扣减出现超卖,排查发现是多个Pod同时执行了库存检查逻辑。这时候就需要一种跨JVM的互斥机制——分布式锁。
Redis因其高性能和丰富的数据结构,成为实现分布式锁的热门选择。但真正落地时你会发现不少坑:锁过期了业务还没执行完怎么办?客户端崩溃后锁无法释放怎么处理?主从切换导致锁失效如何防范?这些正是我们今天要深入探讨的。
最朴素的实现方式是使用SETNX+EXPIRE组合:
bash复制SETNX lock_key unique_value # 尝试获取锁
EXPIRE lock_key 30 # 设置过期时间
DEL lock_key # 释放锁
这种方案存在原子性问题:如果SETNX成功但EXPIRE执行前客户端崩溃,就会产生死锁。改进方案是使用Redis 2.6.12后支持的扩展参数:
bash复制SET lock_key unique_value NX PX 30000
重要提示:value必须使用唯一标识(如UUID),避免误删其他客户端的锁。我曾见过有人用线程ID导致锁冲突,最终引发雪崩。
lua复制if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
通过Lua脚本可以封装完整的锁操作:
lua复制-- 加锁脚本
local key = KEYS[1]
local value = ARGV[1]
local ttl = ARGV[2]
local result = redis.call("SET", key, value, "NX", "PX", ttl)
if result then
return 1
else
return 0
end
-- 解锁脚本
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
实测发现Lua版本比原生命令吞吐量下降约15%,但可靠性显著提升。在跨机房场景下,建议设置script kill保护机制。
Redisson采用watchdog机制解决锁续期问题,核心组件包括:
java复制Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setLockWatchdogTimeout(30000);
RedissonClient client = Redisson.create(config);
RLock lock = client.getLock("orderLock");
在8核16G环境压测结果:
| 方案 | QPS | 平均耗时 | 死锁概率 |
|---|---|---|---|
| SETNX | 12,000 | 8ms | 0.3% |
| Lua脚本 | 10,200 | 11ms | 0.01% |
| Redisson | 9,500 | 15ms | 0% |
| Zookeeper实现 | 3,200 | 45ms | 0% |
遇到网络抖动时,建议:
必须监控的关键指标:
我们团队使用Grafana搭建的监控看板曾及时发现锁竞争加剧问题,避免了线上故障。
最近遇到个典型案例:某支付系统使用Redisson但未配置看门狗线程,导致长时间GC时锁自动失效。最终通过调整JVM参数和锁超时时间解决。