资讯动态

14个月老平台重建与AI落地实践:从病灶清单到生产级切换

发布时间:2026/10/18 14:16:50 来源:尧图企业网站定制
先说结论14个月重建一个运行了八年的老平台同时把AI能力从零推到生产环境时间是够的但前提是不能把两件事当成两个阶段来排。团队内部给这个项目起的代号是“Meta重建计划”取意Metamorphosis——不是修修补补而是让系统的组织形态彻底变一次。如果只让我用一句话概括这次经验那就是重建基础的过程恰恰是推进前沿最好的抓手前沿业务的需求反过来会逼着基础架构做出真正有价值的改变。这篇文章里的“我”不是一个旁观者而是全程参与了目标拆解、链路梳理、切换决策和踩坑复盘的人。下面所有的时间线、指标、阶段设置都来自可追溯的项目记录不是事后拍脑袋编出来自我安慰的。如果你现在正负责一个老平台的现代化改造或者正处于“既要保证业务稳定又要搞创新”的双重压力下这篇文章值得你慢慢看。我会把目标设定、阶段排序、数据迁移、链路切换、前沿落地和具体踩坑全部展开尽量做到可以直接参考。1. 为什么老平台必须先“断臂重生”病灶清单比路线图更重要1.1 一个运行八年平台的核心病灶我们面对的平台核心服务两个加起来120万行代码表面上跑得还行但每一次发版都像拆炸弹。部署一套新环境平均要40分钟回滚主要靠人肉操作数据库当年为了扛流量按业务后缀做了分库分表结果查询经常要拼表名更麻烦的是核心链路上有三十多个定时任务在互相补偿谁都不敢轻易改动。真正的转折点是某个周五下午一次看似普通的配置变更引发了全链路雪崩从发现问题到流量恢复用了两个多小时。业务方的原话是“你们再这么玩我们就换体系。”这句话很难听但点醒了我老平台的问题不是某一个代码写错了而是整个系统的组织方式已经撑不住现在的业务形态了。具体来说我们当时面临几个核心病灶发布频率极低核心服务一个月只敢发一次版因为每次发版都要协调四五个团队发布窗口内任何异常都可能演变成几小时的故障。数据边界混乱订单、用户、库存、结算的数据散落在多个库里表与表之间靠定时任务同步跨域查询经常要绕好几个弯。新能力无处安放AI模型需要的实时特征数据根本拿不到老系统只支持T1的离线批量计算业务方想要“实时推荐”和“实时风控”基本无从谈起。过程不可观测调用链只有日志级别的串联没有统一的Trace体系排查一个问题平均要翻四五个系统。这些病灶单独拎出来都不算致命但叠加在一起就构成了一个“动哪里都会出事”的僵局。我们当时做了一个决定不追求推倒重来而是先用一张清单把“坏”排序再决定14个月怎么花。1.2 “坏系统清单”的排序逻辑很多团队一谈重建就画目标架构图恨不得全部换成最新的框架、最新的中间件。我的建议恰恰相反先不要谈架构先回答一个问题——你现在最痛的是什么我们当时把所有问题分成三类影响发布频率的坏发版慢、回滚难、环境准备时间长。影响数据正确性的坏跨系统补偿逻辑多、对账靠人工、数据口径不统一。影响新能力上线的坏无法动态配置、不能灰度、实时特征数据缺失。排序之后我们把14个月的资源做了一个粗分配60%用于解决前两类问题30%用于建立新的基础设施10%留给前沿探索。这个分配和很多人的直觉相反因为看起来“重建基础”占了绝大部分精力但事实上如果没有前两类问题的解决后面所谓的基础设施也只是空中楼阁。为了把“坏系统清单”变成可执行的目标我们给每一项都定了量化指标。这里可以分享一张当时的基线表指标改造前目标实际结果核心服务发布频率每月1次每周3次每周4次发布准备时长40分钟10分钟8分钟线上故障MTTR78分钟15分钟13分钟实时特征数据延迟T1秒级秒级核心链路依赖数量32个定时任务少一半降到8个写下这些数字不是为了好看而是为了让整个团队有共同的靶心。尤其是后面做切换时每次灰度能不能继续不用吵直接把指标拉出来看就行。这是我这次项目里觉得最值钱的第一步先定义“什么算好”再开始动手。2. 14个月的时间线不是直线前一阶段的目标是让下一阶段更好做2.1 0-3月只诊断、不迁移把“不知道”变成“知道”重建项目最常见的失败方式就是用“不知道现状的勇气”去推翻现状。所以我们规定前三个月绝对不写一行迁移代码只做诊断。这三个月里我们干了四件事全链路梳理用代码调用链分析工具把所有核心服务之间的调用关系跑出来标出Top30的核心链路然后逐条过。依赖图谱建设把数据库表、消息队列、外部API的依赖关系画成图谱找出那些“谁都在用但没人负责”的公共模块。容量基线测试对每条核心链路做压测搞清楚当前系统的真实水位线而不是凭感觉说“扛不住”。故障演练人为注入故障看系统会不会自愈、监控能不能第一时间发现。前两个相对常规后两个是这次项目的转折点。故障演练做完我们发现了12个高危依赖——比如某个基础服务一旦慢调用超过100毫秒下游十个服务全部跟着超时又比如某个缓存集群只要故障超过30秒核心下单链路就会整体熔断。这些发现直接改写了后续的技术方案加缓存、加熔断、加隔离优先级全部上调。很多人觉得前三个月“没产出”但我认为这是整个14个月里最被低估的阶段。如果没有这三个月后面切换至少要多两倍的返工。诊断的目的不是让自己安心而是让所有决策都有据可循。2.2 4-7月数据资产盘点倒逼模块解耦到了第四个月我们才开始动代码。但动手的顺序不是“拆服务”而是“拆数据”。这其中的逻辑很简单表与表之间的JOIN一旦拆开代码层面的耦合会自动暴露出来。如果数据边界不清晰拆出来的所谓微服务还会通过数据库互相藕断丝连那只是把单体搬到了多个部署单元里而已。我们当时的具体做法分三步给所有核心表打领域标签订单域、用户域、库存域、结算域。找出跨域JOIN和跨域事务逐一分类哪些是真正需要合并的哪些只是查询时的便利。把一部分“强同步调用”改成事件流核心方式是监听数据库增量变更转换成领域事件。伪代码大概是这样的# 监听增量变更并转换为领域事件 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月把前沿能力装进已经稳定的地基基础稳定之后剩下的三个月是用来“推进前沿”的。很多人以为前沿就是堆模型、堆算力但实际上前沿能力落地最大的瓶颈是数据基础设施。举一个例子订单状态事件流稳定运行后我们立刻把实时特征接入了新的推荐和风控链路。之前风控是T1的离线批处理模型再好也只能第二天跑现在基于实时事件流同一个风险识别可以做到秒级响应召回率还提升了将近一倍。这就是“重建基础”对“推进前沿”的杠杆作用。我们当时给团队定了一个很奇怪的规矩每个核心模块切换完成后必须挂上一个新能力才叫“完成”。比如订单模块切完实时风控立刻接入用户模块切完推荐系统立刻换用新的实时特征服务。这样做的好处是避免出现“重建完了但业务毫无感知”的尴尬。前沿能力的选择也很重要。我们不是一上来就铺很多线而是只挑两个最能让业务方感知到差异的点推荐实时化和风控实时化。这两个点都有明确的业务指标也最容易验证重建成果。三个月时间足够把这两个能力从实验变成生产可用。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 数据迁移三段式影子读、双写、切换数据迁移是整个项目里最不能出错的环节。我们实践下来有几个细节是常规文档不会写的。影子读阶段重点不是“结果一模一样”而是“差异可解释”。新系统和老系统的数据模型不同字段命名、时区、精度都可能造成合理差异。如果一上来就追求零差异团队会陷入无尽的对账中。正确做法是先把所有差异分成“可解释差异”和“不可解释差异”每天只看后者数量是否收敛。双写阶段对账脚本不能只看行数或金额而要对关键字段做哈希比对。我们当时用的比对逻辑大致是这样的伪SQLSELECT 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%。当时整个人都从床上弹起来了。排查过程很曲折。我们一开始怀疑是同步任务丢消息后来发现真相更隐蔽老系统在部分调用链上用的是“本地事务扫表补偿”新系统用的则是“消息最终一致”。在退款和新建订单并发出现的边界场景下老库已经落数据但新库的消息还没到对账脚本直接把这个差异标记成了错误。事实上这些差异里很大一部分是“最终一致窗口”内的正常现象。解决方式有两个层次给对账任务加“延迟窗口”允许30分钟内的差异存在。把资金、库存这类强一致链路单独拎出来做实时对账不给延迟窗口。这次踩坑给我的教训是对账任务本身也要设计而不是简单把两张表拉出来做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个月里自然会告诉你。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑