本文目录
可直接引用的结论:锁服务说“获得锁”还不够,受保护资源必须能拒绝过期持有者
本文面向负责库存扣减、批处理、设备控制、文件生成或 AI 智能体工具执行的架构、研发与运维人员。分布式锁应先明确资源标识、互斥范围和允许的失败结果,再使用唯一持有者标识、有限租约、条件续期与条件释放。进程暂停、网络分区或依赖超时可能让持有者在租约失效后继续执行,因此对不能接受并发写入的关键资源,应由协调系统发放单调递增的栅栏令牌,并由数据库、存储或下游服务原子拒绝比已接受令牌更旧的操作。若目标只是防止重复业务结果,数据库条件更新、唯一约束、幂等键或队列单分区往往更直接。验收不能只测抢锁成功率,还要主动制造超时、暂停、失联、重启和热点争用,并核对最终业务状态。
先按业务后果选择并发控制方式
| 控制方式 | 适用条件 | 容易忽略的风险 |
|---|---|---|
| 数据库条件更新或唯一约束 | 竞争对象就在同一权威数据库,规则能写成版本、状态或唯一键条件 | 只在应用先查询再更新,检查与写入之间仍有竞争窗口 |
| 幂等键与结果复用 | 需要阻止同一业务意图重复生效,但不同请求可以并行处理其他对象 | 把请求随机 ID 当业务意图,重试时换 ID 仍会重复执行 |
| 有租约的分布式锁 | 多个实例必须协调一段有限工作,并且能明确锁服务故障和超时后的处置 | 进程暂停超过租约后仍继续写,旧持有者与新持有者重叠 |
| 锁加栅栏令牌 | 旧操作晚到会破坏数据库、文件、设备或外部系统,下游能够原子比较令牌 | 只生成令牌但下游不校验,令牌只是日志字段,不能阻止陈旧写入 |
适用边界:先说清锁到底保护什么
这份指南适合多实例定时任务、同一业务对象串行处理、库存补偿、报表或文件生成、设备指令和智能体工具任务。它不把分布式锁当成跨数据库、消息和外部接口的通用事务,也不假设“同时只有一个进程进入代码段”就等于业务只生效一次。
锁键必须对应稳定的业务资源,例如租户与任务、订单与操作类型、设备与指令通道,而不是模糊的全局字符串。锁得太粗会把无关工作排队,锁得太细又可能遗漏共同约束;先写出哪些操作必须互斥、互斥持续到哪个权威状态提交、失败后谁可以接手。若规则能在同一个数据库事务里用条件更新、版本号或唯一约束表达,应优先让权威存储裁决,减少额外协调组件。
互斥、幂等和顺序是三个不同目标。互斥限制同一时刻的执行者,幂等使同一业务意图重复到达仍得到同一结果,顺序则决定哪个版本可以覆盖哪个版本。支付、审批、消息消费与智能体工具调用通常还需要业务幂等和结果查询,不能因为入口拿过锁就省略这些设计。
- 为每一种锁记录资源键、保护的业务不变量、持有阶段和最长合理工作时间。
- 明确重复执行、并发执行、乱序到达各自允许还是必须拒绝。
- 先评估数据库约束、条件更新、任务分区或单线程执行是否已经足够。
持有者标识必须贯穿获取、续期与释放
锁记录不能只表示“有人占用”,还要携带每次获取都不同的持有者标识。释放和续期只能在当前值仍等于该标识时完成,避免旧请求删除或延长后来者已经取得的锁。
Redis 官方分布式锁资料以带过期时间的条件写入说明单实例基础做法,并强调释放时必须比较唯一值;若客户端执行时间超过有效期后直接 DEL,可能删除另一个客户端后来创建的锁。工程实现应使用所选服务支持的原子条件操作,不能把先 GET、再判断、最后 DEL 拆成多个可被并发穿插的请求。库版本和命令能力不同,发布前需要核对实际客户端是否真的执行了条件释放。
持有者标识用于证明“这是同一次锁会话”,不等于可排序的栅栏令牌,也不应复用业务幂等键。每次重新获取都产生新身份;续期记录本次尝试、结果与剩余有效期;释放失败要作为可观察结果处理,而不是假定锁已清除。业务日志关联锁键、持有者、任务或请求标识,但不要记录认证凭据。
- 获取、续期、释放都采用不可拆分的条件操作。
- 重新获取时生成新的锁会话身份,不复用上一次随机值。
- 记录获取耗时、剩余有效期、续期失败和非持有者释放尝试。
租约与续期只能限制占用时间,不能暂停旧进程
租约为锁设置有限有效期,使进程崩溃后资源最终可以再次被获取。它同时规定了持有者可以安全完成工作的时间窗;续期只是延长这个时间窗,不会撤回已经发给旧进程的 CPU、数据库连接或外部请求。
客户端可能因为垃圾回收暂停、容器冻结、宿主机抖动、网络分区或下游长尾延迟错过续期。此时锁服务可以把租约判为过期并允许新持有者进入,而旧进程恢复后仍可能继续执行。etcd 官方资料明确指出,服务器与客户端分别感知物理时间时,服务端可能已经撤销租约,而客户端仍认为自己持有它;因此只检查本地续期线程是否存活不能证明排他性。
租约时长应大于已测得的正常工作与续期抖动,并设置明确的截止时间;任务超过截止点时停止开启新步骤,取消可取消的调用,并在最终写入前重新验证资格。自动续期要有最大任务时长或次数边界,避免卡住的任务无限占锁。宁可把长任务拆成可提交、可接管的小阶段,也不要依赖一个覆盖全流程的超长 TTL。
- 续期间隔、请求超时、最大租约和任务截止时间分别配置并监控。
- 续期结果未知或确认失去租约后,停止新的副作用并进入对账。
- 模拟进程暂停超过租约,而不是只模拟正常退出。
用栅栏令牌让权威资源拒绝陈旧写入
对不能容忍旧持有者晚到的资源,每次成功取得锁时发放单调递增的令牌。写入方把令牌随操作提交,资源端只接受不小于当前已记录版本的请求,并在同一原子操作中更新业务状态和已接受令牌。
假设执行者 A 获得令牌 41 后暂停,租约过期;执行者 B 获得 42 并完成写入。A 恢复后即使仍握有连接,只要数据库或下游比较令牌,就会拒绝 41,避免旧结果覆盖新结果。etcd 官方说明其全局 revision 顺序化键空间更新,并指出保护 etcd 外部资源时,外部资源自身必须提供版本校验和一致性;因此不能把协调服务中的 revision 简单写进日志便宣称已经 fencing。
令牌比较的位置必须是最终权威边界:数据库可用带版本条件的 UPDATE,内部服务可在事务中保存最后接受令牌,对象或设备网关则需要自身支持条件版本。若外部供应商接口无法比较令牌,就不能声称已彻底阻止陈旧副作用;此时缩短任务、使用业务幂等、先写本地意图再由单一执行通道发送,并把剩余风险写进方案。令牌溢出、重建与跨环境切换也要有明确规则。
- 令牌由具备一致顺序的权威组件产生,不能使用各应用节点本地时间。
- 最终资源在原子写入中比较并保存令牌,旧令牌返回可识别拒绝。
- 逐项标注数据库、对象存储和第三方接口能否实施版本校验。
热点争用要控制等待、重试与公平性
分布式锁本身可能成为热点。大量实例同时轮询一个锁,会放大协调服务负载;持有者失败后所有等待者同时重试,还会把数据库和外部接口推入下一轮拥塞。获取锁必须有截止时间、退避与抖动,并区分未获得、超时结果未知和业务执行失败。
等待者不应无限占用请求线程或连接。在线接口可以快速返回“处理中”并通过任务状态查询结果,后台任务可以按业务键分区或进入有界队列,让同一资源顺序消费。公平排队是否存在取决于具体实现,不能默认先请求者一定先获得;有强顺序要求时,应使用带序号的任务记录或消息分区表达,而不是从锁竞争时序推断业务顺序。
锁粒度调整必须以业务不变量为准。拆分热点键可以提高吞吐,但若多个子键仍共同修改同一总额度、文件或设备状态,就会破坏保护目标。容量测试同时记录成功获取、等待时间、放弃、续期、过期、栅栏拒绝和实际业务完成量;只看协调服务 QPS 会遗漏受保护资源已拥塞的事实。
- 获取失败后使用有上限的退避与抖动,不做无间隔轮询。
- 为锁等待设置业务可接受截止时间,并提供处理中或稍后重试结果。
- 热点锁同时评估排队替代、按键分区和缩短临界区。
实施步骤:从业务不变量反推锁协议
先选一个会真实产生冲突的资源,写出允许状态转换与最终权威存储;再实现无锁时的数据库条件保护和幂等;确有跨实例协调需要时,引入锁键、持有者身份与租约;对关键写入补充栅栏令牌;最后接入指标、告警和人工处置。
临界区只保留必须串行的读取、计算和提交,网络调用尽量移出或改为可恢复阶段。锁获取成功后保存服务端确认的身份与令牌,任何续期失败都不能静默忽略。业务提交要携带幂等键和栅栏令牌,返回“令牌过旧”时不得换新令牌偷偷重放旧计算,而应重新读取权威状态并决定放弃还是重新计算。
发布采用单一资源或少量租户灰度,保留关闭协调优化、暂停新任务和回到数据库条件路径的开关。版本混跑时确认旧实例是否会绕过令牌校验;只要存在一个不检查令牌的写入口,整体保护就仍有缺口。变更记录包含锁协议版本、客户端库版本、超时配置和下游校验覆盖范围。
- 先固定状态机、幂等键和权威写入条件,再接入分布式锁。
- 锁客户端、业务写入和下游资源共同传播同一追踪标识与栅栏令牌。
- 灰度期间阻止旧版本绕过校验,并准备暂停任务和重新对账。
验收与风险:必须证明旧持有者确实写不进去
验收至少覆盖同时抢锁、持有者崩溃、进程暂停超过租约、续期请求丢失、锁服务重启或不可达、业务写入超时结果未知、新持有者接管、旧请求延迟到达和热点争用。每个场景都对账锁事件、令牌顺序、业务状态与外部副作用。
最关键的故障实验是让 A 取得锁并暂停到过期,让 B 取得更高令牌并完成,再恢复 A。期望不是 A 自己识别过期,而是权威资源拒绝 A 的旧令牌,最终数据保持 B 或后续合法版本。随后验证重复释放不会删除 B 的锁,重复业务请求返回既有结果,过期任务不会通过另一条未加校验的接口落库。
主要风险包括把缓存故障转成全站停顿、租约设置过短造成频繁重叠、自动续期掩盖永久卡死、只做条件释放却没有防旧写、令牌由本地时间生成、下游比较与写入不原子、任务失败后无限抢锁,以及多区域或灾备切换重新从小令牌开始。无法验证的第三方副作用必须标为剩余风险,不能用“理论上只有一个执行者”代替证据。
- 故障测试能复现旧持有者晚到,并看到权威资源明确拒绝。
- 最终业务记录、消息、文件或设备状态与最高已接受令牌一致。
- 报告列出锁服务不可用时的降级、停止条件、恢复与人工对账步骤。
需求讨论中的问题
Redis 设置 SET NX EX,就一定不会同时有两个执行者吗?
不能这样承诺。有限 TTL 只在有效窗口内约束持有者;进程暂停或工作超时后,旧执行者可能与新持有者重叠。至少要使用唯一身份进行条件释放,并按业务风险决定是否由权威资源校验栅栏令牌。
有自动续期的看门狗,还需要栅栏令牌吗?
如果旧写入会造成不可接受的状态破坏,仍应需要下游拒绝旧资格。续期可能因暂停、分区或协调服务故障而失败,看门狗也不能撤回已经发出的请求;栅栏校验提供最终资源边界的保护。
栅栏令牌能用时间戳或随机 UUID 吗?
随机 UUID 只能区分会话,不能判断新旧;不同机器的时间也不适合作为严格顺序。令牌应由具备一致顺序的权威组件产生,并由资源端按单调次序比较。
定时任务已经限制只运行一个 Pod,还要做业务幂等吗?
要按任务后果判断。调度、选主、发布切换和故障恢复都可能产生重复或结果未知;涉及发送、扣减、生成文件或调用智能体工具时,仍应使用稳定业务标识、状态条件与结果对账。
小程序或 APP 连点提交,可以靠分布式锁解决吗?
通常先用客户端防重复体验、服务端业务幂等键和数据库唯一或状态约束。分布式锁只在多个实例确实需要协调同一资源时加入,不能替代弱网重试后的结果查询与订单状态设计。
分布式锁上线验收清单
可逐项勾选,辅助本次阅读;刷新页面后不保留。
参考资料
技术概念可进一步阅读以下资料;文中的业务场景与实施清单用于方案讨论。
- Redis Documentation · Distributed Locks with Redis
核验带有效期的条件获取、唯一随机值、条件释放、有效窗口、有限续期与 fencing tokens 风险提示;具体算法选择需按业务一致性要求评审。
- etcd Documentation · Notes on the usage of lock and lease
核验物理时间租约可能出现服务端已撤销而客户端仍自认持有,以及保护外部资源必须由该资源提供版本校验和一致性。
- etcd Documentation · API design and revisions
核验 revision 作为集群范围逻辑时钟顺序化键空间更新,以及事务比较版本或 revision 的原子条件能力。
- Kubernetes Documentation · Leases
核验 Lease 对象用于节点心跳和组件选主的官方场景;本文不把 Kubernetes Lease 自动等同于外部业务资源的 fencing。
需要评估现有系统的性能瓶颈?
提供关键业务路径、真实峰值、服务器与数据库配置,以及现有压测或故障记录,月弦科技可据此梳理优化顺序、实施范围和可验证的验收条件。