4、分布式锁
4.1 、基本原理和实现方式对比
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。
分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路
那么分布式锁他应该满足一些什么样的条件呢?
可见性:多个线程都能看到相同的结果,注意:这个地方说的可见性并不是并发编程中指的内存可见性,只是说多个进程之间都能感知到变化的意思
互斥:互斥是分布式锁的最基本的条件,使得程序串行执行
高可用:程序不易崩溃,时时刻刻都保证较高的可用性
高性能:由于加锁本身就让性能降低,所有对于分布式锁本身需要他就较高的加锁性能和释放锁性能
安全性:安全也是程序中必不可少的一环
常见的分布式锁有三种
Mysql:mysql本身就带有锁机制,但是由于mysql性能本身一般,所以采用分布式锁的情况下,其实使用mysql作为分布式锁比较少见
Redis:redis作为分布式锁是非常常见的一种使用方式,现在企业级开发中基本都使用redis或者zookeeper作为分布式锁,利用setnx这个方法,如果插入key成功,则表示获得到了锁,如果有人插入成功,其他人插入失败则表示无法获得到锁,利用这套逻辑来实现分布式锁
Zookeeper:zookeeper也是企业级开发中较好的一个实现分布式锁的方案,由于本套视频并不讲解zookeeper的原理和分布式锁的实现,所以不过多阐述
🧠 理论理解:
分布式锁的本质,是用全局共享资源(如 Redis、ZooKeeper)做成唯一的、全局可见的“锁状态”标识,来协调分布式环境下不同节点上的任务互斥。相比单机内存锁(如 synchronized、ReentrantLock),它要解决网络可见性、节点崩溃、锁续期等更复杂的问题。要设计好分布式锁,必须同时满足:互斥性(只有一个持有者)、避免死锁(比如过期自动释放)、高可用(分布式存储系统本身稳定)、高性能(加解锁效率高)、安全性(避免误删、避免悬挂)。
🏢 企业实战理解:
BAT 和字节跳动中,分布式锁最常用于电商秒杀、库存扣减、优惠券发放等强一致场景。Google 和 OpenAI 则更多是用分布式锁管理 GPU/TPU 资源调度和任务分配,保证多算力节点之间不会重复计算或竞争写入。NVIDIA 在大规模渲染任务中,也用分布式锁协调渲染任务和帧缓存共享,避免多个渲染器重复写入同一帧。
❓ 1. 分布式锁和本地锁有什么区别?为什么分布式系统必须用分布式锁?
✅ 答案:
本地锁(如 synchronized、ReentrantLock)只能保证当前 JVM 内部多线程互斥,而分布式锁需要保证跨多个 JVM、多个节点、多个机器进程的互斥。分布式系统中,一份全局资源(如库存、优惠券、配置中心、GPU 资源)通常由多个节点共同访问,必须确保全局唯一性,因此要用 Redis、ZooKeeper、etcd 等全局可见的锁机制。
大厂实战:
阿里在双十一的库存扣减中,用 Redis 分布式锁控制多应用节点并发扣库存;Google 在分布式计算框架(如 MapReduce)中用 ZooKeeper 协调任务分片调度;OpenAI 在大规模推理时必须确保分布式推理节点间任务调度不会重复执行。
🌍 场景题 1:阿里双十一库存超卖
❓ 你的团队负责电商库存系统。双十一当天流量巨大,多节点同时扣减 Redis 库存。结果发现出现超卖:库存变负数。你怎么优化?
✅ 答案方案:
选择 Redis 分布式锁,给每个 SKU 商品设置独立锁。用 setnx + expire 加锁,扣减库存前先抢锁,扣完再解锁,避免并发写入。
✅ 实现方式:
-
key:lock:sku:12345
-
value:UUID+线程ID
-
扣减前:用 Lua 判断当前是否持有锁,原子扣减库存
-
锁超时:设置 30 秒 TTL
-
续期:用 Redisson 自动续期(防止长事务锁丢失)
-
注意:锁粒度按 SKU,避免全局大锁
✅ 遇到的问题:
-
锁续期太短:线程没跑完锁丢了 → 需要 watchdog
-
Redis 单点故障:主挂、从未同步 → 用 Redis Sentinel 或集群
-
大促瞬时流量:锁竞争严重 → 用分片分库存(每个分片独立锁)
4.2 、Redis分布式锁的实现核心思路
实现分布式锁时需要实现的两个基本方法:
-
获取锁:
-
互斥:确保只能有一个线程获取锁
-
非阻塞:尝试一次,成功返回true,失败返回false
-
-
释放锁:
-
手动释放
-
超时释放:获取锁时添加一个超时时间
-
核心思路:
我们利用redis 的setNx 方法,当有多个线程进入时,我们就利用该方法,第一个线程进入时,redis 中就有这个key 了,返回了1,如果结果是1,则表示他抢到了锁,那么他去执行业务,然后再删除锁,退出锁逻辑,没有抢到锁的哥们,等待一定时间后重试即可
🧠 理论理解:
Redis 的分布式锁用的是 set nx ex:set-if-not-exists(抢占唯一标志位)+ 过期时间(防止死锁)。这种方案的核心是简单、高效,适合短时任务。但它需要考虑两个挑战:一是持有锁的线程挂掉怎么办(靠超时释放);二是非原子性操作(get → compare → delete)中间可能有线程切换或超时,导致误删别人的锁。
🏢 企业实战理解:
字节跳动在广告推荐系统中,用 Redis 锁做高频流量位控制(如新广告创意上线时的分布式刷流量监测);而 Google 在 Kubernetes 控制平面上曾用 etcd(分布式键值存储)实现类似锁机制,用于控制 leader election(主节点竞选),这和 Redis 实现方式在思想上很相近,但 Google 会更倾向于基于 Raft 协议的分布式一致性而非单一 Redis。
❓ 2. Redis 分布式锁最简单的实现方式是?它存在哪些问题?
✅ 答案:
最简单方式是用 setnx + expire,抢锁时用 setnx(不存在才设置),并设置一个过期时间,保证死锁时自动释放。
问题有三:
① 误删:如果持锁线程超时,锁被别人抢走,原线程恢复后直接 delete,会误删别人的锁。
② 原子性:get + compare + delete 不是原子操作,可能中间被别的线程插入。
③ 不支持重入、续期:如果一个任务执行时间超过锁的过期时间,锁会失效。
大厂实战:
字节跳动在直播间并发推荐中,遇到过超时误删问题,用 Lua 脚本解决原子性;阿里中台使用 Redisson 来提供锁续期和可重入能力。
🌍 场景题 2:字节跳动短视频推荐限流
❓ 推荐系统每秒上万个请求,热门视频容易被刷量。你要给每个视频加并发限制,保证同一时刻最多处理 N 个推荐请求。你怎么设计?
✅ 答案方案:
用 Redis 分布式信号量(semaphore)或限流器。每个视频单独维护信号量 key。
✅ 实现方式:
-
key:semaphore:video:98765
-
初始值:最大并发数(如 100)
-
请求前:decr 信号量,>0 继续,请求后 incr
-
请求失败或超时:incr 归还
-
执行工具:Redisson 提供分布式信号量实现
-
监控:结合指标(比如 QPS、失败率)动态调节最大并发
✅ 遇到的问题:
-
网络分区:信号量状态不一致 → 用 Redis 集群保证一致性
-
请求崩溃未归还:泄漏信号量 → 设置超时自动归还机制
-
热门视频压力极大:要动态扩容并发 → 支持运维调整 semaphore 数量
4.3 实现分布式锁版本一
-
加锁逻辑
锁的基本接口
SimpleRedisLock
利用setnx方法进行加锁,同时增加过期时间,防止死锁,此方法可以保证加锁和增加过期时间具有原子性
private static final String KEY_PREFIX="lock:"
@Override
public boolean tryLock(long timeoutSec) {
// 获取线程标示
String threadId = Thread.currentThread().getId()
// 获取锁
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name, threadId + "", timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
-
释放锁逻辑
SimpleRedisLock
释放锁,防止删除别人的锁
public void unlock() {
//通过del删除锁
stringRedisTemplate.delete(KEY_PREFIX + name);
}
-
修改业务代码
@Override
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否开始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
// 尚未开始
return Result.fail("秒杀尚未开始!");
}
// 3.判断秒杀是否已经结束
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
// 尚未开始
return Result.fail("秒杀已经结束!");
}
// 4.判断库存是否充足
if (voucher.getStock() < 1) {
// 库存不足
return Result.fail("库存不足!");
}
Long userId = UserHolder.getUser().getId();
//创建锁对象(新增代码)
SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
//获取锁对象
boolean isLock = lock.tryLock(1200);
//加锁失败
if (!isLock) {
return Result.fail("不允许重复下单");
}
try {
//获取代理对象(事务)
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
} finally {
//释放锁
lock.unlock();
}
}
🧠 理论理解:
第一版简单实现:抢锁用 setnx + expire,解锁直接 delete。这能解决大部分场景下的互斥,但它无法防止锁被误删(比如抢到锁的线程因为耗时过长,锁自动释放后被别的线程抢走,但原线程恢复后直接 delete,误删了别人的锁)。
🏢 企业实战理解:
阿里在电商双十一抢购中,曾用第一版 Redis 分布式锁做秒杀限流,发现问题后逐步引入版本号校验。Google Cloud Platform(GCP)和 NVIDIA 的容器化环境中,原始锁机制也只适用于单节点调度,一旦涉及跨节点分布式调度,就必须在设计上增强一致性和版本管理。
❓ 3. 如何用 Lua 脚本解决分布式锁误删和原子性问题?
✅ 答案:
Lua 脚本能把多条 Redis 命令(如 get + compare + delete)封装为一次原子执行,保证在 Redis 内部“要么全做,要么全不做”。在分布式锁里,我们用 Lua 脚本先判断当前锁的 value 是否等于线程标识,如果相等再执行删除,这避免了误删别人锁的问题。
大厂实战:
阿里和字节都有自研工具库封装了 Redis Lua 脚本,让开发者直接调用,不用关心底层细节。Google 在 Spanner 和 Bigtable 中不用 Lua,而用自研事务框架提供原子操作,但设计目标相似。
🌍 场景题 3:Google MapReduce 任务调度
❓ Google 内部分布式计算调度 MapReduce 任务,要避免多个调度器重复调度相同任务。你怎么保证同一任务只分配一次?
✅ 答案方案:
用 ZooKeeper 分布式锁或 Redis 锁,保证调度唯一性。
✅ 实现方式:
-
key:task:mapreduce:taskid-001
-
每个调度器先抢锁,抢到的调度任务,抢不到直接跳过
-
ZooKeeper 实现:用 ephemeral 节点(短连接锁)
-
Redis 实现:用 setnx + Lua 确保锁原子性
-
高可用:ZooKeeper 自带,Redis 用 Sentinel 或集群
-
日志:任务执行完清理锁 + 持久化状态,防止遗漏
✅ 遇到的问题:
-
ZooKeeper 连接抖动:session 丢失 → 设置超时重连
-
Redis 脑裂:主从不一致 → 要用 Redlock 或强一致集群
-
大规模任务:锁竞争激烈 → 按分片加锁(按 bucket、region)
4.4 Redis分布式锁误删情况说明
逻辑说明:
持有锁的线程在锁的内部出现了阻塞,导致他的锁自动释放,这时其他线程,线程2来尝试获得锁,就拿到了这把锁,然后线程2在持有锁执行过程中,线程1反应过来,继续执行,而线程1执行过程中,走到了删除锁逻辑,此时就会把本应该属于线程2的锁进行删除,这就是误删别人锁的情况说明
解决方案:解决方案就是在每个线程释放锁的时候,去判断一下当前这把锁是否属于自己,如果属于自己,则不进行锁的删除,假设还是上边的情况,线程1卡顿,锁自动释放,线程2进入到锁的内部执行逻辑,此时线程1反应过来,然后删除锁,但是线程1,一看当前这把锁不是属于自己,于是不进行删除锁逻辑,当线程2走到删除锁逻辑时,如果没有卡过自动释放锁的时间点,则判断当前这把锁是属于自己的,于是删除这把锁。
🧠 理论理解:
误删问题本质是锁持有者身份消失后(超时、挂起)被其他线程接管,而原持有者恢复时没有校验锁身份就直接 delete。解决方案是:在 value 中存储唯一标识(如 UUID + 线程 ID),解锁时先比对,只有是自己的锁才能删。这是分布式锁设计中“持有者确认”原则的体现。
🏢 企业实战理解:
BAT 在处理高并发抢券场景时,会强制为每个锁分配唯一 owner_id(持有者 ID),并在解锁时用 Lua 脚本校验。OpenAI 在多 GPU 模型训练时,主控进程必须判断 lockfile 持有者,避免多 GPU 互相覆盖写入状态。NVIDIA 在 DGX 系统中会为每个锁定义“持有者心跳”,一旦发现 owner 丢失才允许别人接管。
❓ 4. 分布式锁必须有过期时间吗?为什么?
✅ 答案:
必须有。因为分布式系统中线程或进程随时可能崩溃或超时,如果锁没有自动过期机制,可能会永久挂住(死锁),导致资源永远无法释放。设置过期时间可以保证“失败自动释放”。
大厂实战:
NVIDIA 在多显卡集群的任务调度上,为显存锁设计了严格的时间 TTL(如 30 秒),超过自动收回。OpenAI 在多 GPU 任务中也给分布式锁加了心跳机制,保证主进程意外挂掉时,锁可被其他节点接管。
🌍 场景题 4:OpenAI 大模型推理限流
❓ 多台 GPU 服务器跑大模型推理,用户瞬时涌入,请求数远超 GPU 容量。你怎么用分布式锁或分布式限流保证稳定?
✅ 答案方案:
用 Redis 分布式限流 + 分布式信号量,给每台 GPU 控制最大并发。
✅ 实现方式:
-
key:gputask:node-1
-
信号量值:GPU 核心并发上限(如 16)
-
请求进来:先 decr 信号量,成功则投递任务;失败则排队或拒绝
-
超时或完成:incr 信号量归还
-
错误防护:任务崩溃检测(定时清理死锁、归还资源)
-
调度:用 Redisson 或 NVIDIA Triton 自带 API 封装
✅ 遇到的问题:
-
跨节点一致性:要用 Redis Cluster
-
崩溃未释放:需要 watchdog 自动归还信号量
-
热点节点:请求打爆单节点 → 做任务分散调度,跨节点分流
4.5 解决Redis分布式锁误删问题
需求:修改之前的分布式锁实现,满足:在获取锁时存入线程标示(可以用UUID表示) 在释放锁时先获取锁中的线程标示,判断是否与当前线程标示一致
-
如果一致则释放锁
-
如果不一致则不释放锁
核心逻辑:在存入锁时,放入自己线程的标识,在删除锁时,判断当前这把锁的标识是不是自己存入的,如果是,则进行删除,如果不是,则不进行删除。
具体代码如下:加锁
private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
@Override
public boolean tryLock(long timeoutSec) {
// 获取线程标示
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 获取锁
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
释放锁
public void unlock() {
// 获取线程标示
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 获取锁中的标示
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
// 判断标示是否一致
if(threadId.equals(id)) {
// 释放锁
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
有关代码实操说明:
在我们修改完此处代码后,我们重启工程,然后启动两个线程,第一个线程持有锁后,手动释放锁,第二个线程 此时进入到锁内部,再放行第一个线程,此时第一个线程由于锁的value值并非是自己,所以不能释放锁,也就无法删除别人的锁,此时第二个线程能够正确释放锁,通过这个案例初步说明我们解决了锁误删的问题。
🧠 理论理解:
比对锁 + 删除锁之间的非原子性,是分布式锁最大隐患之一。单独用两条 Redis 命令(get + del)就像银行转账时先查看余额再扣款,中途一旦被别的线程插入,就会出错。Lua 脚本把所有命令打包成一段 atomic 脚本,在 Redis 内部一次性执行,确保“要么全做,要么都不做”。
🏢 企业实战理解:
BAT 内部已经把 Lua 脚本封装成分布式工具包(如 Tair、DRDS),开发者只要调用标准库即可。Google 在 Spanner 和 Bigtable 系统里不直接用 Lua,而是用事务(transaction)封装一段原子操作。NVIDIA 在显存资源调度中,也会用类似内核级 atomic 指令,确保显存分配和释放的不可分割性。
❓ 5. 如果一个持锁线程超时了怎么办?怎么避免锁被误删或误续期?
✅ 答案:
超时后,新的线程可以拿到锁。如果原线程恢复,为避免误删,必须让锁 value 带有唯一标识(如 UUID + 线程 ID),删除锁时先比对。为了避免误续期,要设计“锁续命”机制(如 Redisson Watchdog)只给当前持锁线程续期,别人续不上。
大厂实战:
字节跳动使用 Redisson 的自动续期,主线程持有锁时,后台有 watch dog 自动帮它续租,超时才释放。Google 的 etcd 和 ZooKeeper 则通过 session + lease 租约机制管理。
4.6 分布式锁的原子性问题
更为极端的误删逻辑说明:
线程1现在持有锁之后,在执行业务逻辑过程中,他正准备删除锁,而且已经走到了条件判断的过程中,比如他已经拿到了当前这把锁确实是属于他自己的,正准备删除锁,但是此时他的锁到期了,那么此时线程2进来,但是线程1他会接着往后执行,当他卡顿结束后,他直接就会执行删除锁那行代码,相当于条件判断并没有起到作用,这就是删锁时的原子性问题,之所以有这个问题,是因为线程1的拿锁,比锁,删锁,实际上并不是原子性的,我们要防止刚才的情况发生,
🧠 理论理解:
分布式锁的高阶挑战除了互斥和误删,还有:续期(防止长期任务超时)、重入(同一线程多次获取)、公平性(按请求顺序排队)、可重试(防止线程饿死)。这些需求超出了 Redis 的基本能力,需要引入 Redisson 这样的更完整中间件。
🏢 企业实战理解:
阿里、字节在生产环境几乎都用 Redisson(或自研组件)解决锁续期、可重入、信号量、读写锁等复杂场景。Google 则用 Raft + Paxos 协议提供分布式一致性,解决比锁更底层的“分布式元数据同步”问题。OpenAI 在大模型推理的多实例调度中,也用分布式锁调度多个 inference 请求,保证在 GPU 资源分配上不冲突。
❓ 6. MySQL 可以实现分布式锁吗?为什么它不是首选?
✅ 答案:
可以,用悲观锁(如 select ... for update)、唯一索引(唯一冲突阻塞)或 GET_LOCK() 实现。但它不是首选,因为:① MySQL 性能远不如 Redis(内存 vs 磁盘);② 数据库本身是强一致存储,用它实现短时锁、频繁锁解会浪费资源;③ MySQL 锁有连接生命周期依赖,长时间持锁有风险。
大厂实战:
阿里和字节通常只在特殊场景(如业务必须强一致)才用 MySQL 分布式锁,大多数高并发场景还是 Redis 或 ZooKeeper。
4.7 Lua脚本解决多条命令原子性问题
Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言,它的基本语法大家可以参考网站:Lua 教程 | 菜鸟教程,这里重点介绍Redis提供的调用函数,我们可以使用lua去操作redis,又能保证他的原子性,这样就可以实现拿锁比锁删锁是一个原子性动作了,作为Java程序员这一块并不作一个简单要求,并不需要大家过于精通,只需要知道他有什么作用即可。
这里重点介绍Redis提供的调用函数,语法如下:
redis.call('命令名称', 'key', '其它参数', ...)
例如,我们要执行set name jack,则脚本是这样:
# 执行 set name jack redis.call('set', 'name', 'jack')
例如,我们要先执行set name Rose,再执行get name,则脚本如下:
# 先执行 set name jack
redis.call('set', 'name', 'Rose')
# 再执行 get name
local name = redis.call('get', 'name')
# 返回
return name
写好脚本以后,需要用Redis命令来调用脚本,调用脚本的常见命令如下:
例如,我们要执行 redis.call('set', 'name', 'jack') 这个脚本,语法如下:
如果脚本中的key、value不想写死,可以作为参数传递。key类型参数会放入KEYS数组,其它参数会放入ARGV数组,在脚本中可以从KEYS和ARGV数组获取这些参数:
接下来我们来回一下我们释放锁的逻辑:
释放锁的业务流程是这样的
1、获取锁中的线程标示
2、判断是否与指定的标示(当前线程标示)一致
3、如果一致则释放锁(删除)
4、如果不一致则什么都不做
如果用Lua脚本来表示则是这样的:
最终我们操作redis的拿锁比锁删锁的lua脚本就会变成这样
-- 这里的 KEYS[1] 就是锁的key,这里的ARGV[1] 就是当前线程标示
-- 获取锁中的标示,判断是否与当前线程标示一致
if (redis.call('GET', KEYS[1]) == ARGV[1]) then
-- 一致,则删除锁
return redis.call('DEL', KEYS[1])
end
-- 不一致,则直接返回
return 0
❓ 7. 分布式锁实现中,怎么解决集群场景下的锁粒度?
✅ 答案:
锁粒度是指锁的精细程度。全局锁粒度粗、竞争大(性能低);细粒度锁更高效。可以用多维度 key 设计:比如 order:userId:lock、sku:12345:lock。每个用户、每个商品、每个库存分开加锁,避免无关任务互相阻塞。
大厂实战:
阿里在电商购物车里,用户购物用 userId 级锁,商品库存用 skuId 级锁,双维度分离;Google 在分布式存储中,把锁分散到具体数据分片(shard)级别,避免全局瓶颈。
4.8 利用Java代码调用Lua脚本改造分布式锁
lua脚本本身并不需要大家花费太多时间去研究,只需要知道如何调用,大致是什么意思即可,所以在笔记中并不会详细的去解释这些lua表达式的含义。
我们的RedisTemplate中,可以利用execute方法去执行lua脚本,参数对应关系就如下图股
Java代码
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}
public void unlock() {
// 调用lua脚本
stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + Thread.currentThread().getId());
}
经过以上代码改造后,我们就能够实现 拿锁比锁删锁的原子性动作了~
小总结:
基于Redis的分布式锁实现思路:
-
利用set nx ex获取锁,并设置过期时间,保存线程标示
-
释放锁时先判断线程标示是否与自己一致,一致则删除锁
-
特性:
-
利用set nx满足互斥性
-
利用set ex保证故障时锁依然能释放,避免死锁,提高安全性
-
利用Redis集群保证高可用和高并发特性
-
-
笔者总结:我们一路走来,利用添加过期时间,防止死锁问题的发生,但是有了过期时间之后,可能出现误删别人锁的问题,这个问题我们开始是利用删之前 通过拿锁,比锁,删锁这个逻辑来解决的,也就是删之前判断一下当前这把锁是否是属于自己的,但是现在还有原子性问题,也就是我们没法保证拿锁比锁删锁是一个原子性的动作,最后通过lua表达式来解决这个问题
但是目前还剩下一个问题锁不住,什么是锁不住呢,你想一想,如果当过期时间到了之后,我们可以给他续期一下,比如续个30s,就好像是网吧上网, 网费到了之后,然后说,来,网管,再给我来10块的,是不是后边的问题都不会发生了,那么续期问题怎么解决呢,可以依赖于我们接下来要学习redission啦
测试逻辑:
第一个线程进来,得到了锁,手动删除锁,模拟锁超时了,其他线程会执行lua来抢锁,当第一天线程利用lua删除锁时,lua能保证他不能删除他的锁,第二个线程删除锁时,利用lua同样可以保证不会删除别人的锁,同时还能保证原子性。
❓ 8. 你熟悉 Redisson 吗?它相比自己手写 Redis 分布式锁有什么优势?
✅ 答案:
Redisson 是 Redis 官方推荐的 Java 客户端,内置了分布式锁、读写锁、公平锁、信号量、倒计时器等多种高级功能。相比手写 Redis 锁,优势是:
-
自动续期(watchdog)
-
可重入(reentrant)
-
公平(按请求顺序)
-
多种复杂锁(如信号量、读写锁)
-
支持分布式集群、容灾
开发者无需关注底层 Lua、细节,实现快、健壮性强。
大厂实战:
阿里、字节广泛使用 Redisson 来代替手写 Redis 锁,Google 则用更低层次的 Raft、Paxos 协议来做分布式一致性,Redisson 类似工具是很重要的“业务层分布式一致性组件”。
🌍 场景题 5:阿里云分布式配置中心锁
❓ 阿里云配置中心多个实例同时发布配置,必须保证一个时间点只有一个实例能成功推送。你怎么设计?
✅ 答案方案:
用 Redis 分布式锁(或 ZooKeeper)全局唯一锁。
✅ 实现方式:
-
key:config:publish:env-prod
-
value:发布者实例 ID
-
抢锁:用 Redisson + watchdog 自动续期
-
发布完成:发布者删除锁
-
宕机处理:锁超时自动释放,允许其他节点重试
-
多环境隔离:用 env + namespace 做 key 前缀隔离
✅ 遇到的问题:
-
集群扩容:新实例加入抢锁失败 → 有重试机制
-
ZooKeeper session 丢失:要设计重连和重新抢锁
-
发布任务失败:要有回滚机制防止脏数据推送