组里有同学说:"分布式锁直接用 SETNX 就够了,没必要上框架"。

这里先说结论:优先使用封装好的框架 Redisson。
ps. 之前看交易中心的代码,里面就存在同一个调用链中多次使用分布式锁,这就是一个隐患点。

原因有三:
1、省时省心:只用 SETNX 不够安全;能做到"可用但不严谨"。自研还要补齐一堆细节(SET NX PX + token + Lua 解锁 + 续期/等待/重入/容灾)
2、安全可靠:多年维护、活跃的 issue/PR 与稳定的发布节奏,边角场景被大量用户验证。
3、避免踩坑:避免因个别同学使用姿势不当(开发不规范、技术认知不到位),而造成不必要的问题。
万一万一有一天你的 直属 leader 问你,能有啥问题?你了解 Redisson 细节吗?

可以从这 3大块 切入:
- 分布式锁特性
- 锁的特性
- Redisson 特性
1、分布式锁特性
如果要设计一个分布式锁,就需要明确分布式锁经常出现哪些问题,以及如何解决。
- 可用问题:无论何时都要保证锁服务的可用性(这是系统正常执行锁操作的基础)。
- 死锁问题:客户端一定可以获得锁,即使锁住某个资源的客户端在释放锁之前崩溃或者网络不可达(这是避免死锁的设计原则)。
- 脑裂问题:集群同步时产生的数据不一致,导致新的进程有可能拿到锁,但之前的进程以为自己还有锁,那么就出现两个进程拿到了同一个锁的问题。
通过 "可用问题、死锁问题、脑裂问题" 来展开回答各分布式锁的实现方案的优缺点和适用场景。
2、锁的特性
同时还需要考虑,锁的四种设计原则:
- 互斥性:即在分布式系统环境下,对于某一共享资源,需要保证在同一时间只能一个线程或进程对该资源进行操作。
- 高可用:也就是可靠性,锁服务不能有单点风险,要保证分布式锁系统是集群的,并且某一台机器锁不能提供服务了,其他机器仍然可以提供锁服务。
- 锁释放:具备锁失效机制,防止死锁。即使出现进程在持有锁的期间崩溃或者解锁失败的情况,也能被动解锁,保证后续其他进程可以获得锁。
- 可重入:一个节点获取了锁之后,还可以再次获取整个锁资源。
3、Redisson 特性
- 更安全正确:内置看门狗自动续期、基于唯一 token 的原子加/解锁(Lua 比对),避免 setnx+expire 的非原子、误删他人锁和锁过期并发等坑。
- 更强能力更省代码:支持 tryLock(等待/租期)、Pub/Sub 阻塞通知、重入/读写/公平锁、信号量/栅栏/MultiLock/RedLock,用现成语义替代自研轮子。
- 生产级可靠性:原生哨兵/集群/主从支持,连接管理、超时与重试策略完善,故障切换时行为可控,稳定性和性能有成熟实践背书。
Redisson 使用 demo:加锁、解锁。
java
RLock lock = redisson.getLock("lock:order:123");
try {
// 不传过期时间 -> 默认30s, 看门狗自动续期
boolean isLock = lock.tryLock(5, TimeUnit.SECONDS);
if (!isLock) {
log.warn("加锁失败。lockInfo:{}", lockInfo);
throw new LockException("加锁失败");
}
} finally {
lock.unlock();
}
一张图来解释加锁:

Redis Cluster读写操作时,会基于 key(CRC16)计算 slot,得到哪台 master 机子- 执行 Lua 脚本:保证原子操作
- 启动看门狗(Watchdog):监控
一张图来解释解锁:执行 lock.unlock(), 就可以释放分布式锁 
- 每次都对 myLock 数据结构中的那个加锁次数减1。
- 如果发现加锁次数是 0 了, 说明这个客户端已经不再持有锁了, 此时就会用:
del myLock命令, 从 redis 里删除这个 key。 - 另外的客户端2 就可以尝试完成加锁了。
- 使用
Redis的 发布订阅功能,通知其他客户端
互斥场景有哪些?
- 同个线程,多次
lock.lock(): 不会互斥,因为重入 - 不同线程,进行
lock.lock(): 会互斥,互相阻塞 - 不同客户端,进行
lock.lock():会互斥,互相阻塞
分布式锁存储在 Redis中数据结构是什么?
shell
# Redis 中如下
172.18.1.23:7004> hgetall myLock
1) "dfd3aabb-82ce-4c54-966d-5719675a3d62:1"
2) "1"
# 实际上就类似 map:
# "dfd3aabb-82ce-4c54-966d-5719675a3d62:1",代表某客户端 UUID,1 是 threadId
# 1,代表自增次数
{
"dfd3aabb-82ce-4c54-966d-5719675a3d62:1": 1
}
所以能不能重入锁,要看客户端和线程是否一致。