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

相关推荐
2401_884563242 小时前
进阶技巧与底层原理
jvm·数据库·python
2401_873204652 小时前
使用Pandas进行数据分析:从数据清洗到可视化
jvm·数据库·python
m0_662577972 小时前
自动化与脚本
jvm·数据库·python
曼彻斯特的海边2 小时前
synchronized优化原理
jvm·juc·synchronized
standovon3 小时前
RabbitMQ 的介绍与使用
分布式·rabbitmq·ruby
dapeng28703 小时前
使用Fabric自动化你的部署流程
jvm·数据库·python
2401_874732533 小时前
构建一个桌面版的天气预报应用
jvm·数据库·python
qq_417695053 小时前
实战:用Python开发一个简单的区块链
jvm·数据库·python
2301_767902644 小时前
ceph分布式存储(一)
分布式·ceph