先说结论:一套智能供应链AI预测系统,从线上跑不起来到扛住大促峰值,再到把运维成本砍掉一多半,这中间经历的不只是技术栈的更换,更多的是对架构本质的反复思考。如果你正准备自建预测系统,或者已经在微服务里挣扎,这篇文章里记录的这些选择和踩坑,应该能帮你省下不少弯路。
我一直负责公司供应链侧的AI预测平台。这个平台的核心任务不复杂:基于历史订单、库存水位、促销计划、天气、甚至外部市场行情,预测未来若干天的销量和安全库存线,给采购、补货、调拨提供决策依据。但就是这套听起来很简单的系统,架构上前后经历了三次大改。
1. 单体阶段:AI预测系统最初的样子和它的承载极限
1.1 第一版单体架构的选型逻辑
第一版系统上线时,业务刚起步,预测链路砍得很短。每天凌晨定时跑批,从数据仓库拉过去90天的订单明细,经过特征工程生成训练样本,更新模型,然后对全量SKU做未来14天的预测,最后把结果写回业务库,供采购后台查询。
当时的技术选型很直接:Spring Boot + MySQL + Redis,模型训练用独立的Python进程,预测结果通过消息队列异步写回。整个系统就是一个单体应用,预测引擎以本地函数库的方式嵌在Java服务里,训练脚本独立部署在一台Cron服务器上。
那时候这么选是合理的。团队只有三个人,业务方要的是"先跑起来看效果",不是要一个分布式标杆。单体架构下的优势非常明显:应用内部直接调用,不需要考虑网络开销;MySQL事务保证数据强一致;日志集中在一个应用里,排查问题不用跳来跳去;部署只需要打一个Jar包丢到服务器上。
这个阶段,整个系统的QPS很低。预测是离线批任务,每天凌晨触发一次,业务端的查询接口峰值也就每秒几十次。单机4核8G就能扛住,Redis里缓存的热点SKU预测结果,命中率能到85%以上。
1.2 单体内部的功能模块划分
虽然是单体,但代码层面必须保证模块边界清晰,不然后面重构就是灾难。我当时的做法是严格分四层:
- 数据接入层:负责对接数据仓库、业务库、外部API,统一做数据清洗和对齐,输出标准化的事件流。
- 特征平台层:把原始数据转换成模型可用的特征,包括滞后特征、滑动平均、节假日哑变量、促销折扣因子,所有特征都有版本号和生成时间戳。
- 模型服务层:封装多种预测算法,包括时序的Prophet、LightGBM回归、以及后续引入的DeepAR,统一走模型注册和版本管理。
- 结果分发层:把预测结果按照业务线拆分,写回业务库或者推送下游系统。
分层的好处是,当预测效果不准需要调特征时,不用动模型服务代码;当模型版本要回滚时,也不用碰数据接入逻辑。这个模块边界在单体阶段被保住了,后来拆微服务时,基本上就是按这条线切开的。
1.3 单体架构的承载极限:不是性能先崩,而是协作效率先崩
很多人以为单体架构是被高并发打垮的,但我们这个场景恰恰相反,性能和资源一直够用,先撑不住的是开发和协作效率。
随着业务扩张,预测不再只有销量预测,还增加了断货风险预测、滞销预警、动态安全库存计算。每个新场景都要加定时任务、加数据源、加模型脚本。单体应用里,Job越来越多,每次发布都得把所有任务停掉再重启,一个任务的代码有问题,整个应用启动失败,所有预测全部停摆。
更头疼的是资源隔离问题。深度学习模型(DeepAR)训练时要把内存顶到十几个G,而线上预测服务却只需要2G内存,两者挤在一个进程里,互相争抢资源。有一次模型训练脚本内存溢出,直接把Java服务拖垮了,采购后台整整四十分钟查不到预测结果,业务电话被打爆。
1.4 单体阶段复盘:哪些经验迁移到了后面
单体阶段不是白干的,有几样东西一直沿用到后面:
- 特征版本管理的思想。每个特征都记录生成逻辑、时间窗口、数据源版本,这让后来做特征服务时非常顺畅。
- 模块边界的分层。预测系统天然分四层,拆服务时几乎不用重新设计接口。
- 数据预处理的标准化。所有数据源统一清洗成"SKU-时间-事件"的三元组结构,后面接实时数据流时直接复用这套结构。
这一阶段最大的教训是:会有一个明确的时间点,让你意识到单体已经不适合了。当你发现自己为了"不动整体代码"而开始写各种开关和if-else分支时,就是该拆的时候了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 微服务拆分:从一次库存预测事故开始的架构重构
2.1 触发拆分的导火索:一次预测数据错乱
直接让我们下决心拆分的,是一次数据错乱事故。
当时为了支持一款新品的上市预测,需要紧急接入一个新数据源。因为是在单体应用里修改,变更影响范围很难控制,数据接入层的改动不小心影响到了特征计算模块的时间窗口逻辑。结果是,整整三天内,所有参与促销的商品预测销量都比实际低了30%,补货计划按错误预测执行,仓库备货不足,大促前最走量的一批SKU直接断货。
复盘时,本质原因很清楚:数据接入、特征计算、模型推理耦合在一个进程里,任何一环的改动都可能影响全局。而且当时的CI流程也不完善,没有针对特征计算的独立测试集,问题直接被带到了生产。
这次事故之后,我们正式启动了微服务化改造。
2.2 服务拆分背后的逻辑:不是按技术分层,而是按变更频率和资源特征
很多团队拆微服务,喜欢按技术分层拆,比如"数据服务""计算服务""接口服务",这种拆法看起来规整,但实际用起来很别扭,因为每一个需求变更都要跨多个服务协调。
我们换了个思路:按变更频率和资源特征拆。
- 数据接入服务:负责所有数据源的拉取、清洗、对齐。变更频率最高,因为业务方经常加数据源、改口径。
- 特征平台服务:负责特征计算和特征存储。资源消耗平稳,但需要大量内存做特征缓存。
- 模型训练服务:独立拆分,资源消耗大且有明显的波峰波谷。训练任务跑在独立的GPU服务器池,通过队列接收任务。
- 预测推理服务:对外提供实时和批量预测接口,要求高可用,需要弹性扩容。
- 结果分发服务:把预测结果推送到下游业务系统(采购后台、供应链中台、大屏),支持多种协议。
这套拆分方案,本质上把"会互相影响的资源"隔离了,同时也把"经常一起变更的代码"聚合了。训练服务要吃满内存就让它吃,不影响推理服务对外响应;数据接入层要频繁发布,也只需要重启自己,不会拖垮整个系统。
2.3 微服务落地的具体过程:网关、注册中心、配置中心,一个都不能省
拆分不是把代码拆开就行,配套基础设施必须跟上。
- 网关层选了Spring Cloud Gateway。所有内部服务不直接暴露,统一走网关转发,顺便做鉴权和限流。对外只暴露预测查询和批量结果推送两类接口。
- 注册中心用的Nacos。服务实例上下线能秒级感知,配合网关的负载均衡,某个实例挂了,流量会自动摘除。
- 配置中心也是Nacos。每个服务独立配置,支持灰度发布。这个很关键,因为特征平台和数据接入服务的数据源配置经常变,以前改配置要重启应用,现在可以在线热更新。
- 链路追踪用SkyWalking。微服务化之后,一次预测请求会经过网关、鉴权、特征服务、推理服务,没有追踪系统,出了问题根本查不到在哪一环。
- CI/CD流程重写。每个服务独立的构建流水线,合并主干后自动跑单元测试、接口测试,然后构建镜像推到仓库,通过K8s滚动发布。
从单体到一个能稳定运行的微服务集群,前后花了将近三个月。这三个月里相当痛苦,但效果也立竿见影:模型训练和大促预测同时跑,不再互相干扰;数据接入层每天发版,其他服务完全无感。
2.4 微服务带来的新问题:分布式事务和数据一致性,比想象中更难
拆分之后,第一个让我们陷入泥潭的问题,是数据一致性。
单体时代,一次预测任务的流程是:接入数据 -> 生成特征 -> 调用模型 -> 写结果。整个过程在同一个数据库事务里,要么全部成功,要么全部回滚。
拆成服务之后,这个流程变成了跨服务调用。数据接入服务处理完数据,要通知特征平台去拉取。特征平台算完特征,要通知推理服务去预测。任何一环失败了,数据的中间状态怎么处理?重试怎么做?如何保证不重复计算?
最开始我们采用的是"同步调用 + 本地重试"的方式:订单状态流转通过服务间HTTP调用完成。但很快发现,网络抖动导致的超时会掩盖真实状态,调用方不知道对方是成功了还是超时了。重试时又可能把同一个任务提交两次,造成重复预测和重复写入。
最终,我们引入了事务消息和本地消息表,把核心链路全部改成异步事件驱动:
- 数据接入服务处理完数据后,把"数据就绪"事件写入消息队列,同时更新本地状态表。
- 特征平台消费事件,计算特征后,把"特征完成"事件写入下一个队列。
- 推理服务消费特征事件,执行预测,把"预测完成"事件写入结果队列。
- 结果分发服务消费结果事件,推送下游。
这套方案牺牲了一定的实时性(整体链路增加了约1-3秒延迟),但换来了可靠性和可追踪性。每个环节的状态都记录在案,出问题可以精确查到是哪个环节没完成,重新触发即可。
提示:如果你是第一次做微服务改造,强烈建议在设计阶段就把链路的数据流图画清楚,明确每一步是同步还是异步,每个状态的流转条件是什么。中间件选型没那么重要,重要的是状态流转模型是清晰的。
3. 微服务跑起来之后,新的瓶颈比旧的更棘手
3.1 成本失控:每个服务都要预留资源,大促资源利用率只有17%
微服务解决了稳定性和协作问题,但很快暴露了另一个问题:成本。
我们有8个核心服务,每个服务为了保证高可用,至少要部署2个实例。平时流量低的时候,大量实例闲置。大促期间流量上来,又要临时扩容,扩容之后流量回落,又忘了缩容。后台监控面板上,整个集群的CPU平均使用率长期在10%-15%徘徊,内存使用率也就30%左右。
更难办的是预测模型训练。这个任务是明显的波峰负载,平时几乎不占资源,但每次模型更新时,需要瞬间申请大量GPU资源。为了这几次训练任务,我们常备了一个8卡GPU集群,一年下来,有超过90%的时间是空闲的。算了一笔账,GPU资源利用率不到8%,按当时的云厂商定价,一年光浪费的钱就够招两名工程师了。
3.2 运维复杂度失控:十几个服务,几十个实例,要处理的问题成倍增加
如果说成本问题还可以用"预算充足"来安慰自己,那运维复杂度就是实打实的体力折磨。服务一多,各种问题呈指数级增长:
- 服务之间的依赖关系越来越复杂,排查一个问题,经常要在五六套日志里来回跳。
- 每个服务都有自己的配置、环境变量、启动参数,升级K8s集群时,要保证所有服务都不受影响,工作量大得惊人。
- 某个服务OOM了,自动重启之后,依赖它的下游服务需要感知并做补偿,这种边界情况永远测不完。
- 大促前的容量评估非常难做。每个服务的峰值流量不是同步的,给每个服务都按峰值预留资源,成本就爆炸;不预留,又担心某个服务被冲垮。
有一次大促前压测,我们发现特征平台服务是瓶颈,于是给它扩容到8个实例。结果压测结束忘了缩回来,多跑了整整一个月的8实例,月底看到账单才发现了这个失误。这不是个案,每个月总有那么一两个服务忘记缩容。
3.3 为什么微服务在这个场景下显得"过度"了
回头看,微服务解决了单体阶段的两个问题:资源隔离和独立发布。但对我们这类以离线批处理为主、峰值频繁但耗时短的AI预测场景来说,微服务带来的分布式复杂度反而超过了它解决的问题。
预测系统的核心负载特征是什么?是短时、突发、密集计算。比如每天凌晨的模型训练任务、每天几次的批量预测任务、业务方临时发起的"新品预测"需求。这些任务耗时从几秒到几十分钟不等,但它们的共性是:任务之间有明确的边界,不需要常驻进程等待流量,天然适合按任务粒度弹性分配资源。
微服务的常驻实例模型,本质上是为"持续流量"设计的,而我们系统的核心负载是"离散任务"。这种错配,导致我们花了大量精力处理Service Mesh、熔断、限流这些分布式问题,而真正该关注的计算效率、成本优化、任务调度,反而被边缘化了。
正是在这种"微服务已经不再合适"的觉悟下,我们开始研究Serverless架构。
4. Serverless不是银弹,是特定负载下的最优解
4.1 理解Serverless:它不是某一个具体技术,而是一种资源供给方式
在决定转向Serverless之前,团队内部有过激烈争论。有同事认为,Serverless适合Web后端、适合事件处理,但不适合AI预测这种需要GPU计算、需要长时间运行的任务。这个观点有道理,但它混淆了"Serverless"和"FaaS函数计算"的概念。
Serverless的核心思想是:按实际执行时间和资源消耗付费,无需为闲置资源买单。它有两种典型形态:
- 函数即服务(FaaS):比如AWS Lambda,适合短时、事件驱动的计算。
- 容器即服务(CaaS/Serverless容器):比如阿里云ECI、AWS Fargate,把一个容器实例当成一次任务运行,用完即销毁。
对于AI预测系统,FaaS不是一个好选择,因为单次预测任务可能要跑十几分钟,超过了函数计算的超时限制,而且函数计算的冷启动和内存限制也扛不住深度模型推理。
但Serverless容器完美匹配我们的需求:训练任务就是一个容器实例,跑完就销毁;批量预测任务也是一个容器实例,按需拉起;平时没有任务时,整个系统的基础资源消耗几乎为零。
4.2 从微服务到Serverless:不是废弃所有微服务,而是把核心链路改成事件驱动的任务流
我们的做法是:保留微服务中"有价值"的部分,把"重负载"的部分Serverless化。
具体落地方案如下:
- 保留网关和服务注册中心,作为所有请求的入口和服务的发现机制。
- 把模型训练直接改成Serverless容器任务。数据接入服务将训练请求发到任务队列,训练执行器从队列拉到任务后,动态创建训练容器实例(指定GPU规格),训练完成上传模型文件到对象存储,容器自动销毁。
- 把批量预测拆成Serverless函数+Serverless容器的组合。预测请求到达后,先由函数计算做参数校验和任务拆分,然后按分片创建多个预测容器并行计算,每个容器处理一部分SKU,结果写回对象存储后合并。
- 保留特征平台和结果分发服务作为常驻微服务。因为特征有强缓存需求,结果分发要和下游系统维持长连接,常驻实例更合适。
这套方案落地后,基础设施层面的变化非常大。常驻服务从8个减少到3个,GPU集群彻底取消,按需创建训练容器。资源利用率从全年不到10%,提升到高峰期的75%以上。
4.3 Serverless带来的真实收益:不是技术上的降维打击,而是成本结构的改变
把成本列出来看,效果非常直观。
改造前(纯微服务):
- 常驻实例:8个服务 × 2实例 × 4核8G,月成本约 12,000 元。
- GPU集群:8卡V100常驻,月成本约 65,000 元。
- 存储和流量:约 8,000 元。
- 每月总计约 85,000 元。
改造后(Serverless混合架构):
- 常驻实例:3个服务 × 2实例 × 4核8G,月成本约 4,500 元。
- Serverless容器(训练+批量预测按量计费):高峰期一次训练约 200 元,每天两次训练,加上预测任务,月成本约 22,000 元。
- 存储和流量:约 10,000 元(对象存储流量略增)。
- 每月总计约 36,500 元。
每月基础设施成本降低接近六成,而且这是在吞吐能力不变的前提下。这个收益不是靠技术先进性,而是靠"不为闲置资源付费"的成本模型。
5. Serverless落地的具体形态:事件驱动、队列与状态管理
5.1 核心架构:用消息队列串联起整套任务流
Serverless化之后,系统的核心不再是"服务",而是"事件"。整个预测任务的生命周期,就是一系列事件的流转:
code复制数据就绪 -> 特征计算请求 -> 特征完成 -> 预测请求 -> 预测完成 -> 结果推送
这个事件流转建立在三个消息队列上:
- 数据事件队列:承载数据接入服务生产的数据就绪事件,特征平台消费。
- 特征事件队列:承载特征完成事件,预测任务调度器消费。
- 结果事件队列:承载预测完成事件,结果分发服务消费。
每个队列都配置了死信队列。任何环节处理失败超过三次,事件自动进入死信队列,由告警系统通知值班人员手动处理。这个设计非常重要,因为Serverless容器的执行环境和常驻进程不一样,任务失败不是"进程重启"就能解决的,必须有一个明确的补偿机制。
5.2 状态管理:Serverless无状态环境下,如何追踪一个预测任务的状态
Serverless容器本身是无状态的,这意味着我们不能像单体时代那样,在内存里维护一个任务状态机。所有状态必须外部化。
我们引入了一张任务状态表(存储在云数据库里),作为整个任务追踪的单一事实来源。任务状态包括:待提交、等待特征、特征完成、预测中、预测完成、部分失败、全部完成、已推送。
每个服务在处理完自己的环节后,更新任务状态表。同时,状态表里记录每个环节的开始时间、结束时间、处理实例ID、数据分片范围。这样,任何一个任务卡住了,我们都能通过状态表快速定位卡在哪个环节,以及这个环节对应的输入数据是什么。
状态表还能用来做幂等。消费队列消息时,先查状态表,如果某个分片已经处理过(状态为完成),直接跳过。这一招解决了Serverless容器重复执行可能带来的重复计算问题。
提示:Serverless架构下的状态表设计,一定要把"处理幂等"当成一个核心原则。因为函数或容器可能因为底层原因被重复调度,唯一能依赖的就是外部持久化状态。
5.3 冷启动优化:让预测任务不再等那漫长的初始化时间
Serverless容器最大的痛点是冷启动。一个预测容器从创建到真正的可执行计算,需要拉镜像、初始化环境、加载模型文件,这个过程可能要几十秒甚至几分钟。
对于实时性要求高的场景,这段冷启动是不可接受的。我们的优化思路有几种:
- 模型文件不打进镜像,而是放在对象存储上。容器启动时,先从对象存储拉取模型文件到本地缓存。模型文件有多个版本,大多数预测场景用的是最新的稳定版,这个版本会被提前预热到本地磁盘缓存。
- 对高频使用的预测容器配置"预留并发"。云厂商支持预留一定数量的实例,让它们保持热状态。我们从最开始的全冷启动,改成预留3个预测容器实例。每次批量预测任务到来时,优先复用热实例,只有新分片才创建冷实例。
- 把预测场景按耗时拆分。短时预测(查询类)走函数计算,只处理单SKU或小批量,执行时间控制在10秒内;长时预测(批量跑全量SKU)走Serverless容器,按并行分片方式处理。
优化之后,预热完毕的预测容器执行时间基本稳定在2-3秒。冷启动导致的额外等待时间,也通过分片并行掩盖掉了:第一个分片的冷启动时间里,其他分片已经在计算了,整体吞吐受冷启动影响显著减小。
5.4 成本控制:为不同预测任务设置不同的资源规格
Serverless一个容易被忽视的优势,是按"规格"计费。同样的预测任务,对精度要求不高的场景,用便宜的CPU容器跑就够;对深度模型推理,才需要使用GPU容器。
我们对预测任务做了分级:
- L1:用LightGBM模型的轻量级预测,CPU实例即可,成本最低。
- L2:用Prophet模型的时序预测,需要4核8G内存,CPU实例。
- L3:用DeepAR深度学习模型的预测,需要GPU实例。
调度器根据任务类型,动态选择合适的容器规格。同样是预测一万个SKU,如果全部走GPU实例,成本可能是CPU实例的5倍以上。分级之后,大约60%的预测任务走CPU实例,整体成本进一步降低。
6. 成本和性能的实测对比:三个阶段的量化数据
6.1 三个阶段的成本与性能全景对比
下面这个表格,是我们运维团队每月统计的真实数据汇总。虽然每家公司的业务规模和技术栈不同,但量级和趋势应该有一定参考价值。
| 指标 | 单体架构 | 微服务架构 | Serverless混合架构 |
|---|---|---|---|
| 常驻服务数 | 1个应用 | 8个服务 | 3个服务 |
| 平均CPU利用率 | 35% | 13% | 68% |
| GPU资源利用率 | 无法共享(训练阻塞推理) | 8% | 75%以上(按需创建) |
| 月基础设施成本(约) | 58,000 元 | 85,000 元 | 36,500 元 |
| 平均发布耗时 | 手动+停机30分钟 | 滚动发布约5分钟/服务 | 无感,任务粒度分发 |
| 故障恢复时间 | 分钟级(依赖人工) | 分钟级(依赖K8s重启) | 秒级(任务失败自动重试) |
| 大促扩容方式 | 手动加机器+重启 | 手动或定时HPA | 自动按任务量弹性伸缩 |
| 运维精力占比 | 20% | 55% | 25% |
这里有一个数据值得注意:微服务阶段的大促扩容,我们还是靠定时HPA。因为预测任务不是典型的HTTP流量模型,传统基于QPS的HPA指标不准,只能手动设定扩容窗口。到了Serverless阶段,容器实例完全按任务量创建,任务来了就扩,任务结束就缩,人力介入大幅减少。
6.2 性能指标的实测:延迟和吞吐不降反升
很多人担心,微服务改造成Serverless之后,性能会变差。我们实际测下来的结果是:关键指标不降反升。
以每日凌晨的全量预测任务为例:
- 单体阶段:全量10万SKU预测,串行执行,耗时约3小时。
- 微服务阶段:并发执行,但常驻实例有限,耗时约40分钟。
- Serverless阶段:按100个分片并行创建容器,总耗时压缩到12分钟左右。
预测任务从3小时压缩到12分钟,带来的业务价值是非常直接的:预测结果出得越早,补货计划就能越早下发,仓库和采购就有更充裕的时间做准备。
实时查询接口的P99延迟,也从微服务阶段的320ms降到了200ms以内。原因是Serverless容器有独立的资源配额,不会像微服务常驻实例那样被其他任务争抢CPU和内存。
6.3 可观测性:Serverless架构下的监控体系重构
Serverless化之后,我们常驻服务变少了,但任务的粒度变多了。每个月要跑上千个容器任务,监控指标不再按"服务"聚合,而需要按"任务批次"聚合。
监控体系做了调整:
- 所有任务在启动时,注入本次任务的任务ID和批次ID到日志和指标标签里。
- 任务执行时间、成功/失败数量、容器启动耗时、分片处理速率,全部通过指标接口上报到Prometheus。
- 告警策略改为基于任务状态:某个批次失败率达到5%触发警告;超过10%触发紧急;某个任务超过预期执行时间2倍仍未完成,触发卡住告警。
- 每个任务结束后,生成一份执行报告,包含总耗时、分片耗时分布、失败原因、资源消耗。这个报告会推送到团队群,每天早上扫一眼就能知道昨日预测任务整体情况。
这套可观测体系,比微服务时代更精细,也更省心。因为任务的边界非常清晰,每个任务的成败一目了然,不再需要从一堆服务日志里拼凑调用链。
7. 这次架构演进留给我的几条判断准则
7.1 架构选型的第一原则:匹配负载特征,而非追逐技术潮流
我们经历了三个阶段,最大的体会是:没有"最好的架构",只有"当前业务阶段最合适的架构"。
单体适合业务不确定性高、团队规模小、系统负载和变更频率都不高的阶段。微服务适合业务复杂度上升、模块间需要独立扩容和隔离故障的阶段。Serverless适合任务边界清晰、负载波动大、且成本敏感的阶段。
必须承认,这套AI预测系统的负载特征——离线批处理、任务边界清晰、短时并行计算——与Serverless的模型高度契合。如果你的系统是典型的在线交易型负载,比如订单系统、支付系统、IM系统,对延迟极度敏感,Serverless可能并不是最好的选择。做技术选型前,先把你的负载特征列出来,再对照三种架构的适用条件,答案会清晰很多。
7.2 拆分和演进的节奏:不要一次推倒重来
在微服务重构的时候,我们踩过一个很大的坑:试图一次性把单体全部拆完。结果中间有一个月时间,系统处于"半单体半微服务"的混沌状态,数据流断裂,线上问题频发。
后来我们改变了策略:先拆最痛的部分。第一个拆的是模型训练服务,因为它是资源冲突最严重、变更频率最低、边界最清晰的服务。单独拆出来之后,稳定性立刻提升。然后再依次拆数据接入、特征平台、结果分发。每一步都保证系统是可运行的,每拆一个服务,都留出两周左右的时间观察稳定性。
Serverless化也是同样的节奏。我们并没有把微服务全部推倒重来,而是保留了网关、特征平台、结果分发三个常驻服务,只把训练和批量预测迁移到Serverless容器。事实证明,这种渐进式迁移风险最低,收益最直接。
7.3 可观测性和数据一致性,永远是重构的底线
两次重构过程中,我们都差点在"可观测性"和"数据一致性"上翻车。
单体阶段,虽然服务是单体的,但日志要打全。我们每个关键步骤都有日志输出,这为后来排查微服务问题提供了极大的帮助。微服务阶段,链路追踪必须尽早接入,不要等服务多了再加,那时候改造的代价已经非常高了。Serverless阶段,任务ID和批次ID要从第一行代码就开始传递,这是分布式排查的基础。
数据一致性方面,我强烈建议:
- 所有跨服务的状态流转,用异步事件+状态表管理,不要用同步调用来维护跨服务事务。
- 事件的幂等消费,必须是默认设计,而不是事后补救。
- 把"数据血缘"记录清楚。每个预测结果的生成,依赖了哪些数据源、哪些特征版本、哪些模型版本。这个问题不解决,Serverless化之后你会被各种"这个预测结果怎么来的"的问题淹没。
7.4 关于Serverless,我最后想说几句实话
Serverless不是万能的,它有自己的边界。
- 如果单次任务执行时间极长(超过数小时),Serverless容器的性价比优势会减弱,因为长时间占用的费用不比常驻实例低太多。
- 如果任务之间有强依赖关系,需要复杂的编排(DAG),Serverless实现起来会比较痛苦。虽然云厂商有Step Functions之类的工作流服务,但调试成本不低。
- 如果团队对容器、云厂商生态完全不熟悉,第一次落地Serverless的学习成本会比较高。
但如果你面临的问题和我的处境相似——预测系统的负载是离散任务型的,基础设施成本不断攀升,运维精力被大量空转的常驻实例消耗——那Serverless这条路线,是值得认真评估的。
我们这套系统已经稳定运行了一年多。每个月看账单的时候,我还会想起当初微服务时代那些空转的GPU集群,那种"钱在燃烧,却没有任何产出"的感觉,是推动架构演进的最真实动力。技术选型就是这样,有些路只有亲自踩过,才知道下一段路应该往哪里走。
