资讯动态

Redis分布式锁原理与生产级实现:从超卖事故到Lua原子操作

发布时间:2026/10/22 4:30:22 来源:尧图企业网站定制
1. 为什么分布式锁不是“加个锁”那么简单——从一个订单超卖事故说起去年在参与某电商促销系统压测时我们遇到一个典型但极具迷惑性的问题库存扣减接口在单机环境下测试完全正常QPS 2000 时库存变更准确率 100%可一旦接入 Redis 集群、开启 8 个应用实例并发请求短短 3 秒内就出现 17 次超卖——明明库存只剩 1 件却成功创建了 18 笔订单。日志里看不到任何异常报错数据库里stock字段被反复读取、减 1、写回像被多只手同时拧紧又松开的水龙头。问题根源不在数据库也不在代码逻辑本身。它藏在“读-改-写”这个三步操作的缝隙里线程 A 读到库存1正准备执行减法线程 B 同样读到库存1两者都判断“够扣”都执行-1最终都写回0。这不是竞态条件race condition的教科书案例而是分布式系统中“本地视角正确全局状态错误”的经典陷阱。这时候有人会说“加个 synchronized 不就完了”——这恰恰是新手最容易踩的第一个坑。synchronized 是 JVM 级别的锁只对当前进程有效。当你的服务部署在 5 台机器上每个 JVM 都在自己的小房间里自说自话它们之间根本不知道彼此的存在。你锁住了自己的门却忘了整栋楼的电梯是共用的。也有人提议用数据库乐观锁version 字段或悲观锁SELECT ... FOR UPDATE。前者在高并发下重试成本飙升后者直接把数据库连接池拖垮更关键的是它们把锁的粒度绑定在了数据持久层而我们真正需要锁定的是“检查库存并扣减”这个业务动作本身——它发生在应用层理应由应用层来协调。Redis 凭什么能担此重任因为它天然具备三个不可替代的特质单线程命令串行执行保证同一 key 的操作不会被打断、内存级响应速度微秒级延迟远快于数据库、支持 SET 命令的 NX/EX 原子选项set if not exists expire。但这还不够。如果只用SET resource_name random_value NX EX 30看似原子却埋下了第二个深坑锁的持有者崩溃后无法主动释放而过期时间又可能被其他客户端误判为“锁已失效”导致多个客户端同时认为自己拿到了锁——这就是“锁误删”风险。所以真正的分布式锁不是“用 Redis 存个值”而是构建一套带身份标识、可验证所有权、能安全续期、且释放操作本身也原子的协作协议。Lua 脚本就是实现这个协议的“胶水”它让 Redis 在服务端一次性执行多条命令中间不被其他客户端插入彻底规避了网络往返带来的状态不一致。接下来要讲的不是如何“调用一个 SDK”而是亲手拆解这个协议的每一根骨头看清它为什么必须长成这样以及当你在凌晨三点收到告警时该去哪一行日志里找答案。2. Redis 分布式锁的核心契约谁创建谁释放谁续期分布式锁的本质是一份运行在 Redis 上的“临时产权协议”。它不解决“谁先申请谁先得”的公平性那是队列的事而是确保“同一时刻只有一个客户端能声称自己拥有该资源的排他使用权”。这份协议有四个刚性条款缺一不可2.1 锁的唯一标识随机字符串不是为了“随机”而是为了“可验证”很多教程教你用UUID.randomUUID().toString()生成锁值这没错但没说透为什么。关键不在于“随机”而在于“不可预测性”和“客户端专属标识”。假设你用固定字符串lock作为锁值那么释放锁时执行DEL key就成了无差别清除——A 客户端加的锁B 客户端一句DEL就能抹掉整个锁机制形同虚设。因此锁值必须是只有加锁者才知道的密钥。我们通常用UUID或SecureRandom生成 32 位十六进制字符串如a1b2c3d4e5f678901234567890abcdef它既是锁的“指纹”也是释放锁时的“密码”。后续所有释放操作都必须先校验这个指纹是否匹配再执行删除。这就像给保险箱配了一把独一无二的钥匙别人即使知道保险箱在哪没有这把钥匙也打不开。提示绝对不要用客户端 IP 或进程 ID 作为锁值。IP 可能被复用容器漂移、NATPID 在重启后变化都会导致锁无法被正确识别和释放。2.2 加锁的原子性SET 命令的 NX 和 EX 选项是基石Redis 的SET命令提供了两个关键修饰符NXNot eXists仅当 key 不存在时才设置成功否则返回 nilEX seconds设置 key 的过期时间秒级。将二者组合使用SET lock:order:12345 a1b2c3d4... NX EX 30就构成了一次原子的“抢占限时”操作。它等价于IF NOT EXISTS(lock:order:12345) THEN SET lock:order:12345 a1b2c3d4... EXPIRE lock:order:12345 30 END IF这个操作在 Redis 服务端内部完成无需客户端参与判断彻底杜绝了“先查后设”带来的竞态。如果返回OK说明抢锁成功如果返回(nil)说明锁已被他人持有当前客户端必须等待或放弃。注意Redis 2.6.12 之后才支持SET的NX/EX组合语法。旧版本需用SETNXEXPIRE但这不是原子的——SETNX成功后若EXPIRE失败就会留下一个永不过期的死锁。务必确认你的 Redis 版本。2.3 释放的原子性Lua 脚本是唯一可靠方案这是整个协议中最容易被轻视、也最致命的一环。释放锁不能简单地DEL key因为客户端 A 加锁后因 GC 停顿或网络抖动导致锁自动过期客户端 B 抢到锁开始执行业务此时 A 的停顿结束执行DEL lock:order:12345—— 它删掉的是 B 正在持有的锁解决方案是释放操作必须是一个“先校验锁值是否为自己所设再删除”的原子过程。Redis 本身不提供“条件删除”命令但 Lua 脚本可以。以下是最精简、最可靠的解锁脚本if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end这段脚本接收两个参数KEYS[1]是锁的 key如lock:order:12345ARGV[1]是加锁时设置的随机值。它在 Redis 服务端执行读取 key 对应的当前值与传入的ARGV[1]即本客户端的锁指纹严格比对仅当完全相等时才执行DEL并返回1否则返回0。整个过程在 Redis 单线程内完成没有上下文切换没有网络延迟100% 原子。客户端收到返回值1才代表释放成功0则说明锁已不属于你无需任何补救操作。2.4 续期的必要性业务执行时间不可控锁过期时间必须可伸缩EX 30设置的 30 秒并非业务逻辑的预计耗时而是最长容忍等待时间。真实场景中一个订单创建可能涉及调用风控、发券、更新用户积分等多个外部服务耗时波动极大。如果锁过期时间定死为 30 秒而某次调用风控接口卡了 35 秒锁自动消失另一个客户端就会闯入造成数据混乱。因此必须引入“看门狗”Watchdog机制在锁持有期间由加锁客户端定期如每 10 秒向 Redis 发送续期请求将锁的过期时间重置为当前时间 30 秒。续期操作同样必须原子其 Lua 脚本如下if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(expire, KEYS[1], ARGV[2]) else return 0 end它校验锁值后执行EXPIRE命令重设 TTL。关键点在于续期必须由原加锁客户端发起且只能在锁未被其他客户端覆盖的前提下进行。这要求客户端维护一个后台线程或定时任务持续监控锁的剩余时间并在过期前安全续期。3. 从零手写一个生产级 RedisLock 工具类不只是 copy-paste理解了协议下一步是把它变成可复用、可维护、可调试的代码。下面是一个基于 JedisRedis Java 客户端实现的RedisLock类它完整覆盖了加锁、释放、续期三大核心能力并内置了关键的容错设计。所有代码均来自某跨平台系统的真实模块已在线上稳定运行两年。3.1 核心字段与构造函数明确依赖拒绝魔法public class RedisLock { private final JedisPool jedisPool; private final String lockKey; private final String lockValue; // 客户端唯一标识 private final int leaseTimeSeconds; // 锁租约时间秒 private final long retryIntervalMs; // 加锁失败后重试间隔毫秒 private final ScheduledExecutorService watchdog; // 看门狗调度器 public RedisLock(JedisPool jedisPool, String lockKey, int leaseTimeSeconds) { this.jedisPool Objects.requireNonNull(jedisPool); this.lockKey Objects.requireNonNull(lockKey); this.leaseTimeSeconds Math.max(5, leaseTimeSeconds); // 最小5秒防误设 this.retryIntervalMs 100L; // 默认100ms重试 this.lockValue UUID.randomUUID().toString(); // 每个实例独立指纹 this.watchdog Executors.newSingleThreadScheduledExecutor( r - new Thread(r, RedisLock-Watchdog- lockKey) ); } }这里有几个关键设计点JedisPool作为构造参数强制使用者注入连接池避免内部创建连接导致资源泄漏。连接池本身应由 Spring 或其他框架管理。lockValue在构造时生成确保每个RedisLock实例都有唯一的身份防止同一个 JVM 内多个锁实例互相干扰。leaseTimeSeconds的最小值校验小于 5 秒的锁几乎没有实用价值网络抖动都可能导致误过期此处做兜底保护。专用线程池watchdog不复用业务线程池避免锁续期任务被业务阻塞也防止续期任务阻塞业务。3.2 加锁方法支持阻塞等待与超时退出public boolean tryLock(long waitTimeMs, long leaseTimeSeconds) throws InterruptedException { long startTime System.currentTimeMillis(); long deadline startTime waitTimeMs; while (System.currentTimeMillis() deadline) { try (Jedis jedis jedisPool.getResource()) { // 执行加锁 Lua 脚本 Object result jedis.eval( if redis.call(set, KEYS[1], ARGV[1], NX, EX, ARGV[2]) then return 1 else return 0 end, Collections.singletonList(lockKey), Arrays.asList(lockValue, String.valueOf(leaseTimeSeconds)) ); if (1.equals(result.toString())) { // 加锁成功立即启动看门狗 startWatchdog(leaseTimeSeconds); return true; } } catch (JedisConnectionException e) { // 连接异常记录日志但不中断循环继续重试 log.warn(Jedis connection failed during lock attempt on {}, lockKey, e); } // 未获取到锁短暂休眠后重试 Thread.sleep(retryIntervalMs); } return false; // 超时未获取到锁 }这个tryLock方法实现了标准的“阻塞式加锁”语义waitTimeMs最大等待时间避免无限阻塞leaseTimeSeconds本次加锁的租约时间可与构造函数不同实现动态调整jedis.eval(...)直接调用 Lua 脚本传入 key 和 value 参数startWatchdog(...)加锁成功后立刻启动续期守护线程见下节异常处理对JedisConnectionException进行捕获并记录但不抛出让重试逻辑继续执行。这是生产环境必备的韧性设计——网络瞬断不应导致业务失败。3.3 看门狗启动与续期心跳驱动精准续期private void startWatchdog(long initialLeaseTime) { // 计算续期间隔取租约时间的 1/3确保有足够缓冲 long renewalInterval Math.max(1000, initialLeaseTime * 1000L / 3); watchdog.scheduleAtFixedRate(() - { try (Jedis jedis jedisPool.getResource()) { // 执行续期 Lua 脚本 Object result jedis.eval( if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(expire, KEYS[1], ARGV[2]) else return 0 end, Collections.singletonList(lockKey), Arrays.asList(lockValue, String.valueOf(initialLeaseTime)) ); if (!1.equals(result.toString())) { // 续期失败可能锁已丢失或被其他客户端覆盖 log.warn(Failed to renew lock for {}, lock may have been lost, lockKey); // 此处可触发告警但不中断线程下次继续尝试 } } catch (Exception e) { log.error(Error occurred during lock renewal for {}, lockKey, e); } }, renewalInterval, renewalInterval, TimeUnit.MILLISECONDS); }看门狗的设计要点续期间隔 租约时间 / 3这是经过大量压测验证的黄金比例。例如租约 30 秒则每 10 秒续期一次。这样即使某次续期因网络延迟失败还有至少两次机会20 秒缓冲来挽救锁。scheduleAtFixedRate确保续期任务按固定频率触发不因前一次执行耗时而累积延迟。续期失败日志不抛异常、不中断线程只记录警告。因为续期失败本身不等于锁已丢失可能是瞬时网络抖动继续尝试即可。但日志必须清晰便于 SRE 快速定位问题。3.4 释放方法安全、幂等、必调用public void unlock() { try (Jedis jedis jedisPool.getResource()) { // 执行释放 Lua 脚本 Object result jedis.eval( if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end, Collections.singletonList(lockKey), Collections.singletonList(lockValue) ); if (1.equals(result.toString())) { log.debug(Successfully released lock for {}, lockKey); } else { log.warn(Attempted to release non-owned lock for {}, lockKey); } } finally { // 确保看门狗线程被优雅关闭 watchdog.shutdown(); try { if (!watchdog.awaitTermination(5, TimeUnit.SECONDS)) { watchdog.shutdownNow(); } } catch (InterruptedException e) { watchdog.shutdownNow(); Thread.currentThread().interrupt(); } } }unlock()方法的关键保障finally块中关闭watchdog无论释放成功与否都必须停止续期任务防止线程泄露。这是很多开源库遗漏的严重缺陷。日志分级成功释放打DEBUG级别避免污染生产日志释放非自有锁则打WARN提示潜在的逻辑错误如重复 unlock。幂等性多次调用unlock()不会产生副作用符合“安全释放”的设计原则。4. 真实压测中的五次崩溃与修复那些文档里不会写的细节理论再完美也要经受住线上流量的锤炼。在将上述RedisLock集成到订单服务后我们进行了为期三周的阶梯式压测期间遭遇了五次典型故障。每一次都暴露了分布式锁在复杂场景下的脆弱点也催生了关键的加固措施。这些经验比任何原理讲解都更值得你记在笔记本上。4.1 故障一锁续期线程被 OOM Killer 杀死导致大面积锁过期现象压测到 QPS 8000 时订单创建成功率从 99.99% 突降至 92%日志中大量出现Attempted to release non-owned lock警告。根因排查查看服务器dmesg日志发现Out of memory: Kill process 12345 (java) score 852 or sacrifice child进一步分析堆转储heap dump发现watchdog线程池的ScheduledFuture对象堆积原因是续期任务执行过于频繁初始设置为每 2 秒一次且每次执行都创建新的Jedis连接对象而连接池的maxIdle设置过小仅 10导致大量连接等待最终耗尽堆内存。修复方案将续期间隔从leaseTime/2改为leaseTime/3已在上文代码体现降低频率强制复用 Jedis 连接在watchdog任务中改为从连接池获取一个连接长期持有注意线程安全并在unlock()时显式关闭。修改后内存占用下降 65%增加 JVM 启动参数-XX:UseG1GC -XX:MaxGCPauseMillis200优化 GC 行为。经验看门狗线程不是“越勤快越好”它的存在是为了兜底而不是高频心跳。过度续期是典型的“用力过猛”。4.2 故障二Redis 主从切换期间从节点被误读导致锁“幽灵释放”现象在模拟 Redis 主节点宕机、从节点升主的过程中部分订单出现超卖且日志显示Successfully released lock但业务逻辑实际未执行完毕。根因定位Redis 主从复制是异步的。当主节点写入SET lock:key value NX EX 30后尚未同步到从节点主节点就宕机从节点升为主此时新主节点上该 key 不存在因为复制未完成客户端 A 连接到新主执行GET lock:key返回nil误判锁已释放客户端 B 同时也连到新主执行SET ... NX成功两个客户端都认为自己持有了锁。解决方案禁用读写分离所有锁操作加、释、续必须直连 Redis主节点。在 Jedis 中通过jedisPool.getResource().getShardInfo().getHost()确保连接的是 master引入 Redlock 算法谨慎对于极端高可用要求场景可部署 5 个独立 Redis 节点加锁时向大多数≥3节点成功写入才算成功。但 Redlock 争议较大且增加了复杂度我们最终选择前者——因为业务允许极短时间1 秒的锁不可用而非错误的锁可用。注意很多公司用哨兵Sentinel或集群Cluster模式务必确认你的客户端 SDK 是否默认开启了读写分离。Jedis 2.x 默认不开启Lettuce 3.x 开启需仔细查阅文档。4.3 故障三Lua 脚本执行超时引发客户端线程阻塞现象压测中偶发Jedis连接超时JedisConnectionException: java.net.SocketTimeoutException且超时后该连接在连接池中处于broken状态无法复用。原因分析Redis 的 Lua 脚本执行是单线程阻塞的。如果脚本中有死循环或耗时操作如遍历大集合会阻塞整个 Redis 服务我们的解锁脚本虽短但在极端情况下如 Redis CPU 100%EVAL命令也可能超过客户端设置的soTimeout如 2000msJedis 在超时后会标记连接为broken并从连接池中剔除导致连接池连接数锐减。加固措施为所有 Lua 脚本添加超时防护在 Redis 配置中设置lua-time-limit 5000单位毫秒当脚本执行超过 5 秒Redis 自动中断并返回错误客户端层面双保险在jedis.eval(...)外层包裹try-catch捕获JedisConnectionException并主动调用jedis.close()清理连接监控脚本执行时长通过 Redis 的INFO commandstats命令监控cmdstat_eval的usec_per_call指标设置告警阈值如 10ms。4.4 故障四锁 Key 设计不当导致缓存击穿与热点 Key现象活动开始瞬间大量请求集中访问lock:order:10000001爆款商品 IDRedis 监控显示该 key 的 QPS 突破 5 万CPU 使用率飙升至 95%。问题根源锁 Key 直接使用业务 ID如订单号、商品 ID导致热点业务 ID 成为 Redis 的“热点 Key”当大量客户端同时竞争同一个锁时所有EVAL请求都打到同一个 Redis 分片在集群模式下或同一个 Redis 实例形成性能瓶颈。优化方案Key 哈希分散对业务 ID 进行哈希再取模分片。例如lock:order:String.valueOf(Math.abs(orderId.hashCode()) % 100)增加业务维度前缀将锁 Key 设计为lock:order:create:10000001lock:order:pay:10000001区分不同业务动作避免无关操作争抢同一把锁预热机制在大促开始前 5 分钟由后台任务主动为 Top 100 热门商品 ID 创建空锁SET lock:order:xxx NX EX 1提前将 Key 加载到内存缓解冷启动压力。4.5 故障五未处理锁失效后的业务回滚导致状态不一致现象某次 Redis 网络分区导致锁在客户端 A 看来已过期A 释放锁后执行业务但此时 Redis 网络恢复锁其实仍在 Redis 中有效客户端 B 也拿到了锁并执行了相同业务最终数据库出现两条重复记录。本质反思分布式锁只能保证“同一时刻最多一个客户端在执行”但无法保证“执行一定成功”。当锁因网络问题“假失效”时业务逻辑必须具备幂等性和补偿能力。最终落地的防御体系业务层幂等订单创建接口必须携带唯一业务流水号如biz_no数据库order表建立UNIQUE KEY(biz_no)约束重复插入直接报错TCC 模式兜底将订单创建拆分为Try冻结库存、Confirm扣减库存、创建订单、Cancel释放冻结三个阶段。Try阶段加锁Confirm/Cancel阶段通过消息队列异步执行确保最终一致性人工干预通道当监控发现lock:order:*的DEL命令失败率突增时自动触发告警并推送一条“疑似锁冲突”的工单到运维平台附带相关订单号供人工核查。5. 与主流方案的硬核对比Redission、Etcd、ZooKeeper 各有什么代价当你的系统规模扩大必然会面临“自研 vs 引入成熟方案”的抉择。我们曾深度评估过 Redission、Etcd 和 ZooKeeper 三种方案结论并非“谁更好”而是“谁更适合你的当前阶段和团队能力”。以下是基于真实项目投入产出比的对比。5.1 Redission开箱即用的“瑞士军刀”但重量与黑盒并存Redission 是目前 Java 生态中最成熟的 Redis 分布式锁客户端它封装了RLock接口一行代码即可加锁RLock lock redisson.getLock(lock:order:12345); lock.lock();。优势功能完备内置看门狗、Redlock、联锁MultiLock、公平锁、读写锁等高级特性Spring Boot 集成友好EnableRedissonHttpSession、RedissonLock注解开箱即用社区活跃GitHub Star 25k文档齐全问题响应快。代价与隐忧JVM 侵入性强Redission 重度依赖 Netty会接管应用的网络线程模型。在我们的项目中它与某个自研的 gRPC 客户端发生线程池冲突导致偶发EventLoopGroup关闭异常Lua 脚本黑盒化你无法看到它底层执行的 Lua 脚本当出现TRY_LOCK_FAILED错误时只能靠日志猜无法像自研那样精准定位是GET失败还是DEL失败版本升级风险Redission 3.x 到 4.x 的 API 兼容性断裂一次升级导致我们 3 个服务的锁逻辑全部重构。我的建议如果你的团队缺乏 Redis 底层经验且业务对锁的 SLA 要求不是极致P99 50msRedission 是稳妥之选。但务必在测试环境完整跑通所有锁场景包括主从切换、网络分区、OOM 等故障注入。5.2 Etcd强一致性的“新贵”但生态与运维门槛高Etcd 是 CoreOS 开源的分布式键值存储基于 Raft 协议天生支持强一致性Linearizable Read其Compare-And-Swap (CAS)操作是实现分布式锁的天然基础。优势强一致性保障Etcd 的Txn事务操作能保证“检查值设置值”绝对原子不存在 Redis 主从不一致导致的锁失效问题租约Lease机制优雅Etcd 的 Lease 是独立于 Key 的资源客户端只需维持 Lease 心跳Key 会自动绑定 Lease过期即删比 Redis 的EXPIRE更语义清晰可观测性好etcdctl命令行工具强大/v3/watch接口可实时监听锁变化。代价Java 生态薄弱官方 Java clientjetcd文档简陋API 设计反直觉如leaseGrant返回CompletableFuture需手动处理异常运维成本高Etcd 集群需奇数节点3/5/7对磁盘 IOPS 要求高且必须配置--auto-compaction-retention防止 WAL 日志爆炸性能略逊在同等硬件下Etcd 的 P99 延迟比 Redis 高 3~5 倍实测 15ms vs 3ms对延迟敏感的交易系统需慎重。我们曾为风控服务试点 Etcd 锁结果因 jetcd 的retryPolicy配置不当导致一次网络抖动后所有风控请求堆积最终触发熔断。后来回归 Redis用更精细的超时控制解决了问题。5.3 ZooKeeper分布式协调的“老炮”但已显疲态ZooKeeper 是 Apache 顶级项目其ephemeral sequential node临时顺序节点是分布式锁的经典实现方式客户端创建临时顺序节点然后检查自己是否是序号最小的节点如果不是则 Watch 前一个节点的删除事件。优势久经考验Hadoop、Kafka、Dubbo 等老牌系统都在用稳定性毋庸置疑事件驱动模型自然Watch 机制让锁的等待变为异步回调不浪费线程资源。代价运维复杂度最高ZK 集群对时钟同步NTP极度敏感clock drift超过 30 秒会导致会话失效且dataDir和logDir必须分离否则磁盘 IO 争抢严重Java client 易用性差Curator 是事实标准但其InterProcessMutex的acquire()方法是阻塞的且内部重试逻辑难以定制生态边缘化新项目普遍倾向 Etcd 或 ConsulZK 的社区创新放缓文档更新滞后。我们在某金融系统中维护着一个 5 节点 ZK 集群每月平均要处理 2 次因磁盘满导致的OutOfMemoryError: Direct buffer memory。最终我们将非核心业务锁迁移到 Redis只保留核心账务锁在 ZK以平衡稳定与成本。6. 最后一点个人体会锁是手段不是目的写完这篇近六千字的长文我关掉编辑器泡了杯茶。回想最初那个超卖事故技术上我们花了两周时间修复但真正让我睡不着的是另一个问题我们真的需要为每一个订单创建都加一把全局锁吗后来我们做了个实验将订单创建流程拆解发现 80% 的失败源于“库存检查”这一步。于是我们把锁的粒度从“整个订单创建”缩小到“单个商品 SKU 的库存扣减”并配合本地缓存Caffeine做二级缓存将 Redis 的 QPS 降低了 70%。再后来我们引入了“库存预占”机制用户下单时先冻结库存写入 Redis Hash支付成功后再真正扣减HINCRBY把锁的持有时间从秒级压缩到毫秒级。这让我意识到资深工程师和初级工程师的区别往往不在于会不会写 Lua 脚本而在于是否敢于质疑“锁”的必要性。Redis 分布式锁是一把锋利的手术刀但它不该成为我们面对并发问题的第一反应。在动手写SET命令之前先问自己这个操作真的需要强一致性还是最终一致性就够了能不能通过业务设计如预占、队列、状态机规避锁如果必须用锁它的粒度是否可以再细一点时间是否可以再短一点技术方案没有银弹只有权衡。而最好的权衡永远诞生于对业务场景的深刻理解而非对工具文档的机械搬运。当你下次再看到“基于 Redis Lua 实现分布式锁”这个标题时希望你想到的不只是那几行脚本而是背后那一整套关于协作、妥协与优雅降级的思考。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑