锁过期了,旧任务为什么还在运行

AI 生成概念视觉;图中装置不代表真实系统架构。
摘要| 一个任务要运行二十分钟,锁却每分钟需要续租。这两个时间并不矛盾。沿着接口引擎的实际代码,分清执行预算、Redis 租约和旧执行者写入资格,才能判断长任务是否可靠。
✦ 01|先把两个时钟分开
设想一个批量导入:节点 A 获得锁,开始处理文件;它随后卡在外部请求里,Redis 中的锁先到期。节点 B 此时能获得新锁,但 A 的线程并不会因为一个键消失而自动终止。两段代码可能继续向前走。这个例子是故障模型,不是本次在线事故记录。
因此,看到“锁已经失效”不能推导出“旧任务已经停下”。锁回答谁现在可以进入,执行预算回答允许跑多久,取消信号回答要不要停;最后还有一个问题:数据库或外部系统是否拒绝已经失去资格的写入。
先记住这一点| 租约到期是所有权变化,执行停止是运行时行为。 两者需要明确衔接,不能依靠时间恰好一致。

解释示意:锁到期后,旧执行可能仍停留在不可立即中断的调用中。
✦ 02|从业务入口找到真正的锁边界
这次分析选择 Microi吾码平台的接口引擎路径。一般业务逻辑由表单事件或接口引擎编排,可信身份、Redis 所有权和运行时取消由平台底层维护。阅读时先找执行入口,再找构造锁参数的函数,最后沿着锁包装器查看任务如何运行。
当前工作区的 ApiEngine.cs 会判断调用是否来自可信后台执行上下文,再创建锁参数,随后通过 ActionLockAsync 包住 V8 执行。请求里写一个看起来像后台任务的字段,并不能代替服务端建立的可信身份。
- 普通 HTTP 或普通 V8 调用:保持固定租期,不自动获得后台续租资格。
- 可信持久后台任务:采用可续租路径,同时传入取消令牌。
- LockKey 配置的是请求参数字段名,例如 OrderId;平台再用其值区分业务对象。
配置字段:LockKey = OrderId
请求示例:{ "OrderId": "order-demo-001" }
含义:同一订单互斥,不同订单不必排同一条队。
不要把示例当租户实测| 这里展示字段语义与当前源码调用关系,没有修改线上接口,也没有向真实订单写入数据。
✦ 03|二十分钟预算,一分钟租约
当前源码的 CreateExecutionLockParam 把后台可续租调用的单次租期限制在执行预算和六十秒中的较小值。例如预算一千二百秒时,Redis 单次租期为六十秒;只要持有者持续完成续租,任务就可以跨过多个租期。较短租期也缩短了进程崩溃后等待旧键自然失效的时间。
最长续租边界是另一项限制。当前实现取执行预算与十二小时中的较大值。因此十三小时预算的测试会保留十三小时边界。这个数字描述代码中的边界选择,不意味着每项任务都应运行十二小时,更不意味着外部调用可以无限等待。
后台预算 30 秒 → 单次租期 30 秒
后台预算 1200 秒 → 单次租期 60 秒
后台预算 13 小时 → 单次租期 60 秒
最长续租边界 → max(执行预算, 12 小时)
本地文档的后台任务表格仍有“Timeout 是单次租期”的概括,不能据此忽略源码中新加入的六十秒上限。本文以本次读取的实现和测试为准;判断已部署环境时,还需要核对实际二进制版本。

解释示意:三个边界各有职责,业务幂等仍需要自己的持久约束。
✦ 04|令牌不是装饰字段
分布式锁不能只保存“有人正在执行”。如果 A 的锁过期后 B 获得同一个业务锁,A 恢复运行时执行无条件删除,就可能把 B 的新锁删掉。所有权令牌使释放和续租能够核对“现在持有的人仍然是我吗”。
单调递增的 fencing token 则提供先后顺序。它的价值取决于写入边界是否比较令牌并拒绝旧值。仅仅生成或打印一个递增数字,并不会自动保护所有数据库和第三方接口。业务扩展调用独立外部系统时,必须检查那个系统实际支持什么约束。
一个必要的追问| 令牌在哪里被验证? 只有找到拒绝旧执行者的权威写入位置,才能评价该副作用是否得到保护。
✦ 05|业务幂等还要单独设计
锁降低并发碰撞,却不能替代“这件事已经做过”的业务记忆。导入可能在一半时重启,通知可能发送成功但响应丢失,支付回调也可能再次到达。此时最需要的往往是稳定的业务键、数据库唯一约束、条件状态更新和可查询结果。
- 把同一业务动作映射到稳定 RequestId 或 EventId;失败重试继续使用原值。
- 对不允许重复的流水建立唯一约束;单纯先查再插存在并发窗口。
- 用带旧状态或版本条件的更新推进状态;未命中应重新读取并判断。
- 跨系统副作用用 outbox/inbox 或对方真实支持的幂等键衔接。
// 设计表达,非可直接运行的数据库语法
提交条件 = 业务版本仍匹配 && 写入令牌未过期
重复 EventId = 读取此前结果
新 EventId = 事务内记录结果与待发送事件
把这些判断放到权威服务端,能让普通客户端、接口引擎和重试消费者遵循同一规则。前端禁用按钮可以改善交互,但无法封住网络重发、多个终端和后台补偿入口。
✦ 06|这次到底验证了什么
本次读取了当前接口引擎源码、锁实现、配置 Skill 和中文接口文档,并运行 ApiEngineLongRunningLockTests。现有 Release 测试程序集的筛选结果为八项通过、零失败、零跳过,测试执行时间六十六毫秒;原始 TRX 结果已保留。
测试覆盖可信后台识别、普通调用保留固定租期、长预算保留、四组预算到短租期的映射,以及源码调用路径约束。由于使用现有 Release 程序集,结果不能替代一次全量重新构建;源码检查与程序集测试在证据里分别登记。

由本次真实 TRX 结果渲染;不是在线控制台截图,也不是生产故障演练。
证据边界| 八项通过支持参数与调用路径判断。它没有证明双节点 Redis 故障切换、网络分区、第三方副作用或生产部署已经验收。
✦ 07|把评审问题写成可验证的问题
评审长任务时,可以沿一次具体业务从入口走到最后一个副作用:身份在哪里建立,锁怎么区分订单,失去租约以后取消如何传播,事务提交前是否仍确认资格,外部发送成功但本地未记账时怎样恢复。每个问题都应对应一段实现或一个实验。
对于超过十分钟的任务,检查点分片比单纯加长 Timeout 更容易说明恢复行为。检查点应表达已完成的稳定工作单元,让下一次执行知道从哪里继续,并保留任务版本和幂等范围。这里给出的是设计要求,本次没有运行长时间恢复演练。
Microi吾码AI 的价值可以落在这种可审阅的辅助分析上:帮助定位配置、整理调用路径和生成检查清单,最终结论仍由实际源码、测试和部署证据支撑。模型写出的“已经安全”不能当作证据。
✦ 08|把结论带回自己的系统
遇到“旧任务还在运行”,先观察它卡在什么调用,再查谁拥有当前租约,随后检查旧执行的下一次写入会不会被拒绝。这个顺序比只看锁键是否存在更接近问题本身。可靠性来自多个边界协同,任何一个漂亮数字都无法单独担保结果。
参考入口:Microi 官方中文接口引擎文档 。源码与测试分析对应本次工作区快照;本文由 AI 辅助创作并进行源码核对,封面及图卡背景由 AI 生成,解释图为人工编排的示意,测试图来自真实结果。
更多推荐




所有评论(0)