分布式锁实战演练:跨越 JVM 的并发掌控者

前言:为什么 synchronized 突然不香了?

前几篇博客我们把 synchronizedReentrantLock 这个级别的锁研究得透透的了。但在如今微服务、集群化部署的大时代下,这些传统的锁突然"失灵"了。为什么?

因为 synchronizedReentrantLock 都是单机锁(本地锁) ,它们的作用域只能是在同一个 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 和设置过期时间合并为一条原子命令!
致命漏洞:超时问题与误删。

  1. 锁只有 10 秒,但我业务执行了 15 秒!这时候第 10 秒锁过期了,别的机器把锁抢走了。(并发安全没了)
  2. 等我 15 秒办完业务,我习惯性地去 del 操作。结果,我竟然把刚刚别人加的锁给删掉了!!!

4. 王者级:终极形态 ------ Redisson 看门狗机制 (Watch Dog)

为了彻底解决"锁过期但业务还没跑完"的千古难题,开源框架 Redisson 大显神威!

它自带了一只**"看门狗"**:

  1. Redisson 帮你在后台开启一个守护线程。
  2. 当你拿到锁时,默认它只有 30 秒的寿命。
  3. 但是看门狗每隔 10 秒会去看一眼:主人你还在干活吗?如果还在干活,它就自动帮你发送一条续期命令,把锁重新续满到 30 秒!
  4. 只要你不死,看门狗会一直帮你续命!由于宕机会导致看门狗也一并死掉,所以根本不怕死锁!

二、 代码实战: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 分布式锁,是后端程序员脱胎换骨的必经之路。掌握了分布式环境的加锁原理、超时释放以及续命机制,你在大型微服务架构面前就可以横着走了!

相关推荐
无忧智库21 小时前
港口行业数字化转型:智慧港航信息化管理平台解决方案(PPT)
分布式·微服务·架构
isNotNullX1 天前
数据仓库是什么?怎么搭建数据仓库?
大数据·分布式·spark
爱学习的小囧1 天前
ESXi 8.0 vSwitch与dvSwitch(分布式交换机)核心区别
服务器·开发语言·分布式·php·虚拟化
星晨雪海1 天前
Redis 分布式 ID 生成器
数据库·redis·分布式
REDcker1 天前
RabbitMQ系列01 - 消息中间件与 MQ:在分布式系统里解决什么问题
分布式·rabbitmq
Albert Edison1 天前
【RabbitMQ】七种工作模式
java·开发语言·分布式·rabbitmq
☞遠航☜1 天前
rabbitmq 创建延迟队列
分布式·rabbitmq
Rick19931 天前
RabbitMQ 死信队列(DLX)
分布式·rabbitmq
REDcker1 天前
RabbitMQ系列02 - RabbitMQ 消息模型:Broker、交换器、队列与收发路径
分布式·rabbitmq·ruby
飞Link1 天前
LangGraph SDK 全量技术手册:分布式 Agent 集群的远程调用与编排引擎
开发语言·分布式·python·数据挖掘