“你在实际项目里用过什么算法?”——这题是业务开发面试的分水岭。背过动态规划的人很多,在真实生产系统里跑过约束求解器的人很少。这个系列的主线机制到此收官,最后一块硬骨头给排产(APS):用 OptaPlanner 给注塑车间排产——几十张工单、十几台机台、几十副模具,算出一版”哪台机、哪副模、几点到几点”的预排方案,画成甘特图给排产员确认。
本篇讲四件事:排产问题的本质(一个活的 Job-Shop 调度问题)、链式建模怎么把车间现实翻译成求解器语言、三层打分与可配置权重的工程化、以及比算法本身更有面试价值的部分——为什么算出来的方案不敢直接下发,”辅助排产”的人机协同怎么设计。约束求解的教科书到处都有,车间里求解器怎么和人工编辑、在制保护、沙箱回滚共处,才是做过的人才讲得出的东西。
一、问题:排产到底在解什么
注塑车间的生产要素高度同质:塑料粒子加热、模具合模注塑、冷却出件。一台注塑机装上一副模具,就能生产某个产品——三个实体两两多对多关联(产品有可用模具集、模具能装到若干机台、某些物料只上特定机台组),这组匹配关系是排产的物理地基。
排产要回答的核心问题:N 张工单,每张放到哪台机、用哪副模、什么时间开始结束。约束和优化目标全部来自物理现实:相邻两单模具不同要停机换模、换材质要洗机、换颜色有清洗代价(浅转深便宜、深转浅贵,甚至有一张颜色切换代价矩阵);每张工单有交期,拖了要罚;负荷别压一台机;同一副模的单尽量连着干别反复装卸。
这是教科书级的 Job-Shop 调度问题——NP-hard,意味着不存在”算出最优解”的奢望,只有”给定时间内足够好”的工程目标。有意思的是这个模块的历史考古:第一代实现是自研遗传算法(染色体、适应度、基因的类都还在仓库里),后来整体迁到 OptaPlanner 约束求解器,GA 代码保留做演示——自研元启发式到成熟求解框架的迁移,本身就是一道面试题:什么时候该承认”求解器框架比我手写的好”。
“辅助”排产的定位一句话讲清:机器算、人改、确认才生效。排产员圈选待排工单,机器几秒到半小时算出一版方案;方案以甘特图呈现,人工拖拽微调;确认之前一切都在沙箱里,确认后才变成正式生产任务。为什么不全自动?因为车间现实永远比模型复杂——急单插队、模具突然在修、某台机老师傅说要照顾——模型的边界交给人的裁量。
二、建模:链式变量,车间现实的求解器方言
OptaPlanner 的建模三件套是规划解、规划实体、规划变量。这个模块里最值得讲的是链式变量的教科书落地:
每张工单(规划实体)有两个规划变量——选哪副模具(值域是产品的可用模具列表),以及排在哪台设备链的哪个位置。后者是关键:机台上正在生产的在制工单是链的头,后续每张待排工单通过一个”前驱”变量挂在链上;每个工单再用锚点影子变量沿链回溯出自己的所属设备(链结构变化时由变量监听器重算)。学过车辆路径问题的话一眼即懂——这是同一套链式建模,只是”客户点”换成了机台、”路径长度”换成了工时。工时的尺子也有讲究:数量 × 周期 ÷ 模穴数——同一百件活,四穴模的工时是单穴模的四分之一,求解器靠它衡量每副模具的效率。
还有一对容易混的时间口径:求解器内部用相对时间轴打分(交期延迟多少分钟),落库和甘特显示时再经工作日历换算成墙钟时间(跳过休息日与非工作时段)。打分口径和展示口径显式分离——面试聊排产时能主动指出这一点,是做过的人才有的细节。
链式建模的妙处值得停下来体会:排产结果对”顺序”极度敏感(相邻工单决定换模换色代价),如果建模成”工单选机台 + 独立开工时间”两个变量,求解器改一个时间不会自动顺延后续工单,约束会写得满地都是。链式结构把”机台上的一串顺序”作为一个整体对待,移动一个节点,影子变量沿链自动重算时间——顺序约束内建在数据结构里,而不是散落在打分规则里。这是”把领域结构编码进数据结构”的经典示范,也是我问候选人”影子变量解决什么问题”时想听到的答案。
在制保护也做在建模层,而且比”链头固定”更进一步——求解是注入式的:每台机的时间零点 = 当前时间 + 已排工单的剩余工时 + 换模延迟,已排和在制单不参与重排,新单只从链尾往后接。现场最怕”每次排产把我正在干的活重排一遍”,注入式起点从建模上杜绝了它。
打分用的是三层分数(硬/中/软)——这个系统从两层分数迁移到三层的历史在代码里留有大段注释残骸,迁移动机正是”交期违约和不可行解要分开度量”:硬层管物理可行(匹配关系违反直接不可行),中层管交期,软层管优化(换模次数、同模连续、换色、换材质、模具效率)。**分层的本质是让求解器知道”先活下来,再守时,再省钱”**。
三、打分:规则加密、权重开放
打分规则是 Drools 规则文件——而且有个罕见细节:规则源码是加密的,文件带专有魔数、配原生解密库才能加载。这是厂商保护核心资产的手段,副作用是排产逻辑对维护者半透明,改规则要走特殊流程。面试聊到这个,两面都要讲:保护有理(这是产品化模块的核心 know-how),但代价是可维护性——我维护它时的感受是”约束调试变成了黑盒里的考古”。
比规则本身更有工程价值的是权重的开放:软约束权重不是编译期常量,而是三张配置表——整体权重表(五列权重归一化成百分比)、约束明细表(每个约束单独配权重和是否必选)、用户级个性化表。求解结束后还会把每个约束的得分贡献落库,甘特图上有”约束匹配结果”面板——**排产员能看见”为什么这版方案排成这样”**:哪条约束扣了多少分、哪个机台因为换色矩阵被排后。可解释性在算法系统里不是锦上添花,是用户信任的前提——排产员不信任的黑盒方案,再优也不会被使用。
四、求解:终止时间的分档艺术
求解配置是教科书组合:构造启发式(FFD 递减首次适配)先出一版初始解,再进局部搜索——链上移动、交换、子链搬家,接受器用 Late Acceptance(和”历史若干步前比”而不是只和当前解比,逃离局部最优的经典手法)。求解器配置有三套按策略切换——基础版、材质颜色版、材质设备颜色版(考虑的约束逐版加多),求解预算分别三十秒和十秒:约束越重,越要压单轮时间。
真正见功力的是终止时间的分档:五张工单给十秒(三秒无改进即停),十张十六秒,二十张三十秒,五十张四十秒,一百张两百秒,再往上半小时封顶;手动模式由排产员定秒数(默认一百秒、上限二百秒)。另有一个”够好即收工”的兜底——软惩罚压到阈值以下就提前终止,不为最后几分软分烧完整个预算。这段代码经历过反复调参(仓库里留着多版本注释掉的参数组合),它背后的判断是:求解时间预算必须和问题规模、使用者的等待耐心对齐——排产员点”开始”后盯着进度条,十秒出可用解远比两分钟后出更优解有价值。
交互形态也值得讲:HTTP 同步阻塞求解,但求解器挂了事件监听器,每收敛到一个更优解就把分数实时推给前端(WebSocket),排产员看着分数从”-5hard/-200soft”一路爬向”0hard/-30soft”,还能随时点”暂停”提前终止取当前最优解。把黑盒计算过程可视化到这个程度,用户才敢等、敢信、敢按暂停。
五、人机协同:沙箱里的甘特图
这是本篇比算法更值钱的部分。求解之前有一道便宜的预校验:按松弛度(交期减最快生产时间)给工单排序,同时踢掉三类”烂单”——交期早于基准时间的、产品没关联模具的、可用机台全被过滤的。这一步的价值是排产失败的单在求解器之前就报了,不让求解器背锅也不浪费求解预算。
求解结果不直接进正式表,而是落进按操作者会话隔离的草稿表(会话标识加工厂双条件隔离,沙箱里新合成的计划行用负数临时 ID,确认时才换成真身)——每个排产员有自己的沙箱,互不干扰,随时整体撤销重排(撤销靠前后像快照表,undo/redo 全套)。沙箱还有生命周期:每小时定时任务清理隔夜草稿——半成品排产不过夜,防止没人认领的临时方案被误确认。甘特图上排产员有九种操作:跨机台拖拽、同机台调序、时间轴整体平移、锁定(时间锁/机台锁)、撤销重做……
关键设计是拖拽不重跑求解:人工移动一个条块,系统走手写的局部重排算子——只对受影响的链段重算时间(日历、换模、碰撞全算进去),增量写沙箱表。如果拖一下也触发全量求解,交互延迟会毁掉整个体验;如果局部重排不做约束校验,人工会把方案拖到物理不可行。自研约束类(产能、模具机台匹配、锁信息)就在这里把关——重活给求解器,微调给确定性算子,校验给约束类,三种计算形态各司其职。
与人工编辑共存的还有锁体系:OptaPlanner 原生支持”钉住”规划实体(pinned),生产路径用的是自研锁表——时间锁、机台锁、锁定人、锁定时间。两者并存的原因很实际:钉住是求解器视角的”这个实体别动”,锁表是业务视角的”这张单排产员张三锁了三号机到周五”——同一个保护语义的两种投影,前者给求解循环,后者给页面和审计。
最后的闭环:确认前系统会显式算出”本次排产会改动哪些工单的计划时间”(比较沙箱方案与正式计划,计划开始或结束差异达到五秒才算改动),排产员先看差异清单再确认;确认动作才把计划时间回写正式工单表——注意回写在工单服务里完成(排产模块连自己那份额外写正式表的方法都停用了,职责收口),后续由工单状态机(状态机篇)驱动下发。与 ERP 的回写同步走独立的数据同步链路异步进行——排产停摆不影响 MES 核心报工,这是全景篇讲过”旁路不反噬主路”的又一实例。
六、遗址考古:算法系统的真实肌理
这类模块的代码里全是现场的痕迹,挑三条:
调试探针遍地。约束调试期留下成串的”某台特定设备打印一句 find it”式的探针——排产员说”十三号机排得不对”,开发者没有约束级调试工具,只能在打分逻辑里埋点还原现场。约束求解系统的可观测性欠账,是这一类技术最真实的成本。
调参史即砍价史。终止配置在多个版本间反复横跳,每一段注释都是一次”求解太慢”和”解不够好”之间的重新议价。
架构的诚实。这个模块一个 Feign 调用都没有——读工单、设备、模具、库存全靠共享库直连,回写也直接写正式表。微服务是假的,单体是真的。为什么敢这么做?因为排产是典型的”读一切、算一把、偶尔写”的离线型计算,跨服务拉数据的延迟和一致性成本远大于收益。架构一致性在计算密集型模块面前要让步——这句话在面试里说出来,比”我们全线微服务”成熟得多。
七、面试视角:六个问题拆到底
Q1:你在项目里用过什么算法?
答:约束求解。注塑车间排产是 Job-Shop 调度(NP-hard),用 OptaPlanner 建模:工单是规划实体,模具选择和链上前驱是规划变量,时间沿链用影子变量推算;三层打分(硬=匹配可行、中=交期、软=换模换色等优化项),FFD 构造加局部搜索,终止时间按问题规模分档。我要强调的不是算法名词,而是它落在生产里的三个工程问题:求解时间预算、结果可解释性、和人工编辑的共存——算法只占这个模块三成工作量。
Q2:链式建模解决什么问题?
答:排产对顺序敏感,相邻工单决定换模换色代价和时间推算。链式结构把”机台上的一串顺序”编码进数据结构:改链上任何节点,影子变量自动沿链重算时间和切换判定,不用在规则里手写”改了一单要顺延后面所有单”。在制工单做成链头的问题事实,物理上不可移动——保护做在建模层而不是校验层。
Q3:分数为什么分三层?
答:硬/中/软对应”先可行、再守时、再优化”的语义分级:硬约束(匹配关系)违反的解不可行,求解器根本不接受;交期放中层——宁可软指标差一点也不能拖期;换模换色同模连续这些优化项放软层。这套系统从两层迁到三层的动机就是交期和软优化混在一层时,求解器会拿交期换换模次数。另外权重全部配置化加约束得分落库展示——可解释性是排产员信任的前提。
Q4:求解太慢或不收敛怎么办?
答:四招按序上:终止时间按工单数分档(小问题几秒、大问题给上限),保证响应可预期;无改进限时停(别在收益趋零时烧时间);用户可随时暂停取当前最优(最优解监听器实时推分数,用户看着收敛决定等不等);以及数据侧减规模——增量排产只排新单、机台过滤排除检修设备。我们没走”加大并发求解器”的路线,因为瓶颈不在算力在时间预算。
Q5:自动排产为什么不敢直接下发?
答:三个原因:模型永远有没覆盖的现场(急单、突发检修、师傅的经验偏好);错误下发直接指挥产线,代价不可逆;而排产员对方案的信任需要”看见并改过”来建立。所以形态是沙箱——求解结果进会话隔离的草稿表,甘特图人工微调(拖拽走确定性局部重排算子,不重跑求解),显式展示”本次会改动哪些工单”,确认才回写正式表。”自动算、人工定”不是保守,是对模型边界的诚实。
Q6:约束求解、规则引擎、手写贪心怎么选?
答:看解空间的形状。规则能直接推出唯一解的,规则引擎;解空间爆炸、约束互相牵扯、需要权衡的,约束求解——它卖的是”给定时间内的可行且较优”;规模小到贪心够用的,别上求解器,维护成本不匹配。我们的历史更有说服力:第一代自研遗传算法,效果和可维护性都不行,迁到 OptaPlanner——**算法选型要估的是”三年后谁来调它”**。
小结
- 排产是活的 Job-Shop:三实体多对多、换模换色有价、交期要罚——约束全部来自物理现实,不是产品经理编的;
- 链式建模把顺序编码进结构:影子变量沿链推算,在制保护做在建模层——好建模胜过一百条打分规则;
- 分数三层 = 先活下来再守时再省钱:权重开放配置、约束得分落库展示,可解释性是算法系统被信任的前提;
- 终止时间按规模分档:NP-hard 面前,”几秒内可用解”比”两分钟后更优解”工程价值更高;
- 沙箱 + 人工微调 + 确认回写:拖拽不重跑求解,重活给求解器、微调给确定性算子、校验给约束类;
- 架构一致性要给计算密集模块让路:读一切算一把的离线模块,共享库直连比七次 Feign 诚实;
- 算法系统的成本在可观测性:调试探针和调参注释是它最真实的肌理。
主线机制十一篇到此收官。下一篇是收官前的盘点篇:导出与归档的拼图整合(浓缩已有长文的机制要点)、全系列考点索引——然后按预告以”面试终局”收尾:项目介绍的 3 分钟与 30 秒话术、高频问题清单、反问环节。