SWE / Infrastructure · System Design

多租户CI/CD:Git事件、工作流状态机和故障恢复

绑定commit读取YAML,作业输出、状态和副作用需要分别处理。

题目重点:绑定commit读取YAML,作业输出、状态和副作用需要分别处理。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 内部API收到repo_id、commit_hash的push,YAML固定位置定义工作流;训练DAG作业,可退化成顺序。shell script已足够表达job,不强制引入镜像/Kubernetes。
  • 允许retry attempt,目标是一次逻辑状态提交加幂等副作用,不保证任意shell物理执行恰好一次。作业权限按租户隔离,commit不可变。

解题路线

  1. 入口按push event ID幂等,WorkflowRun绑定repo/commit/config_digest;从该commit读YAML解析并校验依赖环。Job(run,job_id,attempt,state,lease_epoch)与依赖剩余计数推进ready,run只由所有job最终态聚合。
  2. 调度器按tenant队列和资源配额发lease,worker领取后CAS queued→running,heartbeat续租。完成带attempt/epoch回报,旧attempt不能覆盖新attempt;lease过期→unknown/retry不是证明旧进程已经停止。
  3. shell部署或写数据库等副作用通过幂等key(run,job,operation)及目标CAS/version保护;不支持幂等的外部动作需保留unknown人工确认或事务协调。只把队列改成exactly once不能使脚本无重复执行。
  4. 每attempt日志分offset chunk存对象存储,worker流式上传后索引last_offset,用户SSE/WS从offset订阅,重连补读;状态小记录与大日志分开。job finished后仍等待日志flush标识,避免页面显示完成却少尾部输出。
  5. 租户隔离包含checkout token、secret、工作目录、CPU/内存/并发预算;可信脚本仍需防一个任务打满机器。不可变checkout防运行中main更新污染commit,缓存按依赖digest且验证租户可见性。

手动走一遍

push重复送两次:同event ID只建一个run;同commit另一次用户手动run可有新run ID。worker完成部署后回报丢失:job unknown,重新执行必须用相同逻辑operation key防重复部署;epoch只能防旧回报,不能防目标服务被调用。慢日志消费者重连用attempt+offset,不能把重试的新attempt接在旧attempt没有标识的日志尾部。

规模与取舍

Vjobs、E依赖调度元数据O(V+E),峰值worker需求由CPU/内存/作业时长而定。日志吞吐=并发job×每秒日志bytes;日志保留往往大于状态存储。关键路径决定run下界,租户公平会影响等待时间。

易错点与追问

容易失分的边界

  • 读取当前main而不是push commit使同一run不可复现。
  • running超时直接重启并宣称不会重复副作用。
  • 日志只保存在WebSocket连接内导致断线丢失。

继续深挖

  • running状态卡住时如何区分worker失联、长作业与上报故障?
  • 大日志怎样实行背压、截断与检索,同时不阻塞shell任务?
✓已复制微信号!