步步糕升 发表于 前天 07:12

什么是分布式锁:多服务集群下,为什么要抢同一把 “锁”

<div class="quote"><blockquote><font size="4">单机时代,多线程并发冲突可以依靠本地锁(Java 的synchronized、ReentrantLock)解决。但微服务普及之后,同一个服务会部署多台实例,不同机器、不同进程之间,本地锁互相看不见,就需要<b>分布式锁</b>。分布式锁是分布式系统中跨机器协调多个服务实例访问共享资源的互斥机制,保证同一时间只有一个执行者处理关键业务,避免多节点同时修改数据带来错乱。</font><br><blockquote><font size="4">提示:本文为后端架构科普,讲解原理与工程实践,不直接作为生产上线代码。</font><br></blockquote></blockquote></div><br><font size="5">一、本地锁与分布式锁的通俗理解</font><br><br>把修改一份共享业务数据比作多人修改同一份文档:<br><ul><li><b>单机本地锁</b>:同一台机器内,谁拿到 “笔” 谁修改,其他人等待。所有线程在同一个进程内,锁可以被所有人看见。</li><li><b>分布式集群困境</b>:服务部署在多台服务器,每台机器都有自己的本地锁。A 机器上的锁,B 机器完全感知不到。相当于每个办公室各配一把锁,但大家操作的是远处同一个仓库,锁住自己办公室完全起不到保护共享资源的效果。<b>这时就需要一把所有机器都能共同看见的锁,也就是分布式锁</b>。<br></li></ul><font size="4">典型业务场景</font><br><br><ul type="1" class="litype_1"><li><b>电商库存扣减秒杀</b>:商品仅剩 1 件,上百请求打到多台实例。不加控制,多实例同时判定库存充足,同时扣减,发生超卖。分布式锁保证同一商品同一时刻只有一个实例执行库存校验扣减。</li><li><b>支付回调重复处理</b>:支付平台多次推送回调,多实例同时收到同一个订单通知,可能出现重复发货、重复开通会员。对订单号加分布式锁,同一个订单同一时间只允许一个实例处理。</li><li><b>集群定时任务</b>:服务多实例部署,防止多台机器同一时刻重复执行定时任务。</li><li><b>积分变更、订单结算</b>:防止多节点并发修改同一用户、同一订单的数据。<br></li></ul><blockquote>重要认知:分布式锁不是用来提升系统速度,很多场景会让请求排队,牺牲部分并发能力,换取数据一致性安全。<br></blockquote><font size="5">二、主流实现方案对比</font><br><br><font size="4">1. Redis 实现分布式锁(生产最常用,高性能)</font><br><br>利用 Redis 作为所有实例都能访问的公共协调存储。<br><ul><li><b>加锁核心要求:必须原子操作</b>,不能先判断锁是否存在,再设置锁,两步分开高并发下会失效。使用SET lock:order:1234 uuid NX EX 30000一条命令完成:不存在才设置(NX),同时设置过期时间 EX,value 存入全局唯一标识(UUID),标记锁归属者。</li><li><b>过期时间作用:防止死锁</b>。如果拿到锁的服务直接宕机,没有执行释放锁,锁到期自动失效,其他服务可以继续获取锁。</li><li><b>释放锁大坑:禁止直接 DEL 删除 key</b>。业务执行时间超过锁过期,锁自动释放,其他服务拿到同一把锁;此时旧任务执行完毕直接删除 key,会误删掉别人正在持有的锁。<br></li></ul><blockquote>正确做法:释放锁执行 Lua 脚本,先比对锁内的唯一标识,确认是自己持有的锁,才执行删除操作腾讯云。<br></blockquote><ul><li><b>长业务任务:锁续约(看门狗 WatchDog)</b>。如果业务耗时不确定,锁过期时间很难预估。Redisson 等成熟框架提供看门狗机制,后台线程定时续期锁过期时间;业务正常结束就停止续期;服务宕机锁随 TTL 自动释放,解决业务没跑完锁先过期的问题。<br></li></ul><b>Redis 锁短板</b>:Redis 主从异步复制,主节点加锁成功还未同步到从节点就宕机,主从切换后会出现锁丢失,存在极小概率并发风险;金融级强一致性业务不能直接依赖简单 Redis 锁。<br><font size="4">2. 数据库实现分布式锁</font><br><br>两种常见思路:<br><ul type="1" class="litype_1"><li><b>锁表唯一索引</b>:新建一张锁表,资源 ID 作为唯一键;成功插入记录代表拿到锁;业务完成删除记录释放锁。插入失败代表锁被占用。</li><li><b>数据库行锁悲观锁SELECT ... FOR UPDATE</b>,直接利用数据库事务互斥。<br></li></ul>优点:实现简单,依托数据库本身事务约束;缺点:并发性能弱,数据库会成为瓶颈;宕机死锁需要额外清理逻辑;适合并发压力不大的小型系统。<br><font size="4">3. ZooKeeper /etcd 分布式锁(强一致性方案)</font><br><br>依靠分布式共识协议,ZooKeeper 使用临时有序节点;etcd 依靠 lease 租约 + 事务版本号。<br><ul><li>客户端创建临时节点,会话断开节点自动消失,天然避免死锁;依靠节点序号判断谁获得锁,支持 watch 监听等待。优点:一致性保障更强,锁丢失风险极低;适合集群选主、任务调度这类对可靠性要求高的场景。缺点:运维成本更高,写入性能弱于 Redis,组件重,小业务会过度设计。<br></li></ul><br><table class="t_table"><tbody><tr><td>实现方案</td><td>性能</td><td>一致性</td><td>运维成本</td><td>适用场景</td></tr><tr><td>Redis 锁</td><td>高</td><td>最终一致,存在极小风险</td><td>中等</td><td>高并发普通业务,互联网业务系统</td></tr><tr><td>数据库锁</td><td>低</td><td>强一致</td><td>低</td><td>低并发简单业务</td></tr><tr><td>ZooKeeper /etcd 锁</td><td>中等</td><td>强一致</td><td>高</td><td>选主、任务调度、高可靠要求场景</td></tr></tbody></table><font size="5">三、分布式锁≠万能,什么场景不应该上来就加锁</font><br><br>很多开发者遇到并发问题第一反应就是加分布式锁,但锁会降低吞吐,增加系统复杂度,很多场景有更优解法:<br><ul type="1" class="litype_1"><li><b>能用数据库原子更新就不加锁</b>:库存扣减直接执行update stock set count=count‑1 where id=xx and count&gt;0,数据库原子条件更新,不需要额外引入锁组件。</li><li><b>可以做幂等处理优先做幂等</b>:支付回调、消息重复消费,设计业务幂等键,重复执行不会产生副作用。</li><li><b>可以业务分片拆分资源,分散竞争,减少锁冲突</b>。<br></li></ul><blockquote>区分两个重要概念:<br><ul><li><b>分布式锁:过程控制</b>,禁止同一时刻多个实例同时执行业务;</li><li><b>幂等性:结果兜底</b>,即使重复执行,最终业务结果不会出错。<br></li></ul></blockquote><b>生产最佳实践:多层防护</b>分布式锁用来减少并发冲突;幂等校验处理重复请求;数据库唯一约束、条件更新作为最后的兜底。三层结合,不能单独把分布式锁当成全部保障。<br><font size="5">四、生产环境常见坑点总结</font><br><br><ul type="1" class="litype_1"><li>❌加锁分成两条命令:先 SETNX,再 EXPIRE,中间进程宕机会产生永久死锁;✅必须一条原子命令完成加锁 + 过期时间。</li><li>❌释放锁直接执行 DEL,出现误删别人锁;✅Lua 脚本校验锁持有者唯一标识,确认归属才删除。</li><li>❌锁过期时间写死,业务执行时长不可控,业务还没做完锁过期;✅长任务使用看门狗自动续期,合理评估业务耗时。</li><li>❌过度使用分布式锁,简单业务也强行引入,系统复杂度飙升;✅优先原子更新、幂等、分片,锁是最后的手段。</li><li>❌把 Redis 锁用于绝对零风险金融核心业务,忽略主从复制带来的锁丢失风险;✅强一致性场景选用 ZooKeeper/etcd 方案。<br></li></ul><font size="5">五、全文总结</font><br><br>分布式锁解决<b>多机器多实例之间共享资源的互斥访问</b>,保证同一时刻只有一个业务执行者操作关键数据,常见用于库存、订单支付回调、集群定时任务。<br>主流实现:Redis 高性能适合互联网高并发业务;数据库适合低并发简单场景;ZooKeeper、etcd 提供更强一致性,但运维成本更高。分布式锁不能包治百病,它属于过程防护。线上系统建议:<b>分布式锁减少冲突 + 幂等处理做重复防护 + 数据库约束兜底</b>的多层架构。能不用锁就尽量不用锁,优先选择原子更新、幂等、业务分片等更轻量的方案。<br><br>
页: [1]
查看完整版本: 什么是分布式锁:多服务集群下,为什么要抢同一把 “锁”