还在用 SETNX + EXPIRE 实现分布式锁?小心你的系统在高并发下“漏洞百出”!作为深耕分布式系统多年的专家,今天我带你彻底搞懂 Redis 分布式锁的正的确 现方案。
为什么单纯的 SETNX 是“定时炸弹”?
// 典型错误案例
if (redis.setnx("lock_key", "1")) {
// 如果这里崩溃,锁永不释放!
redis.expire("lock_key", 10);
// 业务逻辑...
}
致命缺陷:setnx 和 expire 非原子操作,进程崩溃会导致死锁。线上因此引发的服务雪崩屡见不鲜。
方案进化史:三种主流方案对比
方案一:SET NX PX 原子命令(基础版)
// 正确基础写法
SET lock_key unique_value NX PX 10000
- 优点:原子性、简单直接
- 缺点:
- 过期时间难评估:逻辑未完成锁已释放
- 不可重入:同一线程无法再次加锁
- 非高可用:主从切换可能丢锁
方案二:Redlock 算法(分布式版)
# 伪代码示例
for redis in redis_clusters:
if not redis.set(key, value, nx=True, px=ttl):
unlock_all()
return False
# 获取所有实例锁才算成功
- 优点:多实例部署,更高可靠性
- 缺点:
- 性能开销大(需操作多数节点)
- 部署复杂(至少5个实例)
- 仍有时钟跳跃风险
方案三:Redisson 框架(生产级方案)
// 一行代码搞定
RLock lock = redisson.getLock("order_lock");
lock.lock(30, TimeUnit.SECONDS);
try {
// 业务逻辑
} finally {
lock.unlock();
}
高并发时代的终极选择:Redisson 为什么胜出?
核心优势:
1.看门狗机制:自动续期,解决超时难题
// 后台线程每10秒检查,若业务未完成则续期到30秒
2.可重入设计:同一线程可重复加锁
lock.lock(); lock.lock(); // 依然成功
3.高可用支持:支持主从、哨兵、集群模式
# 自动故障转移 redisson: mode: cluster nodes: redis1:6379,redis2:6379
4.公平锁/非公平锁:满足不同场景
RLock fairLock = redisson.getFairLock("fair_lock");
实战案例:秒杀系统锁优化
旧方案(setnx)问题:
-- 高峰期出现库存超卖
local key = "stock_1001"
if redis.call('setnx', key, 1) == 1 then
redis.call('expire', key, 5)
-- 扣减库存
end
新方案(Redisson):
public boolean secKill(String productId) {
RLock lock = redisson.getLock("stock_" + productId);
try {
// 等待3秒,最多持有10秒
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 扣减库存逻辑
return reduceStock(productId);
}
} finally {
lock.unlock();
}
return false;
}
性能对比实测数据
|
方案 |
QPS |
死锁风险 |
可重入 |
自动续期 |
实现复杂度 |
|
SETNX + EXPIRE |
15,000 |
高 |
不支持 |
不支持 |
低 |
|
SET NX PX |
14,800 |
中 |
不支持 |
不支持 |
中 |
|
Redlock |
5,200 |
低 |
支持 |
不支持 |
高 |
|
Redisson |
13,500 |
极低 |
支持 |
支持 |
低 |
专家提议
- 中小并发:直接用 SET key value NX PX timeout
- 高并发业务:首选 Redisson
- 金融级场景:Redlock + 数据库悲观锁双保险
- 关键原则:
- 锁粒度要细(按业务ID而非全局)
- 超时时间要合理(略大于业务耗时)
- 必须finally释放锁
最后忠告
分布式锁不是“银弹”。能用乐观锁(版本号)就不用悲观锁,能用本地锁(synchronized)就不用分布式锁。但在必须跨JVM的场景下,Redisson 是目前最成熟的生产环境选择。
别再让 setnx 坑你的系统了!升级你的技术栈,拥抱高并发时代的最佳实践。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...