1. 分布式锁的本质与核心诉求
在分布式系统中,多个服务实例对共享资源的并发访问控制是个经典难题。想象一下春运抢票场景:当多个用户同时点击购买同一趟列车的车票时,如何确保不会出现超卖?这就是分布式锁要解决的核心问题——跨JVM进程的互斥访问控制。
1.1 分布式锁的四大核心特性
互斥性:这是分布式锁的根基。就像电影院里的座位,同一时刻只允许一个人入座。技术实现上需要确保即使有100个并发请求,最终只有1个能成功获取锁。
可重入性:一个线程如果已经持有锁,应该能够再次获取该锁而不被阻塞。这类似于现实中的门锁——你用自己的钥匙开门后,不需要反复开锁就能多次进出。
锁超时:必须设置自动释放机制,防止死锁。比如设置10秒有效期,就像会议室预定系统,超时未使用会自动释放预定。
高可用:锁服务本身不能是单点。Redis Cluster或RedLock等方案就是为了解决这个问题,就像银行的金库不能只靠一把钥匙保管。
1.2 常见实现方案对比
| 实现方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Redis | 性能高,实现简单 | 需要处理锁续期问题 | 高并发短事务场景 |
| Zookeeper | 原生支持临时顺序节点 | 性能相对较低 | 强一致性要求的场景 |
| 数据库乐观锁 | 无需额外组件 | 大量重试消耗资源 | 低并发简单场景 |
| etcd | 高可用强一致 | 部署复杂度高 | 云原生环境 |
提示:选择方案时需要权衡CAP理论中的CP和AP特性。Redis是AP系统,ZK/etcd是CP系统
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Redis分布式锁深度实现
2.1 基础实现:SETNX的陷阱与救赎
最基础的Redis锁实现是这样的:
java复制Boolean result = redisTemplate.opsForValue()
.setIfAbsent("lock_key", "value", 30, TimeUnit.SECONDS);
但这里藏着三个致命陷阱:
- 非原子性过期设置:早期版本需要用SETNX+EXPIRE两条命令,中间可能崩溃
- 误删他人锁:线程A超时释放后可能删除线程B的锁
- 锁续期问题:业务执行时间超过TTL怎么办?
Redis 2.8版本后提供了原子性解决方案:
bash复制SET lock_key unique_value NX EX 30
NX表示不存在时才设置,EX设置过期时间,一条命令保证原子性。
2.2 锁续期:守护线程的智慧
对于可能长时间执行的业务,需要实现锁续期机制。这就像租房子时的续租——在到期前主动延长租约。
java复制private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public void renewLock(String key, String value, int timeout) {
scheduler.scheduleAtFixedRate(() -> {
if (getLockValue(key).equals(value)) {
expire(key, timeout);
}
}, timeout / 3, timeout / 3, TimeUnit.SECONDS);
}
这里有几个关键点:
- 续期时间间隔设置为TTL的1/3(比如30秒TTL就每10
