前言:为什么 synchronized 突然不香了?
前几篇博客我们把 synchronized 和 ReentrantLock 这个级别的锁研究得透透的了。但在如今微服务、集群化部署的大时代下,这些传统的锁突然"失灵"了。为什么?
因为 synchronized 和 ReentrantLock 都是单机锁(本地锁) ,它们的作用域只能是在同一个 JVM 进程 内部!
想象一下,搞秒杀活动,由于流量太大,你部署了 3 台 Tomcat 服务器处理订单。此时一件商品同时被 3 个人抢,由于是三个独立运行的机器,本地锁根本拦不住跨服务器的请求,结果就是商品发生了超卖。
**为了解决跨 JVM 的锁问题,"分布式锁"应运而生!**相当于在三台机器外面,挂出一把公共的大锁,谁抢到谁才能进!
一、 打怪升级:Redis 实现分布式锁的演进史
Redis 是实现分布式锁最常见的组件,因为它的速度极快,而且支持单线程操作。但想用好Redis分布式锁,里面全是坑!我们看看前人是怎么踩坑的。
1. 青铜级:占坑法 (setnx)
思路 :利用 Redis 的 setnx(SET if Not eXists)命令。如果一个 key 不存在,我就把它设置进去(抢到锁);如果已经存在,我就抢锁失败。处理完业务再把它 del(释放锁)。
致命漏洞 :如果拿到锁的机器突然停电宕机了 ,没来得及执行 del 操作。这把锁就永远留在了 Redis 里。其他人都饿死在门外!------这就是死锁!
2. 白银级:给锁加个寿命 (setnx + expire)
思路 :为了防止宕机死锁,我抢到锁之后,立马给它加个过期时间(比如 10 秒)。哪怕我宕机了,10 秒后 Redis 也会自动删掉它。
致命漏洞 :这两步不是原子操作 !万一我刚执行完 setnx,还没来得及执行 expire 就宕机了怎么办?死锁依然存在!
3. 黄金级:原子占坑 (set key value EX 10 NX)
思路 :从 Redis 2.6.12 开始,官方支持吧 setnx 和设置过期时间合并为一条原子命令!
致命漏洞:超时问题与误删。
- 锁只有 10 秒,但我业务执行了 15 秒!这时候第 10 秒锁过期了,别的机器把锁抢走了。(并发安全没了)
- 等我 15 秒办完业务,我习惯性地去
del操作。结果,我竟然把刚刚别人加的锁给删掉了!!!
4. 王者级:终极形态 ------ Redisson 看门狗机制 (Watch Dog)
为了彻底解决"锁过期但业务还没跑完"的千古难题,开源框架 Redisson 大显神威!
它自带了一只**"看门狗"**:
- Redisson 帮你在后台开启一个守护线程。
- 当你拿到锁时,默认它只有 30 秒的寿命。
- 但是看门狗每隔 10 秒会去看一眼:主人你还在干活吗?如果还在干活,它就自动帮你发送一条续期命令,把锁重新续满到 30 秒!
- 只要你不死,看门狗会一直帮你续命!由于宕机会导致看门狗也一并死掉,所以根本不怕死锁!
二、 代码实战:Redisson 秒杀分布式锁完整演示
引入 Redisson 极其简单,下面就是生产级环境里我们真正在用的写法:
xml
<!-- 引入 Redisson 依赖 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.4</version>
</dependency>
java
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class SeckillService {
// 注入强大的 Redisson 客户端
@Autowired
private RedissonClient redissonClient;
public void buyIphone() {
// 1. 获取一把分布式锁(只要名字一样,就是同一把大锁!)
RLock lock = redissonClient.getLock("seckill:iphone14:lock");
// 2. 加锁阻塞等待(核心绝招:不要手动传过期时间,让看门狗自动生效!)
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " -> 成功抢到了锁,开始疯狂处理秒杀核心业务...");
// 模拟极其耗时的业务(比如 40 秒),看门狗会在后台帮我们不停续期,确保锁不会失效
Thread.sleep(40000);
System.out.println(Thread.currentThread().getName() + " -> 业务办完,准备下班!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 3. 终极防坑:解锁前,务必要判断这把锁是不是自己的 且 锁是不是还被锁着
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " -> 解锁成功!");
}
}
}
}
【硬核细节剖析】
如果你在使用 lock.lock(10, TimeUnit.SECONDS) 手动传了过期时间,看门狗机制就会失效 !到期强行释放锁。只有使用无参的 lock.lock() 时,Redisson 才会默认开启看门狗机制!
三、 面试必杀技:Redis 分布式锁的缺陷 (高阶局)
如果你仅仅答到看门狗,面试官最多给你 80 分。想拿 100 分甚至震撼面试官,一定要谈一谈 Redis 极少出现的"脑裂"或主从同步延迟带来的坑。
面试官:"如果你的锁刚刚写入了 Redis 主节点 (Master),主节点立刻宕机了,从节点 (Slave) 还没来得及同步到这个锁就被推举为了新的主节点。这时候,别的机器也能过来拿到这把锁。这就意味着有两台机器同时拿到锁,并发全乱了。你怎么看?"
破局方案:
为了对抗极端情况,Redis 的作者提出了 RedLock(红锁) 算法。
核心思想:搞 5 台完全独立的 Redis 主节点,不上任何主从。每次加锁要分别去请求这 5 台 Redis。只有超过半数(3台及以上)都加锁成功了,才算真的抢到锁!
虽然牛逼,但这玩意儿重到令人发指。一般公司如果没有变态级的金融级强一致要求,千万不要用 RedLock,性能全被毁了。大多数情况下用普通的 Redisson 主从集群就已经完全够用了。
总结
从本地的 synchronized 跨越到微服务的 Redis 分布式锁,是后端程序员脱胎换骨的必经之路。掌握了分布式环境的加锁原理、超时释放以及续命机制,你在大型微服务架构面前就可以横着走了!