我用 Redis 做分布式锁防止任务被重复处理,结果先是一个实例崩了导致所有任务卡死,后来又出现同一个任务被两个实例同时处理,我对着分布式锁这几个致命实现细节排查了大半天的复盘

一个让我对分布式锁看着简单实则处处是坑彻底敬畏的架构坑,它教会我用 Redis 加把锁这件听起来一行代码搞定的事要真正正确实现需要考虑一连串容易被忽略的细节,漏掉任何一个锁就会在某种场景失效——要么死锁卡死所有人要么形同虚设根本没锁住。多实例下要保证同一时刻只有一个实例处理某任务,我用 Redis 分布式锁逐步踩坑:版本1 SETNX+DEL 没过期时间,持锁实例崩了锁永不释放全卡死(死锁);版本2 SETNX+EXPIRE 两步非原子,SETNX 后崩在 EXPIRE 前又成无过期死锁;版本3 SET NX EX 原子加锁但删锁不校验,业务超过30秒锁自动释放、实例B抢到锁、实例A执行完 DEL 把B的锁删了导致同一任务多实例并发。深究才明白正确的分布式锁要同时满足:加锁原子(SET NX EX 一条命令)、有过期时间(防持锁者崩溃死锁)、锁带唯一标识、删锁前校验是不是自己的且原子(Lua 防误删)、业务超时要看门狗续期,缺一条就在对应场景失效。这篇从故障现场逐步暴露、正确分布式锁的必要条件、正解(SET NX EX 原子加锁唯一value+Lua 校验删锁+看门狗续期,强烈推荐用 Redisson/etcd/zk 成熟库,先评估能否用唯一约束/乐观锁/分片/队列替代)、分布式协调其他坑(滥用锁、锁粒度、加锁顺序死锁、忘释放、过期时间、底层不可靠)、各实现版本缺陷对照表、分布式锁也只是尽力而为非绝对可靠(主从切换/GC暂停/时钟/分区)、决策图与铁律,到附上一个含校验删与看门狗续期的相对完整实现(但仍推荐用 Redisson)。核心领悟:看似简单的功能其正确实现可能依赖一连串环环相扣缺一不可的条件,成熟库的价值在于凝结了无数人踩过的坑补齐了你想不到的边界,对正确性依赖微妙条件的基础设施优先用成熟库别造轮子;分布式系统不存在绝对可靠,不要把正确性单点押在某机制上要分层设防用确定性机制(幂等/唯一约束)兜底;单机里简单到理所当然的概念到分布式会变难因为缺失了共享内存可靠执行确定性全局时钟等隐性保证,移植到分布式本质是用复杂机制重建缺失的保证。

我用 Redis 做分布式锁防止任务被重复处理,结果先是一个实例崩了导致所有任务卡死,后来又出现同一个任务被两个实例同时处理,我对着分布式锁这几个致命实现细节排查了大半天的复盘

这是一个让我对"分布式锁看着简单、实则处处是坑"彻底敬畏的架构坑。它教会我:"Redis 加把锁"这件听起来一行代码就能搞定的事,要真正正确地实现,需要考虑一连串容易被忽略的细节;漏掉任何一个,锁就会在某种场景下失效——要么死锁卡死所有人,要么形同虚设、根本没锁住

需求很常见:有一个任务,在多实例部署下,要保证"同一时刻只有一个实例在处理它"。我用 Redis 做分布式锁,写了个看起来没问题的实现:

分布式锁的几个"坑"版本(逐步暴露问题)

# 版本1: 最朴素 —— SETNX 加锁, 处理完 DEL 释放
#   SETNX lock 1     # 如果key不存在则设置(抢到锁), 否则失败(没抢到)
#   ...处理任务...
#   DEL lock         # 释放锁
# ★ 坑1: 没有过期时间! 如果持锁的实例【处理到一半崩溃了】(没执行到DEL),
#   这个lock就【永远存在】、永不释放 → 其他所有实例都抢不到锁、全部卡死 → 死锁!

# 版本2: 加过期时间 —— 但分两步(非原子)
#   SETNX lock 1
#   EXPIRE lock 30   # 单独设过期
# ★ 坑2: SETNX和EXPIRE是【两条命令、非原子】! 如果SETNX成功后、EXPIRE执行前
#   实例崩了, 锁又变成"没过期时间"的死锁!

# 版本3: 原子加锁+过期, 但删锁不校验
#   SET lock 1 NX EX 30    # 原子: 不存在才设、并设30秒过期(解决坑1、2)
#   ...处理任务(假设耗时较长)...
#   DEL lock
# ★ 坑3: 业务执行【超过了30秒】! 锁到期【自动释放】, 这时【实例B抢到了锁】开始处理;
#   而实例A的业务终于执行完, 执行 DEL lock —— 把【实例B的锁】给删了!
#   → 实例B还在处理, 锁却被A删了 → 实例C又能抢到锁 → 同一任务多个实例并发处理!

# 现象:
#   - 版本1: 某实例崩溃后, 所有任务卡死(死锁)
#   - 版本3: 业务偶尔超时, 出现同一任务被多个实例并发处理(锁形同虚设)

我先是遇到了"一个实例崩了,所有任务卡死"(版本 1 的死锁),加了过期时间;后来又遇到了"同一个任务被两个实例同时处理"(版本 3 的锁误删 + 锁提前过期)。我盯着这一连串问题意识到:分布式锁根本不是"SETNX 一下"这么简单——它要正确工作,至少要同时满足"加锁原子(锁和过期一起设)、有过期时间(防死锁)、删锁要校验是不是自己的(防误删)、业务超时要能续期"这一系列条件,缺一不可。我那几个版本,每个都漏掉了其中一两个,于是在不同的场景下,锁就以不同的方式失效了。

第一件事:看清真相——分布式锁要同时满足好几个条件才正确

我去系统地梳理了"一个正确的分布式锁需要满足哪些条件",才彻底明白前面那些版本为什么会失效——分布式锁不是单一的操作,而是一组必须协同满足的约束:加锁必须原子(锁标志和过期时间一次性设上)、锁必须有过期时间(防止持锁者崩溃后死锁)、锁要有唯一标识、删锁前必须校验"这把锁是不是我加的"(防止误删别人的锁)、业务可能超过锁过期时间所以要能续期;漏掉任何一条,就会在对应的场景下失效

正确分布式锁的必要条件

# 一个正确的分布式锁, 必须同时满足:

# 1. 【加锁原子】: "抢锁"和"设过期时间"必须是【一个原子操作】。
#    - SET lock <唯一值> NX EX 30  → 一条命令完成"不存在才设+设过期", 原子。
#    - 不能用 SETNX + EXPIRE 两步(中间崩溃会留下无过期的死锁)。

# 2. 【有过期时间】: 锁必须设过期时间(TTL)。
#    - 防止: 持锁的实例崩溃/卡死(没机会主动释放), 导致锁永不释放、死锁。
#    - 过期时间是"兜底": 即使持锁者挂了, 锁也会自动过期释放, 别人能继续。

# 3. 【锁有唯一标识】: 锁的value要是【当前持有者的唯一标识】(如UUID), 不是固定的"1"。
#    - 用于后面"删锁前校验是不是自己的锁"。

# 4. 【删锁要校验+原子】: 释放锁时, 必须先确认"这把锁的value是不是我的标识",
#    是才删, 否则不能删(那是别人的锁)。而且"校验+删除"也要【原子】(用Lua脚本)。
#    - 防止坑3: 我的锁早过期了、别人拿了新锁, 我却把别人的锁删了。

# 5. 【业务超时要续期】: 如果业务可能执行超过锁的过期时间,
#    - 要有"看门狗(watchdog)"机制: 在持锁期间, 后台定期给锁续期(延长TTL),
#      只要业务没结束就一直续, 业务结束才停止续期并释放。
#    - 防止: 业务还没执行完, 锁就自动过期了, 别人趁机拿到锁 → 并发。

# 6. (高可用)考虑Redis本身的可用性: 单点Redis挂了锁就没了;
#    主从切换可能丢锁(主挂了从还没同步到锁)。→ 高要求用RedLock或更强一致的方案。

# 核心: 正确的分布式锁要同时满足: 加锁原子(SET NX EX)、有过期时间(防死锁)、锁带唯一标识、
#   删锁校验是自己的且原子(Lua, 防误删)、业务超时能续期(watchdog); 缺一条就在对应场景失效。

真相大白,我恍然大悟。原来一个正确的分布式锁,不是单一操作,而是一组必须协同满足的约束:一、加锁原子——"抢锁"和"设过期"必须一条命令完成(SET lock 唯一值 NX EX 30),不能 SETNX+EXPIRE 两步(中间崩溃留下无过期的死锁);二、有过期时间——防止持锁实例崩溃后锁永不释放、死锁;过期是兜底,持锁者挂了锁也会自动释放;三、锁有唯一标识——value 是当前持有者的唯一标识(UUID)而非固定的"1";四、删锁要校验+原子——释放前先确认"这把锁的 value 是不是我的",是才删(用 Lua 脚本保证校验+删除原子),防止"我的锁早过期、别人拿了新锁,我却把别人的锁删了";五、业务超时要续期——用"看门狗(watchdog)"在持锁期间后台定期续期,防止业务没执行完锁就自动过期、别人趁机拿锁导致并发;六、考虑 Redis 本身的可用性(单点/主从切换可能丢锁,高要求用 RedLock)。缺任何一条,就会在对应的场景下失效——我那几个版本,正是各漏了其中一两条。

第二件事:正解——原子加锁带唯一值、Lua 校验删锁、看门狗续期,或直接用 Redisson

搞懂了原理,正解就清晰了:用 SET NX EX 原子加锁(value 用唯一标识)、用 Lua 脚本校验后再删锁、用看门狗续期;或者干脆用 Redisson 等成熟实现,别自己造轮子

分布式锁的正解

# ====== 正解一: 自己实现的关键三步(以Redis命令/伪代码示意) ======

# 1. 加锁: 原子地 不存在才设 + 设过期 + value用唯一标识
token = uuid()                          # 当前持有者的唯一标识
SET lock_key token NX EX 30             # 一条命令: NX(不存在才设) EX(30秒过期)
# 成功 = 抢到锁; 失败 = 没抢到(别人持有)

# 2. 释放锁: 用 Lua 脚本【原子地】"校验是我的才删"
#    (绝不能 GET判断 + DEL 两步, 那中间锁可能过期被别人拿走)
#    Lua逻辑: if GET(KEYS[1]) == ARGV[1] then DEL(KEYS[1]) else 0 end
#    即: 锁的value等于我的token时, 才删除它(原子执行校验+删除)
#    调用: EVAL <上面的Lua> 1 lock_key token

# 3. 续期(看门狗): 持锁期间, 后台线程每隔(过期时间/3)给锁续期
#    只要业务没结束就续(同样用Lua校验是自己的锁才EXPIRE续期), 业务结束停止续期。
#    Lua逻辑: if GET(KEYS[1]) == ARGV[1] then EXPIRE(KEYS[1], 30) else 0 end

# ====== 正解二(强烈推荐): 用成熟的分布式锁库 ======
# 别自己造轮子! 上面这些细节(原子加锁、唯一标识、Lua删锁、看门狗续期)
# 成熟的库都帮你处理好了:
#   - Java: Redisson(RLock, 自带看门狗自动续期、可重入等)
#   - 也可用 ZooKeeper/etcd 的分布式锁(基于一致性协议, 更强的可靠性)
# Redisson 用起来极简:
#   RLock lock = redisson.getLock("lock_key");
#   lock.lock();      // 自动: 原子加锁+看门狗续期
#   try { ...业务... } finally { lock.unlock(); }   // 自动: 校验+释放

# ====== 正解三: 评估是否真的需要分布式锁 ======
# 分布式锁有成本和复杂度。先想想能不能用更简单的方案:
#   - 数据库唯一约束/乐观锁(很多"防重复"场景用它就够了, 见幂等那篇)
#   - 把任务分片, 让每个实例只处理自己分片的(从源头避免争抢)
#   - 用消息队列让任务串行/单消费者处理
# → 能不用分布式锁就不用; 它是"不得不共享资源时"的手段。

# 核心: 分布式锁正解=SET NX EX原子加锁(唯一value)+Lua校验删锁+看门狗续期; 强烈推荐直接用
#   Redisson等成熟库别造轮子; 更要先评估能否用唯一约束/分片/队列等更简单方案替代分布式锁。

修复的核心,是"原子加锁带唯一值 + Lua 校验删锁 + 看门狗续期,或直接用 Redisson"正解一:自己实现的关键三步——加锁SET lock token NX EX 30(一条命令原子完成"不存在才设+设过期",value 用唯一标识 token);释放锁Lua 脚本原子地"校验 value 是我的才删"(绝不能 GET 判断+DEL 两步);续期用看门狗,持锁期间后台定期给锁续期(同样 Lua 校验是自己的锁才续)、业务结束停止。正解二(强烈推荐):用成熟库——别造轮子,Redisson 的 RLock 自带看门狗自动续期、可重入,lock.lock() + finally lock.unlock() 极简;或用 ZooKeeper/etcd 基于一致性协议的锁(更可靠)正解三:先评估是否真需要分布式锁——它有成本和复杂度,很多"防重复"场景用数据库唯一约束/乐观锁(见幂等篇)、任务分片、消息队列串行化就够了;能不用就不用。归根结底:分布式锁正解=SET NX EX 原子加锁(唯一 value)+Lua 校验删锁+看门狗续期;强烈推荐直接用 Redisson 等成熟库;更要先评估能否用唯一约束/分片/队列等更简单方案替代。

第三件事:分布式协调相关的其他常见坑

排查后我把分布式协调相关的其他常见坑也系统梳理了一遍。

分布式协调的其他常见坑

# 1. 分布式锁实现不全(本文): 缺过期/误删/不续期。→ 满足全部条件或用Redisson。

# 2. 滥用分布式锁: 能用唯一约束/乐观锁/分片解决的非要用锁, 增加复杂度和性能损耗。

# 3. 锁粒度太粗: 一把大锁锁住所有, 并发度低; 应按资源细粒度加锁。

# 4. 锁粒度太细/加锁顺序乱: 多把锁互相等待 → 死锁。→ 统一加锁顺序。

# 5. 忘了finally释放锁: 业务异常没释放锁(虽有过期兜底, 但浪费)。→ finally释放。

# 6. 过期时间设不合理: 太短(业务没做完就过期)、太长(崩溃后死锁久)。→ 配看门狗续期。

# 7. 依赖锁保证强一致, 但底层不可靠: 单点Redis/主从切换丢锁。→ 强一致用etcd/zk/RedLock。

# 8. 把分布式锁当万能: 它解决"互斥", 不解决一致性/事务等所有问题。

# 共同根源: 分布式环境下"协调多个节点"本身极其困难(没有共享内存、网络不可靠、节点会挂);
#   分布式锁只是其中一个工具, 它自己的正确性就依赖一连串不容易满足的条件。

# 核心: 分布式协调很难; 分布式锁要满足全部条件(原子/过期/校验/续期)或用成熟库; 先评估
#   能否用更简单方案(唯一约束/乐观锁/分片/队列); 注意锁粒度、释放、过期、底层可靠性。

排查让我把分布式协调的其他坑也梳理清了。一、分布式锁实现不全(本文)。二、滥用分布式锁(能用唯一约束/乐观锁/分片的非要用锁)。三、锁粒度太粗(并发度低)。四、锁粒度太细/加锁顺序乱(死锁,统一加锁顺序)。五、忘了 finally 释放锁六、过期时间设不合理(配看门狗)。七、依赖锁保证强一致但底层不可靠(单点/主从丢锁,强一致用 etcd/zk/RedLock)。八、把分布式锁当万能它们的共同根源是:分布式环境下"协调多个节点"本身极其困难(没有共享内存、网络不可靠、节点会挂);分布式锁只是其中一个工具,它自己的正确性就依赖一连串不容易满足的条件核心是:分布式锁要满足全部条件或用成熟库;先评估能否用更简单方案;注意锁粒度、释放、过期、底层可靠性下面这张图,是这次分布式锁失效的成因与解法:

第四件事:分布式锁各实现版本的缺陷对照表

这次踩坑后,我把分布式锁的几个实现版本和它们的缺陷整理成一张表。

实现 缺什么 会出什么问题
SETNX + DEL 没过期时间 持锁者崩溃→死锁, 全卡死
SETNX + EXPIRE(两步) 加锁非原子 中间崩溃→留无过期死锁
SET NX EX + DEL(不校验) 删锁不校验 误删别人的锁→并发
SET NX EX + Lua删(不续期) 不续期 业务超时锁过期→并发
原子加锁+Lua删+看门狗 —(单Redis) ✓ 基本正确(底层单点风险)
Redisson / etcd / zk锁 ✓ 推荐, 细节都处理好

这张表把分布式锁的"进化与缺陷"钉清了。核心是:分布式锁的正确实现,是一个"逐步补齐缺陷"的过程——每修复一个版本的问题(加过期、加原子、加校验、加续期),都会暴露出下一个被忽略的问题;只有同时满足所有条件(或用已经满足了的成熟库),才算真正正确它给我的最大启发是:有些"看似简单"的功能(分布式锁、缓存、重试),其"正确实现"其实需要满足一连串环环相扣、缺一不可的条件;而这些条件,往往是前人在一次次踩坑中逐步补齐的——你以为的"简单实现",可能只是覆盖了 happy path、漏掉了那一连串边界条件这其实揭示了"为什么要用成熟的库/方案"的深层原因:一个成熟的库(如 Redisson 的分布式锁),它的价值不在于"代码量",而在于它凝结了无数人踩过的坑、补齐了那一连串你想不到的边界条件;你自己"从零写一个",很可能重新踩一遍前人早已踩平的坑所以我学到:对于"正确性依赖一连串微妙条件"的基础设施(分布式锁、加密、并发原语、时间日期处理),优先用成熟的、被广泛验证的库,而不是自己造轮子;"不自己造这类轮子",不是因为懒,而是因为正确地造出来太难、且没必要重复前人的踩坑认清"简单功能的正确实现可能很难"、对依赖微妙条件的基础设施优先用成熟库——是这个分布式锁坑教给我的务实智慧。

第五件事:分布式锁也只是"尽力而为"

这次还让我认识到一个更冷峻的事实:即使实现"正确",分布式锁也不是绝对可靠的。

挑战 说明
Redis单点/主从切换 主挂了, 锁可能还没同步到从, 新主上锁"丢了"
GC停顿/进程暂停 持锁者长时间暂停(如Full GC), 锁过期被别人拿, 它醒来还以为持锁
时钟漂移 各节点时钟不一致, 影响过期判断
网络分区 分区下可能多个节点都以为自己持锁

这张表道出了分布式锁的"局限"。核心是:即使你"正确地"实现了分布式锁,它也不是绝对、100% 可靠的——在 Redis 主从切换、持锁者 GC 长暂停、时钟漂移、网络分区等极端情况下,仍可能出现"两个节点同时以为自己持有锁"的情况;基于 Redis 的分布式锁,本质是一种"尽力而为(best-effort)"的互斥,而非"绝对保证"。它给我的深刻启发是:在分布式系统里,几乎不存在"绝对可靠、100% 保证"的东西;我们能做的,是在"可靠性"和"成本/复杂度/性能"之间做权衡,选择一个"足够可靠"的方案;更稳健的设计,不是寄希望于"锁绝对不会失效",而是即使"万一锁失效、出现了并发",系统依然能保证最终正确(比如配合幂等、唯一约束做最后兜底)这其实呼应了贯穿分布式设计的一条主线:不要把系统的正确性,单点地押在某一个"我们假设它绝对可靠"的机制上(分布式锁、缓存一致性、消息不重复);而要分层设防、用多重保障——分布式锁提供"大概率的互斥",数据库唯一约束/幂等提供"万一锁失效时的最终正确性兜底";"用尽力而为的机制提升效率,用确定性的机制保证底线",是构建可靠分布式系统的成熟之道认清分布式锁也只是尽力而为、用幂等/唯一约束等确定性机制兜底正确性——是这个分布式锁坑,带给我的关于"分布式可靠性"的更深思考。

第六件事:需要互斥/防重复时,我现在的判断习惯

现在每当我遇到"多个实例要互斥访问某资源"的需求,我都会按这张图先想清楚:

这张图的精髓,是"先评估能否用更简单方案,确需分布式锁就用成熟库并配幂等兜底"防重复插入用唯一约束、防并发更新用乐观锁、可分片就分片、可串行用队列;确实需要互斥共享资源才上分布式锁,且尽量用 Redisson 等成熟实现、必须自己写就满足全部条件;关键是配幂等/唯一约束做兜底,万一锁失效也最终正确这套习惯,让我面对互斥需求时,从"上来就 SETNX 加锁"变成了"先想能不能不用锁、要用就用对并兜底"——核心始终是:分布式锁难且非绝对可靠,先评估更简单方案、用成熟库、配确定性机制兜底。

我立下的几条规矩

这场"分布式锁失效"的事故,换来了我做分布式系统时,刻进骨子里的几条铁律:

  1. 分布式锁要满足一连串条件,缺一不可。原子加锁+过期+唯一value+校验删+续期。
  2. 加锁要原子且带过期。SET NX EX 一条命令,防死锁。
  3. 删锁要 Lua 校验是自己的。防误删别人的锁。
  4. 业务可能超时要看门狗续期。防锁提前过期导致并发。
  5. 强烈推荐用 Redisson 等成熟库。别自己造这种难造对的轮子。
  6. 先评估能否用唯一约束/乐观锁/分片/队列。能不用锁就不用。
  7. 分布式锁非绝对可靠,配幂等兜底。万一锁失效也保证最终正确。

附:一个相对完整的 Redis 分布式锁实现(含校验删与续期)

这次踩坑后,我把一个"满足前面所有条件"的 Redis 分布式锁,沉淀成了一个相对完整的实现(Java 示意,生产建议直接用 Redisson):

public class RedisDistLock {
    private final StringRedisTemplate redis;
    private final String key;
    private final String token = UUID.randomUUID().toString();  // 唯一标识(条件3)
    private volatile boolean held = false;
    private ScheduledExecutorService watchdog;

    // 释放锁的Lua: 校验是自己的锁才删(条件4: 校验+删除原子)
    private static final String UNLOCK_LUA =
        "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    // 续期的Lua: 校验是自己的锁才续期
    private static final String RENEW_LUA =
        "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";

    public boolean tryLock(long ttlSeconds) {
        // 条件1+2: 原子地 不存在才设 + 设过期 + value用唯一token
        Boolean ok = redis.opsForValue()
            .setIfAbsent(key, token, Duration.ofSeconds(ttlSeconds));
        if (Boolean.TRUE.equals(ok)) {
            held = true;
            startWatchdog(ttlSeconds);   // 条件5: 启动看门狗续期
            return true;
        }
        return false;
    }

    private void startWatchdog(long ttl) {
        watchdog = Executors.newSingleThreadScheduledExecutor();
        // 每隔 ttl/3 续期一次, 只要还持锁就把TTL续回ttl
        watchdog.scheduleAtFixedRate(() -> {
            if (held) {
                redis.execute(new DefaultRedisScript<>(RENEW_LUA, Long.class),
                    List.of(key), token, String.valueOf(ttl));
            }
        }, ttl / 3, ttl / 3, TimeUnit.SECONDS);
    }

    public void unlock() {
        held = false;
        if (watchdog != null) watchdog.shutdownNow();   // 停止续期
        // 条件4: 用Lua校验是自己的锁才删, 防误删别人的
        redis.execute(new DefaultRedisScript<>(UNLOCK_LUA, Long.class),
            List.of(key), token);
    }
}
// 用: lock.tryLock(30); try { ...业务... } finally { lock.unlock(); }
// → 但生产环境: 强烈建议直接用 Redisson 的 RLock, 它把这些和更多边界都处理得更完善!

// 核心: 一个"基本正确"的分布式锁要把 原子加锁(setIfAbsent带TTL)+唯一token+Lua校验删+看门狗续期
//   全实现到位; 即便如此, 生产仍推荐用Redisson等经千锤百炼的成熟库, 别依赖自己的实现。

这个实现,是我这次踩坑后把"所有必要条件"落到代码里的一次沉淀。它把前面讲的五个条件——原子加锁(setIfAbsent 带 TTL)、唯一标识(token)、Lua 校验删锁、看门狗续期——全部实现到位,比我最初那个"SETNX 一下"的版本,代码量多了好几倍。而这恰恰是我想强调的重点:看,一个"基本正确"的分布式锁,要写这么多、考虑这么多边界——这正说明了"加把锁"这件事在分布式下有多不简单;而即便我写了这么多,我在文末依然强烈建议:生产环境直接用 Redisson为什么写了完整实现还推荐用库?因为我这个"看起来完整"的实现,其实仍然有我没考虑到的边界(可重入、锁等待与公平性、Redis 集群下的可靠性、各种异常处理……);而 Redisson 这样的成熟库,是经过无数生产环境千锤百炼、把这些我想都想不到的边界都处理过了这正是我想用这个"自己写的完整版 + 仍推荐用库"的对比,传达的核心思想:对于"正确性极其依赖周全的边界处理"的基础设施,"我能自己写出一个看起来对的"和"它在所有生产场景下都真的对",中间隔着巨大的鸿沟;而填平这个鸿沟,靠的是海量真实场景的检验和打磨——这正是成熟库相比"自己手写"的核心价值;承认"我自己写的,大概率没有久经考验的库周全",并在这类基础设施上谦逊地选择成熟方案,是一种工程上的成熟把完整实现写出来体会其复杂、再谦逊地选择更周全的成熟库——这,是我用一次分布式锁的事故,换来的、关于"自己造轮子 vs 用成熟方案"的清醒判断。

写在最后

回头看,这场由"分布式锁实现不全"引发的、一连串失效的事故,真正教给我的,远不止"用 SET NX EX 和 Lua 删锁"这些技巧。它让我对"简单的表象下,藏着多少必须满足的隐性约束",以及"分布式的根本困难",有了一次深刻的体会。我栽跟头,是因为我被"加把锁"这个概念的简单表象骗了。在单机里,"加锁"确实简单——一个 synchronizedmutex 就搞定,因为单机里有可靠的共享内存、确定的执行、不会'半路消失'的线程作为坚实的地基。可我把这个单机里的简单概念,想当然地搬到了分布式环境,却没意识到:分布式环境没有那些坚实的地基——没有共享内存(锁要放在外部的 Redis)、节点会突然崩溃(持锁者可能半路消失)、网络不可靠(命令可能丢)、没有全局时钟。在这片"流沙"上实现一把可靠的锁,自然需要一连串额外的、精巧的约束来弥补地基的缺失。这让我领悟到一个深刻的认知:很多在单机/理想环境下"简单到理所当然"的概念(锁、事务、一次调用、一致的数据),一旦放到分布式环境,就会变得异常困难——因为分布式环境缺失了单机环境里那些我们习以为常、却至关重要的"保证"(共享内存、可靠性、确定性、全局时钟);而把单机的简单概念"移植"到分布式,本质上是要在一个缺失了诸多保证的环境里,用复杂的机制去重建那些缺失的保证——这,正是分布式系统之所以困难的根源。这其实是分布式系统设计的一条核心心法:对任何要从单机搬到分布式的概念/能力,都要清醒地问:"它在单机里依赖了哪些'隐性的保证'(共享内存、可靠执行……)?在分布式里这些保证还在吗?如果不在,我要用什么机制去弥补?";不要被概念的"单机版简单"所迷惑,而要正视分布式环境的"地基缺失",并为弥补它付出必要的、精巧的代价认清"单机的简单概念到分布式会变难"的根源、正视分布式缺失的保证并用机制去弥补——这,是我用一次分布式锁失效的事故,换来的、关于架构、关于分布式系统本质困难的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次想"加个分布式锁"时,先肃然起敬地想一想"它真正需要满足哪些条件、能不能用更简单的方案、万一失效怎么兜底",那我对着那一连串锁失效的故障排查的这大半天,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我训练的模型效果差得离谱,梯度下降还死活不收敛,排查发现是特征没做缩放、量纲大的收入完全主导了模型、年龄几乎没起作用,我对着特征量纲不一致这个坑排查了大半天的复盘

2026-6-2 12:47:04

技术教程

我的服务运行越久内存涨得越凶,最后内存泄漏到崩溃,排查发现是一堆本该被回收的对象因为订阅了事件却没取消订阅、被发布者死死攥着无法回收,我对着 C# 事件订阅导致的内存泄漏这个坑排查大半天的复盘

2026-6-2 13:01:05

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索