当前位置:首页 > 文章列表 > 数据库 > Redis > 分布式锁续期失败后还能继续工作吗:租约与围栏令牌

分布式锁续期失败后还能继续工作吗:租约与围栏令牌

来源:17golang原创 2026-10-07 15:23:10 0浏览 收藏

Redis 分布式锁续期失败后,旧客户端不应继续执行会修改共享资源的关键工作。失败意味着它已经无法证明租约仍属于自己;即使业务线程还活着,锁也可能已经过期并被另一个客户端取得。正确处理是立即把租约标记为失效、取消后续副作用,并让下游资源用单调递增的围栏令牌拒绝迟到的旧写入。

Redis 官方文档:https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/

续期只是延长租约的手段,不是所有权的永久证明;围栏令牌才是共享资源判断“这个写入是否已经过期”的最后一道门。

规模上来后,续期失败会暴露什么问题

在任务量较小时,很多系统用一条 SET key value NX PX ttl 就能避免大多数重复执行。规模增大后,任务耗时开始抖动,客户端也会遇到长时间 GC、线程饥饿、网络分区、Redis 超时或进程暂停。此时,业务线程可能还在运行,但锁的 TTL 已经耗尽。

假设客户端 A 获得 15 秒租约,开始生成一份大报表。第 10 秒时续期请求超时,A 仍继续计算。第 15 秒锁自动释放,客户端 B 获得同一资源的新锁并完成写入。随后 A 从暂停中恢复,又把旧结果写进对象存储或数据库。Redis 里从未同时存在两个有效锁,但共享资源仍然被旧持有者覆盖。

这个问题说明:锁服务只能证明某个时间窗口内的互斥,不能替下游存储撤销已经在路上的旧请求。Redis 官方分布式锁文档也把锁的有效性限定在租约窗口内,并提醒不要假设进程存活就等于锁仍被持有。

只有随机令牌和 TTL 的旧方案缺少最后一道门

一个基本的 Redis 锁至少需要随机所有者令牌和过期时间:

# 只有锁键不存在时才写入,并设置 15 秒租约
redis-cli SET lock:report owner-random-token NX PX 15000

# 返回 OK 表示取得租约;空返回表示已有其他持有者

随机令牌的作用是识别所有者。释放和续期时都必须先比较锁值,只允许当前所有者操作自己的锁,避免 A 在锁过期后误删 B 的新锁。它解决的是“这把锁是不是我创建的”,却不能回答“我的写入是否比另一个持有者更新”。

TTL 则同时承担两件事:客户端崩溃后自动释放资源,以及限制客户端可以安全工作的时间。TTL 过短会频繁续期,过长会让故障后的恢复等待更久。无论取值多大,只要系统允许暂停或网络分区,就不能依赖“通常能及时续上”作为一致性保证。

Redis锁键、随机所有者令牌、TTL租约、续期器与业务工作静态依赖图
图1:租约与所有权边界说明图。Redis 协调域保存锁键、随机所有者令牌和 TTL,应用域中的续期器只能在所有者匹配时延长租约;这是静态结构图,不是运行截图。

把锁重构成可取消租约

续期操作必须是原子的:只有锁键仍存在且值仍等于当前随机令牌时,才延长 TTL。下面的 Lua 片段表达了这个条件,示例用于说明原子边界,不代表必须自行替代成熟客户端库:

-- 只有当前客户端仍是锁所有者时才延长租约
if redis.call('get', KEYS[1]) == ARGV[1] then
    -- ARGV[2] 是新的毫秒级租约长度
    return redis.call('pexpire', KEYS[1], ARGV[2])
end

-- 返回 0 表示锁已丢失、已过期或已属于其他客户端
return 0

应用收到续期失败、超时或无法确认结果时,应按“租约已经丢失”处理,而不是继续重试到成功。一个实用状态机包含 ACTIVE、LOST 和 RELEASED:续期成功保持 ACTIVE;续期失败立即进入 LOST,触发取消信号;业务自然完成后,仅在仍为 ACTIVE 时执行最终提交,然后按所有者令牌安全释放。

// 租约丢失时取消业务上下文,阻止后续可取消工作继续推进
func watchLease(ctx context.Context, cancel context.CancelFunc, renew func(context.Context) bool) {
    ticker := time.NewTicker(renewInterval)
    defer ticker.Stop()

    for {
        select {
        case 

核对点有两个。第一,续期应在租约剩余时间进入危险区之前发起,并为网络抖动保留余量;第二,业务代码必须真正响应取消。只设置一个 lost = true 标志却让数据库写入、对象存储上传和消息发送继续进行,并没有缩小风险。

让围栏令牌在资源侧拒绝旧持有者

对于不能及时取消的 I/O,或者已经发到下游的请求,需要围栏令牌补上资源侧保护。每次成功获得锁时,协调系统同时分配一个单调递增整数,例如 A 获得 41,B 后来获得 42。下游资源保存最近接受的 last_fence,只接受比它更大的令牌。

围栏令牌和随机所有者令牌不能互换。随机值适合验证“释放的是不是自己的锁”,但不能比较新旧;围栏值必须可排序,用来判断哪个持有者更新。Redis 的普通锁命令不会自动替所有共享资源完成这个检查,资源侧也必须参与。

在单 Redis 协调点的简化设计中,可以把“锁不存在时分配序列并写入所有者”放进一个 Lua 脚本。若使用 Redis Cluster,脚本涉及的键还需要满足同一哈希槽要求;更复杂的多节点锁方案则应单独设计可靠的全局序列来源。

-- 锁已存在时返回空,调用方不能进入受保护工作
if redis.call('exists', KEYS[1]) == 1 then
    return nil
end

-- 只有成功取得锁的客户端才获得新的单调递增围栏号
local fence = redis.call('incr', KEYS[2])
redis.call('psetex', KEYS[1], ARGV[2], ARGV[1])
return fence

下游数据库可以把令牌检查与业务写入放在同一条原子语句里:

-- 仅接受比当前 last_fence 更新的持有者写入
UPDATE report_state
SET result_uri = :result_uri,
    last_fence = :fence
WHERE report_id = :report_id
  AND last_fence 

回到前面的例子:B 持有 42 并先写入后,资源侧记录 last_fence = 42。A 即使从暂停中恢复并携带 41 发起写入,也会因条件不满足被拒绝。这个拒绝与 Redis 锁键当前是否存在无关,因此能防住迟到的旧客户端。

Redis协调域、客户端围栏令牌与下游资源门禁静态关系图
图2:围栏令牌资源门禁说明图。客户端 A 的旧令牌 41 与客户端 B 的新令牌 42 都到达资源域,但资源门禁依据 last_fence 只接受更新令牌;这是静态结构图,不是运行结果。

接受更强安全边界带来的成本

资源必须能比较令牌。数据库表可以增加 last_fence,对象存储或第三方 API 如果没有条件写能力,就需要在前置服务中串行化或换用支持条件更新的存储。围栏只在最终副作用执行点检查才有效。

令牌分配要保持单调。进程内计数器、时间戳和随机数都不满足要求。序列来源重置、回档或多活冲突会破坏“更大就是更新”的判断,因此序列的持久化与故障模型必须单独评估。

取消不是回滚。租约丢失后取消上下文,只能阻止尚未开始或支持取消的操作。已经提交的数据库事务、已发送的消息和外部 API 调用仍需依靠幂等键、条件更新或补偿逻辑处理。

续期次数要有限。Redis 官方文档指出,续期可以延长锁的生命周期,但不应无限重获,否则会损害活性。长任务应拆分检查点、限制最大持锁时间,并允许失败后从稳定状态重试。

用运行信号判断方案是否生效

架构上线后,不能只看“有没有重复任务”。建议至少记录四组信号:

信号说明异常时先查什么
续期失败率租约无法确认或所有者不匹配的比例Redis 延迟、超时、线程暂停
取消传播延迟检测 LOST 到业务停止新副作用的时间阻塞调用是否支持取消
围栏拒绝量资源侧拒绝旧令牌的次数长暂停、任务重叠、重试风暴
提交时租约余量关键提交时距离 TTL 到期的剩余时间租约是否过短、任务是否应拆分

这些指标的目标不是追求全部为零。围栏拒绝恰恰说明最后一道门在工作;真正需要警惕的是持续增长、取消传播过慢,或者大量任务在租约即将到期时才提交。

继续改进时优先处理的边界

第一,获取失败应随机退避,避免多个客户端同步重试。第二,释放锁必须比较随机所有者令牌,不能直接 DEL。第三,不要把服务器墙上时钟当作绝对安全边界;Redis 官方文档提醒 TTL 过期并非使用单调时钟,时钟变化可能影响锁安全。第四,关键任务同时需要幂等设计,因为围栏令牌解决的是新旧持有者顺序,不会自动消除同一持有者的重复请求。

常见问题

续期请求超时,但可能已经在 Redis 成功了,能继续吗?

不能按成功处理。客户端无法确认租约状态时,应进入 LOST 并停止新的受保护副作用。之后可以查询状态用于诊断,但不能用不确定结果继续执行关键写入。

只用看门狗自动续期够不够?

不够。看门狗降低正常慢任务过期的概率,却无法消除长暂停、网络分区和迟到请求。对一致性敏感的共享资源仍应实现围栏检查。

围栏令牌一定要存在 Redis 吗?

不一定。它可以来自能提供单调序列的数据库或协调系统。关键是每次新租约拿到更大的令牌,并由最终资源原子地比较和保存。

任务已经不可取消怎么办?

把不可取消阶段推迟到最后,并在进入前再次检查租约余量;最终提交必须带围栏令牌。若外部系统不支持条件写,则需要加一层能够执行门禁的代理或改造业务流程。

参考资料:Redis Distributed Locks:https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/;Redis SET:https://redis.io/docs/latest/commands/set/

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
errors.Is 与直接比较为何结果不同,错误链如何遍历errors.Is 与直接比较为何结果不同,错误链如何遍历
上一篇
errors.Is 与直接比较为何结果不同,错误链如何遍历
设计可重试错误与永久错误的稳定边界
下一篇
设计可重试错误与永久错误的稳定边界
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    365次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    421次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    435次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    387次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    214次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码