☰
14个月老平台重建与AI落地实践:从病灶清单到生产级切换
2026/10/18 14:16:46 网站建设 项目流程

先说结论:14个月,重建一个运行了八年的老平台,同时把AI能力从零推到生产环境,时间是够的,但前提是不能把两件事当成两个阶段来排。团队内部给这个项目起的代号是“Meta重建计划”,取意Metamorphosis——不是修修补补,而是让系统的组织形态彻底变一次。如果只让我用一句话概括这次经验,那就是:重建基础的过程,恰恰是推进前沿最好的抓手;前沿业务的需求,反过来会逼着基础架构做出真正有价值的改变。

这篇文章里的“我”不是一个旁观者,而是全程参与了目标拆解、链路梳理、切换决策和踩坑复盘的人。下面所有的时间线、指标、阶段设置,都来自可追溯的项目记录,不是事后拍脑袋编出来自我安慰的。如果你现在正负责一个老平台的现代化改造,或者正处于“既要保证业务稳定又要搞创新”的双重压力下,这篇文章值得你慢慢看。我会把目标设定、阶段排序、数据迁移、链路切换、前沿落地和具体踩坑全部展开,尽量做到可以直接参考。

1. 为什么老平台必须先“断臂重生”:病灶清单比路线图更重要

1.1 一个运行八年平台的核心病灶

我们面对的平台,核心服务两个,加起来120万行代码,表面上跑得还行,但每一次发版都像拆炸弹。部署一套新环境平均要40分钟,回滚主要靠人肉操作;数据库当年为了扛流量按业务后缀做了分库分表,结果查询经常要拼表名;更麻烦的是核心链路上有三十多个定时任务在互相补偿,谁都不敢轻易改动。

真正的转折点是某个周五下午,一次看似普通的配置变更引发了全链路雪崩,从发现问题到流量恢复用了两个多小时。业务方的原话是:“你们再这么玩,我们就换体系。”这句话很难听,但点醒了我:老平台的问题不是某一个代码写错了,而是整个系统的组织方式已经撑不住现在的业务形态了。

具体来说,我们当时面临几个核心病灶:

  • 发布频率极低:核心服务一个月只敢发一次版,因为每次发版都要协调四五个团队,发布窗口内任何异常都可能演变成几小时的故障。
  • 数据边界混乱:订单、用户、库存、结算的数据散落在多个库里,表与表之间靠定时任务同步,跨域查询经常要绕好几个弯。
  • 新能力无处安放:AI模型需要的实时特征数据根本拿不到,老系统只支持T+1的离线批量计算,业务方想要“实时推荐”和“实时风控”基本无从谈起。
  • 过程不可观测:调用链只有日志级别的串联,没有统一的Trace体系,排查一个问题平均要翻四五个系统。

这些病灶单独拎出来都不算致命,但叠加在一起,就构成了一个“动哪里都会出事”的僵局。我们当时做了一个决定:不追求推倒重来,而是先用一张清单把“坏”排序,再决定14个月怎么花。

1.2 “坏系统清单”的排序逻辑

很多团队一谈重建就画目标架构图,恨不得全部换成最新的框架、最新的中间件。我的建议恰恰相反:先不要谈架构,先回答一个问题——你现在最痛的是什么?

我们当时把所有问题分成三类:

  • 影响发布频率的坏:发版慢、回滚难、环境准备时间长。
  • 影响数据正确性的坏:跨系统补偿逻辑多、对账靠人工、数据口径不统一。
  • 影响新能力上线的坏:无法动态配置、不能灰度、实时特征数据缺失。

排序之后,我们把14个月的资源做了一个粗分配:60%用于解决前两类问题,30%用于建立新的基础设施,10%留给前沿探索。这个分配和很多人的直觉相反,因为看起来“重建基础”占了绝大部分精力,但事实上,如果没有前两类问题的解决,后面所谓的基础设施也只是空中楼阁。

为了把“坏系统清单”变成可执行的目标,我们给每一项都定了量化指标。这里可以分享一张当时的基线表:

指标改造前目标实际结果
核心服务发布频率每月1次每周3次每周4次
发布准备时长40分钟10分钟8分钟
线上故障MTTR78分钟15分钟13分钟
实时特征数据延迟T+1秒级秒级
核心链路依赖数量32个定时任务少一半降到8个

写下这些数字不是为了好看,而是为了让整个团队有共同的靶心。尤其是后面做切换时,每次灰度能不能继续,不用吵,直接把指标拉出来看就行。这是我这次项目里觉得最值钱的第一步:先定义“什么算好”,再开始动手。

2. 14个月的时间线不是直线:前一阶段的目标是让下一阶段更好做

2.1 0-3月:只诊断、不迁移,把“不知道”变成“知道”

重建项目最常见的失败方式,就是用“不知道现状的勇气”去推翻现状。所以我们规定:前三个月绝对不写一行迁移代码,只做诊断。

这三个月里我们干了四件事:

  1. 全链路梳理:用代码调用链分析工具把所有核心服务之间的调用关系跑出来,标出Top30的核心链路,然后逐条过。
  2. 依赖图谱建设:把数据库表、消息队列、外部API的依赖关系画成图谱,找出那些“谁都在用但没人负责”的公共模块。
  3. 容量基线测试:对每条核心链路做压测,搞清楚当前系统的真实水位线,而不是凭感觉说“扛不住”。
  4. 故障演练:人为注入故障,看系统会不会自愈、监控能不能第一时间发现。

前两个相对常规,后两个是这次项目的转折点。故障演练做完,我们发现了12个高危依赖——比如某个基础服务一旦慢调用超过100毫秒,下游十个服务全部跟着超时;又比如某个缓存集群只要故障超过30秒,核心下单链路就会整体熔断。这些发现直接改写了后续的技术方案:加缓存、加熔断、加隔离,优先级全部上调。

很多人觉得前三个月“没产出”,但我认为这是整个14个月里最被低估的阶段。如果没有这三个月,后面切换至少要多两倍的返工。诊断的目的不是让自己安心,而是让所有决策都有据可循。

2.2 4-7月:数据资产盘点倒逼模块解耦

到了第四个月,我们才开始动代码。但动手的顺序不是“拆服务”,而是“拆数据”。

这其中的逻辑很简单:表与表之间的JOIN一旦拆开,代码层面的耦合会自动暴露出来。如果数据边界不清晰,拆出来的所谓微服务还会通过数据库互相藕断丝连,那只是把单体搬到了多个部署单元里而已。

我们当时的具体做法分三步:

  1. 给所有核心表打领域标签:订单域、用户域、库存域、结算域。
  2. 找出跨域JOIN和跨域事务,逐一分类:哪些是真正需要合并的,哪些只是查询时的便利。
  3. 把一部分“强同步调用”改成事件流,核心方式是监听数据库增量变更,转换成领域事件。

伪代码大概是这样的:

# 监听增量变更并转换为领域事件 def handle_binlog(record): if record.table == "orders": if record.op == "UPDATE" and record.after.status != record.before.status: producer.send("order.state.changed", { "order_id": record.after.id, "old_status": record.before.status, "new_status": record.after.status, "ts": record.timestamp, })

这种做法的好处在于:老系统不需要感知消息投递,只要数据库的增量日志被解析即可;迁移期间,新老系统可以同时消费事件流,互不干扰。我们用了大概两个月,把三套核心库中两套的领域边界拆清楚了,同时把跨域定时任务从32个降到了14个。

这一阶段最常见的诱惑是“顺手把框架也换了”,我强烈建议克制住。拆数据边界已经够痛了,再叠加框架升级,出问题时根本定位不了是数据问题还是框架问题。

2.3 8-11月:核心链路切换的节奏设计

真正让团队神经紧绷的是流量切换阶段。我们先用了一个“三段式”的结构:影子读、双写、灰度切流。

  • 影子读阶段:把线上流量复制一份打到新系统,只读不写,持续跑三到四周。这个阶段不看别的,就看结果差异率。
  • 双写阶段:写入同时落老库和新库,老库仍然是事实源,新库通过异步任务同步数据,持续四到六周,观察数据一致性和写入延迟。
  • 灰度切流阶段:逐步把线上流量从老系统切到新系统,每次切量都盯着核心指标。

具体的切量节奏和验收标准,我们当时是这样设计的:

阶段持续时间流量比例通过标准
影子读第9-10周0%,全量回放差异率 ≤ 0.01%,P99延迟差 ≤ 10%
双写第11-14周0%,写入双跑双写失败率 ≤ 0.001%,差异趋势收敛
灰度切流第15-18周5% → 20% → 50% → 100%每次切换后观察15分钟,错误率无上升,P99稳定

这里我想分享一个我们内部叫“3-5-2灰度口诀”的经验:每次切换的比例增量不超过20%,观察窗口不少于10分钟,必须盯2个核心指标(错误率、P99延迟)。这不是什么高深理论,而是血的教训换来的——早期有一次从20%直接切到80%,结果新系统的一个隐藏问题瞬间放大,逼着我们当天回滚,白白浪费了一周。

双写阶段最容易忽视的是“写入顺序”。我们采用的是“先写老库,再异步写新库”,因为老库是事实源,任何一笔写失败都能通过重试补齐。如果把新库当作主写,一旦新库出问题,老系统就会立刻断流,业务损失无法挽回。

2.4 12-14月:把前沿能力装进已经稳定的地基

基础稳定之后,剩下的三个月是用来“推进前沿”的。很多人以为前沿就是堆模型、堆算力,但实际上,前沿能力落地最大的瓶颈是数据基础设施。

举一个例子:订单状态事件流稳定运行后,我们立刻把实时特征接入了新的推荐和风控链路。之前风控是T+1的离线批处理,模型再好也只能第二天跑;现在基于实时事件流,同一个风险识别可以做到秒级响应,召回率还提升了将近一倍。这就是“重建基础”对“推进前沿”的杠杆作用。

我们当时给团队定了一个很奇怪的规矩:每个核心模块切换完成后,必须挂上一个新能力,才叫“完成”。比如订单模块切完,实时风控立刻接入;用户模块切完,推荐系统立刻换用新的实时特征服务。这样做的好处是,避免出现“重建完了,但业务毫无感知”的尴尬。

前沿能力的选择也很重要。我们不是一上来就铺很多线,而是只挑两个最能让业务方感知到差异的点:推荐实时化和风控实时化。这两个点都有明确的业务指标,也最容易验证重建成果。三个月时间,足够把这两个能力从实验变成生产可用。

3. 最容易被低估的三类底层工作:兼容层、数据迁移和回滚方案

3.1 防腐层不是中间件,是“新旧共存”的边界协议

很多团队会在迁移方案里纠结“大爆炸”还是“绞杀者”,我们毫不犹豫选了“绞杀者”,也就是新旧系统共存、逐步替换。但共存的前提是必须有一个清晰的防腐层。

这个防腐层不是某种特定的中间件,而是一个边界协议。它要做两件事:第一,把老系统的接口协议翻译成新系统能理解的协议;第二,把流量切换策略控制在配置层面,而不是散落在代码里。

举个路由规则的示意:

{ "service": "order_center", "strategy": "traffic-split", "rules": [ { "condition": "user_id % 100 < 5", "target": "new" }, { "condition": "internal_call == true", "target": "new" }, { "default": "old" } ] }

有了这一层,切量不需要发版,只需要在配置中心调整百分比。这极大降低了切换的心理门槛——反正随时随地能退回去。但这里也有一个忠告:防腐层本身会腐化。如果你一直往里堆翻译逻辑,它最终会变成第二个“老系统”。我们当时规定,每个迭代都要清理防腐层中过时的翻译规则,让它的厚度保持在“够用就好”。

3.2 数据迁移三段式:影子读、双写、切换

数据迁移是整个项目里最不能出错的环节。我们实践下来,有几个细节是常规文档不会写的。

影子读阶段,重点不是“结果一模一样”,而是“差异可解释”。新系统和老系统的数据模型不同,字段命名、时区、精度都可能造成合理差异。如果一上来就追求零差异,团队会陷入无尽的对账中。正确做法是先把所有差异分成“可解释差异”和“不可解释差异”,每天只看后者数量是否收敛。

双写阶段,对账脚本不能只看行数或金额,而要对关键字段做哈希比对。我们当时用的比对逻辑大致是这样的伪SQL:

SELECT COUNT(*) AS diff_cnt FROM ( SELECT order_id, status, amount, updated_at FROM old_order_table WHERE updated_at >= '{start_time}' MINUS SELECT order_id, status, amount, updated_at FROM new_order_table WHERE updated_at >= '{start_time}' );

实际操作中我们并没有用MINUS,而是对每条记录生成一个校验哈希,然后比对哈希集合。这样既能发现漏数据,也能发现字段级差异。每天跑完对账后,差异报告会直接推到群里,而不是躺在离线任务日志里,这一点非常重要:数据问题只有暴露在所有人眼前,才会被真正重视。

3.3 回滚方案的三条命:镜像可回退、数据可回滚、流量可切换

我见过太多迁移项目,回滚方案写得很漂亮,但真正遇到故障时根本执行不了。原因很简单:回滚不是一两个命令,而是一条完整的逃生通道。

我们给回滚方案定了“三条命”原则:

  • 镜像可回退:每次发布前把所有服务打镜像,保留最近5个版本。回滚时不是重新构建,而是秒级拉起旧镜像。
  • 数据可回滚:数据库变更全部采用expand-migrate-contract模式——先加列,再双写,再回填,最后才删旧列。任何一步出问题,都只回滚当前阶段,而不是整体回滚。
  • 流量可切换:切量开关独立于代码,放在配置中心。一旦发现异常,回滚就是把百分比调回0,而不是发布一个旧版本。

这里有一条值得刻在墙上的经验:所有回滚都要先回答一个问题——你的数据是无损回滚还是尽力回滚?如果回答不了,就不要开始双写。我们早期就因为没想清楚这个问题,差点在切换时遇到无法恢复的尴尬。

4. 踩坑实录:几个差点让计划失去意义的决策

4.1 双写延迟不一致的那一周

进入双写阶段大概两周后,某天凌晨对账任务跑出了一条刺眼的数字:差异率超过0.5%。当时整个人都从床上弹起来了。

排查过程很曲折。我们一开始怀疑是同步任务丢消息,后来发现真相更隐蔽:老系统在部分调用链上用的是“本地事务+扫表补偿”,新系统用的则是“消息最终一致”。在退款和新建订单并发出现的边界场景下,老库已经落数据,但新库的消息还没到,对账脚本直接把这个差异标记成了错误。事实上这些差异里很大一部分是“最终一致窗口”内的正常现象。

解决方式有两个层次:

  1. 给对账任务加“延迟窗口”,允许30分钟内的差异存在。
  2. 把资金、库存这类强一致链路单独拎出来做实时对账,不给延迟窗口。

这次踩坑给我的教训是:对账任务本身也要设计,而不是简单把两张表拉出来做diff。如果一开始就按业务差异分级,我们至少能省下三天排查时间。

4.2 一次回滚把缓存设计问题暴露了

切换某模块到新系统后,业务方很快反馈:数据还是老值,刷新也不变。一开始怀疑是浏览器缓存,后来定位到是新系统的缓存设计出了问题。

老系统里有一个约定俗成的习惯:写入后立即读,靠数据库的事务保证一致性。新系统为了性能,在缓存策略上选择了“更新时删除缓存”,理论上下一次读取会重新加载。但问题在于,切换流量时,缓存里还存着老系统的旧快照,新系统读到的还是旧值。

这个坑的根因是缓存键和业务版本没有绑定。后来我们把缓存key里面加了一个版本号,每次切换流量或发布新版本时,整体版本号+1,让旧缓存瞬间失效。这个方案虽然会让切换瞬间的缓存命中率下降,但相比“命中旧数据”带来的业务错误,这点性能损失完全可以接受。

迁移期间的缓存设计有一条原则:宁可多miss几次,也不要命中旧数据。因为多miss的代价是几次查询,命中旧数据的代价是用户信任。

4.3 配置管理与代码管理脱节,差点导致全链路超时

第三个坑很隐蔽,甚至和系统架构无关,但造成的后果最严重。

当时为了调新环境的超时参数,有同事把配置中心里的一个配置直接从新环境复制到了生产环境,覆盖了生产环境原本更保守的超时阈值。结果上线后,核心链路瞬间出现大量超时,因为新的超时设置比服务端实际处理时间还要短。更麻烦的是,这个配置是在配置中心直接改的,没有经过代码评审,等到监控报警时,我们花了很久才意识到问题出在配置上。

从那以后,我们定了一条铁律:配置中心走代码评审,配置变更记录可审计,配置发布和代码发布一样分级。并且给配置中心加了版本diff,每次改动都要能看清改了什么、谁改的、为什么改。

配置的CI/CD远没有代码那么普及,但它和代码一样需要工程化。这是我这次项目里最便宜也最有效的一个投资,早做早好。

5. 复盘后的两个“会保留”和两个“绝不会再做”

5.1 值得保留的决定1:把前沿能力写进验收标准

如果再来一次,我依然会把“前沿能力上线”作为模块改造的完成条件。我们团队对“模块改造完成”的定义不是“老功能能跑”,而是“新能力能上线”。比如订单模块切换完成后,实时风控模型立刻拿到了事件流数据,这才算完成。

这个决定改变了团队的思维方式:前端业务需求不再是“改造完成”之后才考虑的后续项,而是和基础重建同步推进的验收标尺。它不仅保证了业务方对重建有感知,也让做底层的人能感受到自己做的东西真正在创造价值。

5.2 值得保留的决定2:用故障演练而不是代码评审来验收

代码评审当然要做,但它的盲区是“静态”。代码看起来没问题,不代表系统真正扛得住问题。我们后期每个阶段切换前,都会做一次人为故障注入,模拟依赖宕机、消息积压、缓存穿透这些场景。结果每次都能发现和代码评审结论完全不同的脆弱点。

印象最深的一次演练,是人为把下游服务延迟拉大,结果新系统的连接池被打穿,反而老系统因为线程池更小、拒绝更快,没有拖垮全链路。那次演练直接改写了连接池配置,而不是事后靠线上事故来教训我们。

5.3 下次不会再做的事1:用“统一技术栈”把老代码全部重写

“统一技术栈”这个口号听起来很漂亮,但它的成本往往被严重低估。我们中途一度想把老代码全部改写成新框架,后来发现很多“看起来旧但正确”的代码,一旦重写就会引入新的边界问题。

现在我的看法变了:要重写的是数据边界和交互协议,不是每一行代码。老代码能跑且有测试保护的,就让它继续跑;只有当它成为新能力的瓶颈时,才值得动刀。

5.4 下次不会再做的事2:追求双写阶段的“零差异”

双写阶段开始时,我们内部有一句话说“差异必须为0,否则不能切换”。听起来很严谨,实际执行下来差点把自己逼死。因为业务系统的差异天然存在,比如日志类数据、画像类数据,晚几秒根本无人在意;资金、库存、状态机这类强一致数据,却一丝一毫都不能差。

正确的做法是把差异分级:

  • 必须绝对一致:资金、库存、订单状态机。
  • 允许短暂不一致,但窗口内必须收敛:日志、画像、辅助字段。
  • 允许有上限差异,且只影响非核心场景:推荐计数、通知状态。

健康的标准不是diff为0,而是diff在窗口内收敛到0,并且没有方向性偏差。所谓方向性偏差,就是同一类差异总是往新系统或老系统单边倾斜,那才是真正需要警惕的信号。

14个月走下来,我最大的体会是:这个时间本身不是一个固定的数字,而是一个节奏隐喻。如果你在周期里只做重建基础,团队会疲,业务会等不及;如果你只做推进前沿,地基会塌。把两者揉在一起,用同一个节奏推进,才是真正值得复制的部分。

最后分享一个我们从项目第一天就用到现在的小习惯:把“基础重建看板”和“前沿探索看板”放在同一张物理白板上,每天例会同时看两边的卡片。只有当“前沿需求”反过来频繁追问基础架构要数据、要弹性、要时效的时候,重建才不会沦为自娱自乐的技术洁癖;也只有当天天在改底层的时候还坚持往产品里塞新AI能力,团队才不会在前线被磨掉心气。

如果你正准备启动一个重建项目,我建议你第一周就做两件事:把坏系统清单写出来,把一个最近最想要的前沿能力钉在看板最上方。剩下的节奏,14个月里自然会告诉你。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询