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

相关推荐
程序员天天困10 小时前
Arthas ognl 表达式从入门到实战:掌握在线调试最强的表达式引擎
java·jvm·后端
梅头脑11 小时前
交易跨10个库、日均千万订单——选错一次分布式事务方案,加班三个月重写
java·分布式
长不胖的路人甲13 小时前
Serial 串行、Parallel 并行、CMS 并发收集器
java·开发语言·jvm
霸道流氓气质14 小时前
分布式系统中接口时序不确定性处理
java·开发语言·分布式
YOU OU16 小时前
Redis分布式锁
数据库·redis·分布式
小罗水18 小时前
第9章 RabbitMQ 消息队列与索引任务
分布式·rabbitmq
音符犹如代码19 小时前
Arthas classloader + sc 实战:JVM 类加载与手动加载
java·jvm·spring boot
naierfengdian19 小时前
分布式风力发电机机抗风扰、稳发电结构设计技术解析
分布式·能源
wWYy.1 天前
基于Raft分布式Kv存储:sendRequestVote
分布式
一水1 天前
JVM 面试宝典:从入门到调优实战
jvm·面试