JAVA面试|分布式锁执行过程
mhr18 2025-08-01 18:53 8 浏览 0 评论
我们来用通俗易懂的方式详细讲讲分布式锁的执行过程。想象一下你们公司只有一个非常抢手的会议室(共享资源),多个团队(分布式服务节点)都想用这个会议室开会(执行关键操作)。分布式锁就是用来确保同一时间只有一个团队能进入会议室的机制。
核心目标:在分布式系统(多台机器/多个服务)中,确保对某个共享资源(数据库的一行、一个文件、一个配置项等)或关键代码段(称为临界区)的访问是互斥的,即同一时间只有一个客户端(进程/线程)能执行。
一、关键参与者
客户端:你的应用程序(比如服务A、服务B、服务C)。
锁服务:一个所有客户端都能访问的、可靠的、提供原子操作的中间件。常用的有:
Redis:利用其单线程特性和SET key value NX PX milliseconds 命令(或Redlock算法)。
ZooKeeper:利用其顺序临时节点和Watcher 机制。
etcd:类似ZooKeeper,提供分布式协调服务。
数据库:利用唯一约束或乐观锁(较少用,性能较差)。
我们用Redis作为锁服务举例(因为它很常见且相对简单),整个过程如下:
二、阶段1:获取锁-抢会议室钥匙
1. 客户端申请锁:客户端A(比如订单服务的一个实例)想要修改用户余额(关键资源)。它向Redis锁服务发起一个请求:SET lock:user_balance_123 "clientA_unique_id" NX PX 10000
lock:user_balance_123: 锁的名字。通常基于要保护的资源命名(这里是用户123的余额),确保不同资源的锁互不影响。
"clientA_unique_id": 锁的值。极其重要!必须是申请锁的客户端唯一标识(比如进程ID+主机名+随机数)。后面释放锁时用来证明“这把锁是我加的”。
NX:意思是"Set if Not eXists"。只有当 lock:user_balance_123这个key不存在时,才设置它。如果key已经存在(表示锁已被别人持有),这个命令就失败(返回nil)。这保证了互斥性。
PX 10000:设置锁的过期时间为10秒(10000毫秒)。这是防止死锁的关键! 即使客户端A在持有锁期间崩溃了,10秒后锁会自动过期释放,避免资源永远被锁死。
类比:团队A去前台(Redis)申请会议室钥匙(锁),说:“如果钥匙没人拿(NX),请把钥匙给我(SET),并在钥匙上贴个纸条写上我的名字(clientA_unique_id),10分钟后(PX 10000)自动收回钥匙,不管我还在不在会议室。”
2. 锁服务响应:
成功:如果Redis发现lock:user_balance_123不存在,它成功创建了这个key,设置了值 clientA_unique_id和10秒过期时间,然后返回OK。客户端A成功获取到锁!
失败:如果Redis发现lock:user_balance_123已经存在(说明锁正被客户端B或C持有),它什么也不做,返回nil。客户端A获取锁失败。
3. 客户端处理结果:
成功:客户端A知道自己拿到了锁,可以安全地进入“临界区”执行关键操作(比如扣减用户123的余额)。
失败:客户端A知道自己没抢到钥匙。它通常有两种选择:
直接放弃/返回错误:告诉调用方“操作失败,请稍后重试”。(简单粗暴)
循环重试:等待一小段时间(比如50ms-200ms,随机一点避免惊群效应),然后回到步骤1,再次尝试发送SET ... NX PX ... 命令抢锁,直到成功或超过最大重试次数/超时时间。(更常见,但要注意重试风暴)
三、阶段 2:执行业务逻辑-在会议室开会
1. 持有锁操作
成功获取锁的客户端A(且锁未过期)开始执行它需要互斥访问的业务逻辑。
关键点:
操作要快!业务逻辑必须在锁的过期时间内完成!如果操作非常耗时,锁在操作完成前就过期了,其他客户端就能拿到锁,导致数据混乱(两个客户端同时修改余额)。
锁续期 (可选但推荐):如果操作确实可能较长,客户端A需要在锁过期前主动续期。这通常通过向Redis发送PEXPIRE lock:user_balance_123 10000命令(重新设置过期时间为10秒)来实现。客户端A需要启动一个后台线程/协程,定期(比如在锁过期时间的 1/3 时)检查自己是否还持有锁(比较value是否还是自己的clientA_unique_id)并进行续期。Redis官方推荐的Redisson 客户端就内置了这个看门狗(Watchdog)机制。
类比:团队A拿着钥匙进入会议室开会。他们必须在钥匙上的倒计时结束前开完会!如果会议可能超时,他们需要派一个人(看门狗线程)在倒计时还剩 1/3 时去前台(Redis)续租钥匙(PEXPIRE),延长倒计时。
四、阶段3:释放锁 - 归还会议室钥匙
1. 主动释放
当客户端A成功执行完业务逻辑后,它必须主动释放锁,让其他客户端有机会获取。释放锁不是简单删除key!
安全释放流程 (使用 Lua 脚本保证原子性):
客户端A向Redis发送一个Lua脚本。这个脚本做两件事:
检查lock:user_balance_123 这个key的值是否等于 clientA_unique_id。
如果相等,说明这个锁确实是客户端A持有的,然后删除 (DEL) 这个key。
如果不相等,说明锁可能已经过期被自动删除,或者被其他客户端持有(比如客户端A操作太慢锁过期了,客户端B已经拿到锁),那么什么也不做。
为什么需要Lua脚本?因为“检查值”和“删除key”必须是原子操作!如果分两步走(先GET再DEL):
客户端A GET到值还是clientA_unique_id。
此时锁过期了!Redis自动删除了key。
客户端B成功SET ... NX ...获取了新锁。
客户端A执行DEL,把客户端B刚获得的锁给删了!灾难发生!Lua脚本在Redis单线程执行,保证了检查值和删除的原子性。
类比:团队A开完会了,拿着钥匙去前台归还。前台(Redis Lua 脚本)会做:1) 看看钥匙上贴的名字是不是团队A(检查value);2) 如果是,才把钥匙收回去(DEL);3) 如果不是(可能团队A开会超时,钥匙被自动收回,前台已经把钥匙给了团队B),前台就不收这把钥匙(避免把团队B的钥匙错误收走)。
2. 被动释放 (过期)
如果客户端A在持有锁期间崩溃了,或者它的业务逻辑执行时间超过了锁的过期时间且没有成功续期,那么Redis会在锁过期时间到达时自动删除这个key。锁被释放,其他客户端可以尝试获取。
类比:团队A开会开到一半,会议室着火了(客户端A崩溃),或者他们开会严重超时(超过钥匙上的倒计时)。钥匙上的倒计时结束,前台(Redis)自动收回钥匙,其他团队可以来申请使用会议室了。
五、关键挑战与注意事项
1. 时钟漂移
分布式系统中,不同机器的时钟可能不完全同步。这会影响锁过期时间的判断。Redlock算法试图解决这个问题,但也增加了复杂性。
2. 网络延迟与分区
网络问题可能导致客户端认为锁已过期或已获取成功,但实际上锁服务状态不一致。CAP理论告诉我们,在分区发生时,需要在一致性(C)和可用性(A)之间权衡。分布式锁通常选择牺牲一点可用性来保证强一致性(CP),或者牺牲强一致性保证高可用(AP)。
3. 锁续期的可靠性
负责续期的看门狗线程如果挂掉,可能导致锁提前过期。需要仔细设计。
4. 非阻塞vs阻塞
上面描述的是非阻塞获取(失败就返回或重试)。有些实现(如 ZooKeeper Watch)可以实现阻塞获取(排队等待,锁释放时通知)。
5. 可重入性
同一个客户端内,同一个线程能否多次获取同一把锁?这需要锁服务支持(比如在value里记录持有计数)。Redis本身不支持,需要客户端模拟;ZooKeeper天然支持。
6. 性能
频繁的锁获取/释放会带来网络开销和锁服务压力。锁的粒度(保护的范围大小)要合理,避免粗粒度锁成为瓶颈。
7. 正确性vs性能vs复杂度
没有完美的分布式锁方案。Redis简单高效但可靠性依赖单点/主从复制(主从切换可能丢锁,Redlock更复杂);ZooKeeper/etcd 可靠性更高(基于共识算法)但性能稍差,使用更复杂。根据业务场景选择。
六、通俗总结
分布式锁就像大家抢一个唯一的、带倒计时的令牌(钥匙)。谁先通过特定命令(SET NX PX)在公共储物柜(Redis/ZK)里成功存下这个写着自己名字的令牌,谁就获得使用权。使用时要快,或者在倒计时结束前续租。用完了要按规矩(Lua脚本验证名字)把令牌取出来归还。万一有人拿着令牌中途“晕倒”(崩溃),倒计时结束令牌也会自动消失,避免一直占着茅坑。核心就是利用一个可靠的第三方服务提供的原子操作来实现互斥。理解每一步的目的(互斥、防死锁、安全释放)和潜在风险(过期、网络问题)非常重要。
相关推荐
- Java面试题及答案总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Redis、Linux、SpringBoot、Spring、MySQ...
- Java面试题及答案最全总结(2025春招版)
-
大家好,我是Java面试分享最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Spring...
- Java面试题及答案最全总结(2025版持续更新)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 蚂蚁金服面试题(附答案)建议收藏:经典面试题解析
-
前言最近编程讨论群有位小伙伴去蚂蚁金服面试了,以下是面试的真题,跟大家一起来讨论怎么回答。点击上方“捡田螺的小男孩”,选择“设为星标”,干货不断满满1.用到分布式事务嘛?为什么用这种方案,有其他方案...
- 测试工程师面试必问的十道题目!全答上来的直接免试
-
最近参加运维工程师岗位的面试,笔者把自己遇到的和网友分享的一些常见的面试问答收集整理出来了,希望能对自己和对正在准备面试的同学提供一些参考。一、Mongodb熟悉吗,一般部署几台?部署过,没有深入研究...
- 10次面试9次被刷?吃透这500道大厂Java高频面试题后,怒斩offer
-
很多Java工程师的技术不错,但是一面试就头疼,10次面试9次都是被刷,过的那次还是去了家不知名的小公司。问题就在于:面试有技巧,而你不会把自己的能力表达给面试官。应届生:你该如何准备简历,面试项目和...
- java高频面试题整理
-
【高频常见问题】1、事务的特性原子性:即不可分割性,事务要么全部被执行,要么就全部不被执行。一致性或可串性:事务的执行使得数据库从一种正确状态转换成另一种正确状态隔离性:在事务正确提交之前,不允许把该...
- 2025 年最全 Java 面试题,京东后端面试面经合集,答案整理
-
最近京东搞了个TGT计划,针对顶尖青年技术天才,直接宣布不设薪资上限。TGT计划面向范围包括2023年10月1日到2026年9月30日毕业的海内外本硕博毕业生。时间范围还...
- idGenerator测评
-
工作中遇到需要生成随机数的需求,看了一个个人开发的基于雪花算法的工具,今天进行了一下测评(测试)。idGenerator项目地址见:https://github.com/yitter/IdGenera...
- 2024年开发者必备:MacBook Pro M1 Max深度体验与高效工作流
-
工作机器我使用的是一台16英寸的MacBookProM1Max。这台电脑的表现堪称惊人!它是我用过的最好的MacBook,短期内我不打算更换它。性能依然出色,即使在执行任务时也几乎听不到风扇的...
- StackOverflow 2022 年度调查报告
-
一个月前,StackOverflow开启了2022年度开发者调查,历时一个半月,在6月22日,StackOverflow正式发布了2022年度开发者调查报告。本次报告StackO...
- 这可能是最全面的SpringDataMongoDB开发笔记
-
MongoDB数据库,在最近使用越来越广泛,在这里和Java的开发者一起分享一下在Java中使用Mongodb的相关笔记。希望大家喜欢。关于MongoDB查询指令,请看我的上一篇文章。SpringD...
- Mac M2 本地部署ragflow
-
修改配置文件Dockerfile文件ARGNEED_MIRROR=1//开启国内镜像代理docker/.envREDIS_PORT=6380//本地redis端口冲突RAGFLOW_IMA...
- 别再傻傻分不清!localhost、127.0.0.1、本机IP,原来大有讲究!
-
调试接口死活连不上?部署服务队友访问不了?八成是localhost、127.0.0.1、本机IP用混了!这三个看似都指向“自己”的东西,差之毫厘谬以千里。搞不清它们,轻则调试抓狂,重则服务裸奔。loc...
- 我把 Mac mini 托管到机房了:一套打败云服务器的终极方案
-
我把我积灰的Macmini托管到机房了,有图有真相。没想到吧?一台在家吃灰的苹果电脑,帮我省了大钱!对,就是控制了自己的服务器,省了租用云服务器的钱,重要数据还全捏在自己手里,这感觉真爽。你可...
你 发表评论:
欢迎- 一周热门
-
-
Redis客户端 Jedis 与 Lettuce
-
高并发架构系列:Redis并发竞争key的解决方案详解
-
redis如何防止并发(redis如何防止高并发)
-
Java SE Development Kit 8u441下载地址【windows版本】
-
开源推荐:如何实现的一个高性能 Redis 服务器
-
redis安装与调优部署文档(WinServer)
-
Redis 入门 - 安装最全讲解(Windows、Linux、Docker)
-
一文带你了解 Redis 的发布与订阅的底层原理
-
Redis如何应对并发访问(redis控制并发量)
-
Oracle如何创建用户,表空间(oracle19c创建表空间用户)
-
- 最近发表
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle主从同步 (56)
- oracle 乐观锁 (53)
- redis 命令 (83)
- php redis (97)
- redis 存储 (67)
- redis 锁 (74)
- 启动 redis (73)
- redis 时间 (60)
- redis 删除 (69)
- redis内存 (64)
- redis并发 (53)
- redis 主从 (71)
- redis同步 (53)
- redis结构 (53)
- redis 订阅 (54)
- redis 登录 (62)
- redis 面试 (58)
- redis问题 (54)
- 阿里 redis (67)
- redis的缓存 (57)
- lua redis (59)
- redis 连接池 (64)