图 1:本文封面,主题为弹性集群上的批量任务调度(经典题解析)。
想象面试现场:有一百万条消息要发,集群里的机器数量不固定——随时可能扩容加进来几台,也可能缩容被回收几台。问你怎么设计,让任务尽快跑完,还不能漏发、不能重发。这道题属于经典题解析(历史题目复盘,见文末来源说明),它考的不是某个中间件,而是你能不能把「节点不固定」这个约束转化为调度设计的出发点。
第一步:审题,点破核心难点
如果机器数量固定,问题很简单:任务均分,每台领走一份跑完收工。难就难在节点是动态的:均分之后新加的机器闲着没事干,被回收的机器手上的活直接丢了。所以答题的第一句话就该是——静态均分不可行,调度必须以「分片」和「动态领取」为中心来设计。面试官真正想听的,是你对「不确定的劳动力」这个前提的应对思路,而不是某个具体组件的名字。
第二步:基础答案,分片加拉取
图 2:弹性集群上的任务分片与调度架构。
核心设计四件套。一是细粒度分片:把一百万条任务切成几千个带编号、带状态的小分片,状态只有三种——待领取、进行中、已完成。二是共享任务队列:分片统一入队,不预先绑定任何机器。三是 worker 主动拉取:空闲节点自己来领分片,而不是调度器推送。拉取式是点睛之笔:机器性能有差异时,快机器自然多领,慢机器少领,负载均衡是免费的;新扩容的节点注册之后立刻能领活,不需要任何再均衡。四是进度汇报:每完成一个分片就汇报落库,整体进度一目了然。
分片粒度是个权衡:切得越细,单个分片失败重做的代价越小、负载越匀,但状态管理的开销越大;工程上常按「单片几分钟跑完」来定大小。另外一个常见追问是队列放哪:分片状态必须落在共享存储里(数据库或分布式队列),而不是某个调度进程的内存中——否则调度器自己挂掉,整个任务现场就丢了。
第三步:进阶设计,补上三块短板
追问一:worker 领到分片后宕机怎么办?答心跳加重派。worker 执行期间周期性上报心跳,调度器发现某分片心跳超时,就把它标记回待领取,让别的节点重新执行。这与 Kubernetes Job 的设计一脉相承——Pod 失败会被重建重试,直到达到指定的成功次数。
追问二:重派会不会导致重复发送?宕机前可能已经发出去一半。答案是幂等:每条任务带唯一标识,发送前查一下去重表或用「标记已发」的原子操作兜底,宁可多查一次,不可重发一条。不漏靠重派,不重靠幂等,两者必须同时成立。
追问三:缩容时怎么处理?答优雅退出:要下线的节点先停止领新分片,把手上的活干完再走人;实在等不及,就走心跳超时那套重派逻辑兜底。到这一步,扩缩容两个方向就都闭环了:扩容靠「注册即领」立即生效,缩容靠「停领加兜底」安全退出,调度主流程完全不需要感知节点数量的变化——这正是「尽快跑完」的底气:任何时候算力增加,吞吐立刻线性提升。
第四步:工程应用与 AI 关联
这套模式在 AI 工程里无处不在:给几十万条语料批量跑推理、离线生成全量 embedding、把标注任务分发给众包节点——全是「弹性 worker 池 + 任务分片 + 失败重试」的同款问题。面试里答出这层迁移,说明你不只是背了方案,而是真的见过这类系统。
三个易错点
一是静态均分,完全无视节点动态变化;二是只讲分片不讲失败处理,宕机丢任务;三是重试不做幂等,重发造成线上事故。避开这三点,答案就立住了。
答题节奏建议
半分钟点破「节点不固定所以静态均分不可行」,两分钟讲分片加拉取的主方案,然后主动把心跳重派、幂等、优雅缩容三块短板补齐,最后用一句 AI 工程同款场景收尾。
如果分片之间还有依赖顺序(比如先发汇总再发明细),你会怎么改造这套拉取式调度?欢迎讨论你的设计。
参考资料
- Job(Kubernetes 官方文档:Job 运行一次性任务至完成,Pod 失败时自动重建重试直至达到成功次数,支持多 Pod 并行与基于工作队列的并行处理模式) — https://kubernetes.io/docs/concepts/workloads/controllers/job/