这些年做 AI 平台架构,最让我头疼的不是模型本身,而是平台底层那堆绕不开的横切问题:任务怎么排、权限怎么控、结果谁来判定、效果怎么衡量、出了问题怎么追溯。单拎出来每一项都有成熟方案,可一旦放进同一个系统里,它们就会互相拉扯,最后变成一坨没人敢动的代码。这个 55873 版本的项目,就是我们在第三次推倒重来时定下来的方案——一套五引擎微内核架构。调度、安全、研判、评测、存证五个引擎,各自独立演进,通过一个极薄的内核完成协作,全程可以工程化验证。这篇文章把整套设计拆开讲清楚,包括为什么选微内核、引擎之间怎么通信、每个引擎内部到底做了什么、以及我们踩过的坑,适合正在设计或重构 AI 平台的架构师和后台开发参考。
1. 内容整体设计与思路拆解
1.1 为什么是“微内核 + 五引擎”,而不是一个大单体
项目启动时,我们团队只有八个人,要同时支撑模型推理、数据标注、业务审核、对外 API 多条线。第一版架构是典型的大单体:一个服务进程里塞了调度、权限、审核、统计、日志全部逻辑,代码仓库三个月后就膨胀到十几个模块互相 import,改一个权限模型要重新回归所有链路。压力测试一到,某个模型推理超时就能把整个平台拖垮。
转机出现在我们重新审视内核设计原则的时候。微内核的核心思想,是把“机制”和“策略”分开:内核只负责最基础的机制——引擎注册、消息路由、生命周期管理、共享状态访问,而具体的策略(比如任务怎么排队、规则怎么判定、指标怎么计算)全部下沉到各自的引擎里。这样做有三个直接收益:第一,每个引擎可以独立发布、独立扩容,谁出问题就隔离谁;第二,新引擎接入不需要改内核,只实现一套注册协议就行;第三,测试可以针对单引擎做深度验证,不用每次都在全链路里挣扎。
对比一下单体方案,五引擎微内核的代价是引入进程间通信和分布式事务复杂度。但对我们这个场景——任务队列、规则判定、效果评测、证据留痕,每一块的状态边界本来就清晰,用消息驱动反而比共享内存更自然。后来我们把“55873”定为项目代号,其实就是五引擎(5)在 873 号迭代上完成首次全链路联调的意思,这个版本因此成了我们内部所有架构讨论的锚点。
1.2 引擎边界划分的三个原则
边界划得对不对,直接决定这套架构能不能活过三个迭代。我们当时定了三条硬性规则,后来发现这三条规则能过滤掉绝大多数设计分歧。
第一条,按生命周期划分。凡是跟单个任务从创建到结束的流转相关的,归调度引擎;凡是跟请求进来之后身份和权限校验相关的,归安全引擎;凡是跟结果语义判断相关的,归研判引擎;凡是跟批量验证和能力度量相关的,归评测引擎;凡是跟事实固化、防抵赖相关的,归存证引擎。生命周期不同,数据的读写模式就不同,混在一起必然互相拖累。
第二条,按数据所有权划分。每个引擎只对自己拥有的数据有写权限。比如调度引擎维护任务状态机,评测引擎维护指标结果,存证引擎维护证据链。其他引擎需要这些数据时,只能通过内核提供的只读接口去拿。这条规则避免了我们以前常见的“为图方便直接在别的模块表里加字段”的恶习。
第三条,按故障半径划分。一个引擎挂了,不能带着整个平台一起挂。研判引擎依赖的模型服务超时,调度引擎要能做熔断降级;存证引擎写入失败,任务流转不能被阻塞。边界画清楚之后,每个引擎的降级策略就变成了一等公民,而不是事后补丁。
这三个原则落实到代码上,就是一套契约:每个引擎向内核注册时,必须声明自己提供的 Capability(能力点)、订阅的 Event(事件)以及允许被调用的 Command(命令)。内核不关心引擎内部怎么实现,只按契约做路由。
2. 核心细节解析与实操要点
2.1 内核层:极薄但绝不可省的三件事
微内核最容易被误解的地方,就是以为内核越少越好。它确实要薄,但有三件事绝对不可省:引擎注册表、事件总线、共享状态服务。
引擎注册表负责维护所有引擎的元信息,包括引擎 ID、版本号、健康状态、能力清单。我们给每个引擎规定了统一的生命周期接口:Init、Start、Stop、HealthCheck。内核启动时按依赖顺序拉起引擎,运行期每五秒做一次健康检查,连续三次失败就把该引擎标记为 Degraded,并把依赖它的路由自动切到降级策略。
事件总线是引擎之间协作的主通道。所有跨引擎的数据流转都通过事件完成,比如调度引擎发出TaskCreated事件,研判引擎订阅后启动判定,存证引擎订阅后开始记录存证条目。事件总线我们基于消息队列实现,支持发布订阅和点对点两种模式,关键事件走持久化 topic,普通事件走内存总线,避免所有协作都落盘把吞吐拖垮。
共享状态服务解决的是“引擎之间需要读同一份配置”的问题。我们不允许引擎直接读对方的配置表,统一放到一个带版本号的配置中心里,引擎通过内核 SDK 拉取,配置变更通过事件广播。这个设计在排查问题时帮了大忙——每次线上事故,我们都能明确说出当时每个引擎加载的是哪个配置版本。
2.2 引擎间通信协议与数据格式约定
引擎间通信如果各说各话,微内核就会退化成分布式大泥球。我们从一开始就约定了统一的协议信封,所有事件和命令都包在同一套结构里:{ trace_id, event_type, payload, timestamp, producer, version }。
trace_id 是全链路追踪的命根子。一次完整的业务请求,从进入 API 网关开始生成 trace_id,经过调度、研判、存证各个引擎,所有日志和事件都带这个 ID。排查问题的时候,用 trace_id 一把梭,把整条链路的日志拉出来,问题在哪一目了然。我们后来接了一整套日志检索系统,直接按 trace_id 索引,排障效率提升了数倍。
payload 的格式合约我们选择用 JSON Schema 约束,每个事件类型都有对应的 schema 版本。引擎升级时,如果新版本要改事件结构,必须向后兼容;破坏性变更必须先用新版本号发一个新事件,老事件保留一段时间再下线。这个约束让六个团队(加内核组共六组)并行开发了三个多月,没有因为协议问题互相阻塞过一次。
2.3 微内核实现时的三个“千万别”
第一,千万别把业务规则写进内核。我们犯过一次错:为了让某条审核链路走捷径,在事件总线里内置了一段硬编码的分流逻辑。结果那个业务需求一变,内核代码跟着改,引发了一连串回归测试失败。后来我们把所有规则类逻辑全部下沉到引擎,内核只做机械的路由和转发。
第二,千万别让引擎直接依赖彼此的 SDK。引擎之间只通过消息和接口通信,不共享代码库。一开始有人图省事,直接引用了另一个引擎内部的工具类,结果对方改了个函数签名,这边编译就崩了。断开代码依赖之后,各引擎真正做到了独立发版。
第三,千万别忘了超时和重试语义。引擎调用不可能是本地函数调用,IO 超时是常态。我们给所有跨引擎调用强制配置了超时时间和最多三次重试,并且重试必须保证幂等——同一个事件被重复投递,不能产生重复的任务或重复的存证条目。幂等这件事靠事件携带的event_id去重实现,消费端做一次查重再处理。
3. 实操过程与核心环节实现
3.1 调度引擎:任务状态机与优先级策略
调度引擎是整个平台的主动脉,所有业务请求最终都变成一个个任务,在状态机里流转。我们定义的核心状态是:Pending、Ready、Running、Succeeded、Failed、Canceled、Retrying。每个状态之间的迁移都有明确的事件触发,比如 Pending 收到资源可用信号后迁移到 Ready,Running 收到执行完成信号后迁移到 Succeeded。
注意:状态迁移必须是幂等的。我们的实现里,每个任务都带版本号,更新状态时用乐观锁做 CAS(Compare And Swap),只有版本号匹配时才允许迁移。这避免了并发场景下两个执行器同时确认同一个任务导致的重复处理。
优先级策略我们采用“多级队列 + 权重轮转”。任务按紧急程度分为 P0、P1、P2 三档,每档一个队列。调度线程从队列里取任务时,按 5:3:2 的权重比例轮转,既保证紧急任务优先,又避免低优先级任务饿死。资源分配上,每个任务声明所需 CPU、内存、GPU 配额,调度器基于当前集群剩余资源做可行性检查,资源不足的任务留在 Pending 状态并触发资源等待事件。
可工程验证是调度引擎设计的主线。我们内置了一套确定性调度测试框架:通过录制固定的任务到达序列和资源快照,断言调度决策是否符合预期。比如“100 个 P1 任务和 10 个 P0 任务同时到达时,P0 任务的平均启动延迟小于 500ms”这条指标,被固化成 CI 的可执行断言,任何一次调度策略改动都要跑过这套测试才允许合并。
3.2 安全引擎:认证、授权与审计的三层防线
安全引擎不是简单的登录鉴权,它在我们的平台里负责三层防线。第一层是认证,统一接入 OIDC 协议,所有内部服务和外部 API 都通过 token 验证身份。第二层是授权,我们实现了基于 RBAC + 资源标签的混合权限模型:角色决定操作权限,资源标签决定数据范围,两者取交集才放行。比如“运营审核员”这个角色可以调用审核接口,但它关联的数据标签限定在“非金融类”业务,那么金融类任务它连列表都看不到。
第三层是审计,所有敏感操作(创建任务、修改策略、导出数据、人工复核)都会被安全引擎拦截记录,生成一条审计事件,同时发布到存证引擎做持久化。审计事件包含操作者、操作类型、资源 ID、结果、时间戳、trace_id,形成完整的操作时间线。这个设计在上线后被合规部门表扬过一次,因为任何数据泄露问题都可以快速定位到具体的操作人和操作链路。
安全引擎的策略下发同样是动态的。我们把权限规则存成可版本化的策略配置,通过配置中心热更新,引擎每分钟拉取一次变更。紧急封禁某个异常账号时,只需在管理端操作,一分钟内全量生效,不用重启任何服务。
3.3 研判引擎:规则与模型双轨判定
研判引擎负责对任务结果做语义层面的判断,这是我们平台最核心的业务能力之一。它的工作方式可以概括为“双轨制”:一条轨是确定性规则,另一条轨是机器学习模型,两条轨的输出通过仲裁模块合并。
规则轨用了一套可视化规则编排器,业务人员可以直接在界面上拖拽条件节点和动作节点,生成 JSON 格式的规则集。规则引擎执行时按节点优先级逐条匹配,命中规则后输出判定结果和命中的规则 ID。这套东西对业务人员极其友好,上线后很多判定逻辑的调整都是运营同学自己完成的,不再每次提需求排队等开发。
模型轨则负责处理规则覆盖不了的模糊场景。我们部署了几个分类模型,输入是任务的特征向量,输出是各个类别的概率分布。模型轨的结果不是硬判定,而是产出“风险分数”和“置信度”,交给仲裁模块处理。
仲裁模块有一套量化的融合策略:当规则轨命中和模型轨的置信度都超过阈值时,取较高优先级的结果;当两者冲突且置信度都不足时,标记为“疑似冲突”,进入人工复核队列。这条策略解决了我们在早期“完全信规则”或“完全信模型”两个极端之间摇摆的问题——实测下来,冲突样本只占总量的 3% 左右,但恰恰是这 3% 出了大量真问题。
3.4 评测引擎:指标计算与自动化回归
评测引擎解决的是“这个模型或规则改完之后,到底是变好了还是变坏了”的问题。我们不接受拍脑袋式的评估,所有变更上线前必须跑评测。
评测引擎的核心资产是数据集。我们把数据集按场景分类,每类数据集包含输入样本、预期结果和权重。数据集管理支持版本化,每次评测都记录用的是哪个版本的数据集,避免“同一份数据悄悄被改了导致评测结果失真”的乌龙。
指标计算支持分类指标和回归指标两大类。分类指标包括准确率、精确率、召回率、F1、AUC;回归指标包括 MAE、RMSE 等。除了整体指标,我们还按业务维度做分片统计,比如按来源渠道、按任务类型、按时段,防止“总体指标好看、局部一塌糊涂”的情况被掩盖。
自动化回归是评测引擎最实用的功能。每次研判引擎的规则集或模型版本更新,CI 流水线会自动触发一轮评测,把新版本的指标和基线版本的指标做对比,生成差异报告。差异报告里用红绿色标出显著变化的指标,超过阈值则直接阻止上线。这个机制让我们避免了至少三次“优化了个案、伤害了全局”的发布事故。
3.5 存证引擎:可追溯、防篡改的证据链
存证引擎是最后一个上线的引擎,但它在架构里的重要性一点都不低。它的目标很简单:平台上每一个关键动作和判定结果,都要留下不可抵赖的证据,事后可以完整回溯。
实现上我们用了哈希链加签名的方式。每一个存证条目包含事件哈希、事件内容摘要、上一节点的哈希值、时间戳。新条目追加时,把上一个条目的哈希纳入当前条目的计算范围,形成一条逐块关联的链。任何一条历史记录被篡改,都会导致后续所有节点的哈希验证失败。存证条目还附带引擎私钥的数字签名,防止伪造来源。
读取侧我们提供验证接口:给定任意一条记录 ID,可以取出整条链到该记录的路径,逐节点验证哈希和签名,并输出验证结果。这套机制被用在了两个关键场景:一是人工复核操作的留痕,二是模型判定结果的存证。用户要是对平台判定不服,可以申请调取存证记录,看到完整、可验证的处理链路。这一块上线后,内部争议处理时间明显缩短了。
提示:存证引擎不做业务查询,只做顺序追加和验证。我们特意把它和业务数据库隔离,存证数据存在独立的存储集群上,并且定期做冷备归档。这样即使核心业务库出了问题,证据链依然是完整的。
4. 常见问题与排查技巧实录
4.1 疑难问题速查表
下面这些问题是我们在 55873 这个版本迭代里真实遇到过的,整理成对照表,方便大家直接定位。
| 问题现象 | 可能原因 | 排查路径 | 解决方案 |
|---|---|---|---|
| 任务卡在 Pending 不执行 | 资源配额不足或调度死锁 | 查调度引擎的资源快照日志,确认集群余量 | 调整队列权重,或给 P0 队列预留独占资源 |
| 事件重复触发导致重复任务 | 消费端未做幂等去重 | 按 event_id 检索消息队列消费记录 | 消费端增加幂等表,同一 event_id 只能成功一次 |
| 权限变更不生效 | 安全引擎策略缓存未刷新 | 查配置中心版本号和引擎启动时间 | 检查热更新通道,必要时触发一次手动刷新 |
| 模型评测通过但线上效果差 | 数据集与线上分布不一致 | 对比线上样本和评测样本的特征分布 | 增加线上采样回流到评测数据集的机制 |
| 存证链验证失败 | 某节点数据被外部修改 | 定位第一个验证失败的节点,查修改时间 | 排查是否有绕过存证引擎直接操作数据库的脚本 |
| 引擎间调用超时 | 下游引擎 IO 阻塞或线程池耗尽 | 查下游引擎的线程池指标和 GC 日志 | 增加熔断器,超时快速失败并触发降级策略 |
4.2 实践中最容易忽视的三个坑
第一个坑是事件总线的消息堆积与背压。一开始我们认为消息队列吞吐足够高,就没设计背压机制。结果一次大促活动涌进来批量任务,研判引擎消费速度跟不上,消息积压了几百万条,等消费完已经是两个小时之后,任务时效完全不可控。后来我们在事件总线上加了队列长度阈值,超过阈值就把新事件降级为同步调用加快速失败,同时触发调度引擎的限流。这个改动让系统在大流量下从“慢慢拖死”变成了“快速降级、快速恢复”。
第二个坑是评测数据集的污染。我们曾把线上真实样本直接追加进评测数据集,没有做去重和标注清洗。结果模型在评测时表现出幻觉式的提升——因为它“见过”了部分测试样本,模型学会了死记硬背而不是泛化。从那以后,评测数据集与线上样本严格隔离,线上回流数据必须经过匿名化、去重和独立标注审核才能进入评测集。
第三个坑是存证引擎被当成业务数据库用。有人觉得存证引擎有哈希链、很安全,就想把业务查询也放进去。我们花了一个迭代的时间纠正这个思路:存证引擎是 Write-Once-Read-Many 的追加型存储,没有任何更新操作,用它做业务查询只会引入不必要的复杂度,还拖慢写入吞吐。最后我们把查询需求全部收敛到业务侧的只读副本,存证引擎只保留一条 RESTful 验证接口。
4.3 排查链路 trace_id 的落地技巧
trace_id 这套机制听起来简单,真正落地时有很多细节。我们要求所有引擎的日志框架自动注入 trace_id,从消息 Header 里取,取不到就生成新的并向上传递。框架层面统一处理,业务代码完全无感。
还有一个技巧是给每个引擎的输出日志增加“阶段标签”,比如调度引擎输出[SCHED]、研判引擎输出[JUDGE]、存证引擎输出[LIS]。配合 trace_id 检索时,按阶段标签过滤,一眼就能看出任务卡在哪个阶段。后来做性能剖析时,这些阶段标签直接用于统计每个环节的耗时分布——从调度到研判的中位耗时、P95 耗时,全部量化,哪里是瓶颈一目了然。
5. 工程验证体系与扩展思考
5.1 三层验证:单元、联调、混沌
整个架构强调“可工程验证”,落到实操上就是三层验证体系。第一层是引擎内单元测试,每个引擎的业务逻辑必须配套覆盖率达标的核心用例,尤其是状态机和规则判定这类容易出边界问题的地方。第二层是契约联调测试,基于每个事件的 JSON Schema 自动生成 mock 数据,验证引擎之间按契约正确交互。第三层是混沌测试,我们每周挑一个低峰时段,随机杀掉一个引擎实例或注入网络延迟,验证平台能否按预期降级和恢复。
这三层测试全部接入 CI/CD 流水线,任何代码变更必须在三层都通过后才能发布。这套体系的价值在一次线上事故中体现得淋漓尽致:存证引擎所在节点磁盘写满,按照预设的降级策略,平台自动切到“存证降级模式”,业务任务正常流转,存证条目先落缓存,磁盘恢复后异步补写。整个过程没有任何人工干预,业务方零感知。
5.2 后续扩展:新引擎的接入路径
五引擎不是上限,而是一个可扩展的框架。如果要接入新的能力,比如“成本核算引擎”或“交互式推理引擎”,路径是固定的:定义生命周期接口、声明事件订阅和命令、注册到内核、通过契约联调测试。这样做的好处是新增能力不需要改动其他引擎,内核的路由表自动感知新引擎的能力清单。
我们已经在规划两个扩展方向:一是把评测引擎和调度引擎打通,形成“评测驱动调度”的闭环——线上效果变差时自动触发调度降级;二是给存证引擎增加多副本共识能力,解决极端情况下单点存储的可用性隐患。这些扩展都依赖五引擎微内核这套底子,底子稳了,上面怎么长都不乱。
5.3 我觉得最值回票价的设计
坦白说,这套架构里最能扛住时间考验的,不是某个具体引擎的功能,而是引擎间的契约约束。六个人、五个引擎、三个多月的并行开发期,没有因为模块间耦合吵过架,靠的就是那套事件 Schema 和幂等约定。技术方案会迭代,但边界清晰、协议先行、测试证明这套工作方式,是我会带到下一个项目里去的。
如果你正在设计 AI 平台,我的建议是不要一开始就追求引擎数量多,先明确你的核心生命周期是什么,把最主干的三四个引擎跑通,再逐步扩展。微内核最忌讳空有其壳,内核薄但契约不全,还不如老老实实写单体。能通过自动化测试证明每一步都正确,这才是工程上真正值得投入的方向。