公司动态
Redis 分布式锁:上线前先把失败场景讲明白
Redis 分布式锁上线前先把失败场景讲明白在多实例服务中开发者常想用一把 Redis 锁保护“同一件事只能同时做一次”。订单状态变更、定时任务抢占、同一资源的批量处理都可能出现这个需求。Redis 提供的原子写入和过期机制让它成为一个方便的协调工具但方便不等于自动可靠。若不了解锁到期、进程暂停和故障切换带来的限制锁反而会给业务一种虚假的安全感。一套可用的方案首先应承认自己的边界。锁可以减少并发冲突却不能替代数据层的约束、幂等处理和最终一致性设计。只有先弄清业务是否能承受重复执行、旧请求恢复写入或短暂的互斥失效才知道 Redis 锁是否适合放在这条链路上。先问锁保护的是哪一步很多锁使用失败原因不是 Redis 命令写错而是临界区根本没有定义清楚。要保护的是“读取后计算再写回”的整个过程还是只保护某一次写入锁键与哪个业务对象对应抢锁失败的请求是等待、返回忙碌还是进入队列这些规则应由业务决定不能留给每个调用方临时发挥。不要把一把全局锁用在所有请求上。它会让无关操作互相阻塞排队时又很难判断问题来自哪里。按资源、租户、任务或明确的业务标识划分锁键通常更贴合实际约束。反过来过度细分也可能漏掉真正需要协调的组合操作需要结合数据关系判断。在决定加锁前也应检查是否有更直接的机制。唯一索引、条件更新、版本号校验、幂等请求号和消息去重往往比外部锁更接近资源本身。分布式锁适合协调不能替代这些最终防线。获取和释放要保持持有关系锁键必须带过期时间避免服务崩溃后一直被占用。创建锁和设置过期应当原子完成否则可能出现只创建了键、却没来得及设置有效期的残留状态。现代 Redis 客户端一般可以直接表达这种原子语义若团队自己封装更要避免拆成多条独立操作。锁值需要代表具体持有者。每次成功获取时生成独立标识释放时先确认 Redis 中的值仍然属于自己再删除键。比较和删除必须原子执行否则检查通过后、删除之前锁可能已过期并被别的请求重新取得。无条件删除是最典型的错误旧执行者结束时把新执行者的锁删掉临界区于是重新失守。代码结构也要照顾异常路径。拿到锁后正常返回、错误返回和取消都应走同一套释放逻辑。释放结果不匹配时不要简单当作成功这可能意味着锁已过期或持有关系改变日志应留下可关联的资源、请求和时间信息。过期与续期各自解决什么过期时间的存在是为了防止永久占锁却也带来一个现实问题业务还没做完锁就已经失效。暂停、慢依赖、调度延迟都可能让实际运行时间超过预估。此时另一个请求拿到锁并开始执行旧请求随后恢复两者可能并发修改同一资源。自动续期机制能在客户端仍正常运行、且确认自己仍是持有者的前提下延长锁的存活时间。使用类似看门狗能力时需要认识到它并非强一致协议客户端卡顿、网络中断或 Redis 不可用时续期也可能停止。它能降低长任务的过期概率但不能证明旧执行者永远不会在失锁后继续工作。对短且有明确上限的操作固定租约更容易推理。对长任务除了评估续期外还应拆分工作、设置取消点并让业务在发现持有状态失效时停止后续写入。把锁时间一味调大只是把故障后的等待拉得更长。防止旧持有者在失锁后继续写真正危险的情况并不是只有“误删别人的锁”。即使释放实现正确旧客户端也可能在锁过期后恢复运行并把过时结果写回资源。若资源本身没有拒绝旧写入的能力外部锁并不能完全阻止这一点。对一致性要求较高的操作可考虑让每次持有关系携带可比较的版本或令牌资源侧只接受仍然有效的新请求。数据库的条件更新、乐观锁和版本字段也能承担类似职责。具体选择取决于资源位置和业务模型但原则是一致的真正的写入者需要知道请求是否已经过时。这也是为什么库存、金额、不可逆状态迁移等场景不应只靠一个 Redis 键来保证正确性。锁可以优化竞争和降低冲突概率数据层的约束和补偿策略仍然不可缺少。主从切换与多节点不能被神化Redis 在不同部署方式下的故障语义不同。主从复制在某些情况下可能尚未完全传播最新写入故障切换后新主节点的锁状态可能与客户端认为的状态不一致。使用 Redis 锁时应把这种风险纳入业务评估而不是假设“加锁成功”就等于在任何故障下都绝对互斥。多节点锁算法尝试通过多个独立实例提高可用性但同样要面对网络延迟、时钟和客户端暂停等分布式系统问题。它们不应被简单描述为面向所有强一致场景的通用答案。对于必须在故障和网络分区下维持严格协调的资源应评估更贴近强一致需求的协调服务或资源级约束。选型没有一句话结论。能重试、可幂等的后台任务与不能重复扣减的关键交易所需保证完全不同。技术实现要与实际风险相称。把等待、观测和演练一起交付锁竞争失败时的行为必须有限制。无界自旋会消耗 CPU无节制重试会在故障时放大压力。应设定合理等待时间、退避策略和业务侧反馈并监控竞争、持有时长、过期和续期失败等信号。监控应帮助排查但不应记录不必要的敏感业务内容。上线前可在受控环境中演练关键失败路径多个请求竞争、业务超过租约、客户端中止、Redis 短暂不可达、锁过期后的旧请求恢复。演练不是为了证明实现永远不会错而是确认系统在不理想情况下仍有可理解的行为。Redis 分布式锁值得使用的前提是团队知道它承诺什么、没有承诺什么。原子获取、身份校验释放、谨慎的过期与续期、资源侧的最终保护再加上明确的故障处理才能让它成为可靠的协调手段。