GitHub Actions 和 Workers 为什么会卡住发布:走出 Live Schedule 协调地狱

我想做的事其实很简单。就这么一件事。可是不知不觉,发布之前先开起了会。这次谁来负责? 用 Workers 还是 Actions? 预算还够吗? 上一把锁还有效吗? 标记写的是谁的名字? 已经重试几次了?

阅读功能说明

收听会朗读正文;速读会按顺序显示短语,速度可调。语言练习可对照已有的不同语言版本。收藏保存在本浏览器中,可从播放器的收藏列表再次打开。用打砖块来读会把正文的文字变成砖块,边打边读。

分享这篇文章

分享这篇文章

广告
广告

我想做的事其实很简单。

写好新文章,尽快发出去。

就这么一件事。

可是不知不觉,发布之前先开起了会。

这次谁来负责? 用 Workers 还是 Actions? 预算还够吗? 上一把锁还有效吗? 标记写的是谁的名字? 已经重试几次了?

文章在等。

系统也在等。

每个环节都觉得“这不是我的活”。

在公司里还能把人叫进会议室,而分布式系统只会悄悄停住。

这就是 Live Schedule 地狱。

1. 只想马上发布,结果搞成了“选负责人”

想让发布流程更稳,人自然会多加几道保险。

Workers 停了,就换 Actions。 Actions 预算紧,就换 Workers。 加一把锁,免得同一件事做两遍。 加一个标记,免得两边碰同一份工作。 失败了就数重试次数。 再加一个监视的角色。

一件一件看,都没错。

问题出在把它们一个接一个串起来的时候。

在问“这篇文章能发吗”之前,先要回答一个大得多的问题:“谁有资格发?”

最危险的是只剩负责人标记还留着。

Workers 那边的处理已经停了。 可记录里还写着“Workers 负责”。 之后 Actions 醒来,看到“Workers 负责”,就自己跳过。

没有哪个环节坏了。 也没有哪个环节在干活。

这就是分布式系统版的“负责人暂时不在座位上”。

2. 真正卡住的不是执行能力,而是协调

有一次运行,Actions 这边既有处理能力,也有预算。

主要的发布处理还是没有跑。

原因很简单:判断结果是“这件事归 Workers”。

这一点很重要。

不是 CPU 不够。 不是所有 API 都挂了。 也不是 Actions 启动不了。

问题出在协调,不在执行。

卡住的是“谁来做”的协调,而不是做事的能力。

这样一来,只改单个 bug 很难收尾。

改数据读取量。 改保存到 main 失败的问题。 改重试。 改负责人切换。 结果又留下旧标记。 接着又留下旧的锁。 监视的角色发现了问题,却没有发布的权限。

看起来像打地鼠,但地鼠都是从同一个洞里钻出来的。

管理这一层太聪明了。

3. schedule 是时钟,不是待办清单

GitHub Actions 的 schedule 很方便,不过官方文档也写明,负载高时可能延迟,负载足够高时,排队中的一部分任务甚至可能被丢掉。

所以 schedule 适合“大概这个时间叫醒我”。

可是,如果让它承担“这篇稿子一定有人接手,并且一直拿着直到成功”的责任,就不合适了。

时钟会响。

时钟不会替你拿着稿子。

消息队列则不同,它保存的是工作本身,还能处理失败:重试、延后,以及把一直失败的工作单独放进“失败收容区”(dead-letter queue)。

这里想说的不是“一定要引入新服务”。

而是把职责分开:

  • schedule = 定时叫醒的装置
  • 待处理清单 = 未完成工作的唯一依据
  • 执行者 = 真正干活的一方
  • 短期锁 = 防止两边同时干活的钥匙
  • 重试和失败收容区 = 失败工作的去处

让时钟同时兼任人事部、仓库和失物招领处,时钟多半会哭。

4. 以“还有什么没做完”为准,而不是“谁负责”

复杂的分工里,状态的中心很容易变成“负责人”。

可是发布流程真正想知道的是:

这篇文章发了没有。

几乎就只有这一点。

所以以待处理清单为准就够了。

新稿子一进来,就放进待处理清单。 开始发布时,上一把短期锁。 成功了,改成“已发布”。 失败了,退回待处理清单。 失败到规定次数,就移进失败收容区并发出警告。

“Workers 负责”“Actions 负责”只是辅助信息,不能变成永久的否决权。

即使写着“Workers 负责”,如果一段时间没有进展,别的执行者也可以接手。

这样,已经失效的负责人标记就不能把工作扣为人质。

5. 主线是“事件一来就启动”,schedule 只负责捡漏

想马上发布,却要等下一次定时,这就本末倒置了。

新稿子一到,就叫醒执行者。

把这个当作主线。

然后用 schedule 去捡那些过了几分钟到几十分钟还留着的旧待处理项。

也就是说,

事件驱动是主线,schedule 是保险。

这样平时很快。

就算漏掉了事件通知,schedule 之后也能捡回来。

反过来,如果把 schedule 当主线,一延迟,发布本身就晚了;再加上负责人判断,就会出现“醒了却什么都不做”。

定时到了。

检查也过了。

然后说一句“今天不归我管”就回家了。

那你来干什么?

6. 备用方案要做成“回收旧的未完成工作”,而不是“更换负责人”

设计备用方案时,常见的陷阱是正式把工作从 A 交给 B。

交接需要更新状态。

更新一旦失败,交接本身就成了故障。

其实可以更简单。

平时由 Workers 处理。 成功就结束。 失败,待处理清单里就还留着。

Actions 这类备用执行者定期查看,如果

  • 有待处理项
  • 已经超过一定时间
  • 没有有效的锁
  • 自己能执行

就把它接过来。

不需要“Workers 交接过来的证明”。

要看的是工作还没做完这个事实。

这种做法里,成果有没有出来,比分工表是否一致更重要。

对文章发布系统来说,这样更自然。

7. Live Schedule 不必丢掉,但别把它做成维生设备

Live Schedule 本身没有问题。

有人在看的时候,调整优先顺序。 调查异常。 想出改进方案。 加快修复。 放进新的计划。

这些用途它都很擅长。

问题是,它一停,文章工厂也跟着停。

Live 应该是加速器,不能成为一处出错就全盘停摆的单点。

即使没有 Live,

新稿子 → 待处理清单 → 执行者 → 检查 → 发布

也要能自己转起来。

Live 来了,就更快。

不在也不会死。

就像不能把人的心脏接到“负责人有没有在 Slack 回复”上一样。

8. 把完成标准从“聪明的系统做完了”改回“放进去一篇,出来一篇”

这类系统,只要想改,可以改一辈子。

公平分配负责人。 优化免费额度。 防止重复。 预测预算。 优先级。 监控。 自动恢复。 数据分析。

每一样都很有意思。

而全做一遍的话,文章就发不出去了。

所以最初的完成标准可以粗糙得多。

放进一篇新稿子,不做任何特别操作,就会发布一篇。

失败了,也留在待处理清单里。 别的执行者会来接手。 只有反复失败的才交给人处理。

等这条简单的直线稳定了,再把各种优化加回来。

明明只是“想快点发新文章”,却不知不觉自己造起了类似分布式共识的东西。

总有这样的日子。

但是有办法收场。

砍掉复杂性。

发布系统的价值,不在于选负责人选得多漂亮。

而在于文章发到外面去。

结论

Live Schedule 地狱的根源,并不是 schedule 本身。

而是让“负责人的状态”压过了“工作还没完成的状态”。

要把设计改回来,做到这样就够了:

  • 以待处理清单作为未完成工作的唯一依据
  • 事件一来就立即启动
  • 锁要短
  • 备用执行者去回收旧的待处理工作
  • schedule 当作保险
  • 失败到规定次数的隔离进失败收容区
  • Live 只当作可有可无的加速器

与其要聪明的工厂,不如要不停工的工厂。

先从这里开始。

资料


分享这篇文章

如果这篇文章对你有帮助,可以支持本站。 支持本站

广告
广告
广告

再来一篇?有没有好玩的?

读完顺便看看:几篇相近的,还有几篇完全不同但很有意思的。

  1. 相近的话题为什么我动不动就哭?“泪失禁体质”不等于脆弱,照样能把工作做好
  2. 为什么“身为丈夫”“身为男朋友”“身为家人”总会吵起来
  3. 完全不同,但很有趣为什么我看不进“共鸣系”动画
  4. “等我确定时间再说”,为什么总在前一天才回消息?
  5. 9度500mL的罐装酒真的只算“一罐”吗?相当于2.6罐啤酒
  6. “海鳗军团水泥管水槽”为什么这么好看?

今天读这篇

每一篇都回答读完本文后常有的下一个问题。

浏览全部文章更多「工作」文章

查找其他文章

所有文章

Mendoi-chan

本站运营者

Mendoi-chan

把工作与日常生活中的麻烦整理成清晰的结构和下一步行动。