
这期节目聊完我在整理笔记的时候发现Dirk和Linus两个人聊得最投入、最没有嘉宾包袱的一次反而是这期讲旧系统改造的。原因很简单他们俩都亲手拆过自己的烂摊子也都被人用别动动了出问题谁负责这样的话按在椅子上过。所以这一期与其说是技术分享不如说是两个干了十来年活的人在对着一个失败过两次的重构项目做复盘。如果你是那种正被一个运行了五六年的老系统磨得没脾气想重构又不敢动的开发者这期内容值得你花点时间看完。尤其里面有一段数据库连接池被打爆的排查链路我认为比任何一本讲性能调优的书都来得实在。以下内容不是节目文字稿而是我把这期节目里聊到的关键技术点、决策过程和踩坑记录重新梳理了一遍加入了这期节目里没有来得及展开的细节和前后因果方便你照着参考。1. 这期节目的缘起一个老项目为何拖了三年才决定重构节目的前半段没有直接聊技术而是先讲了一个背景故事。Dirk和Linus接到了一个某团队发来的求助对方维护着一套已经运行了八年的订单管理系统。这套系统最早是两个刚毕业的开发者用两周时间赶出来的后来经过了几轮外包、几次人员更替代码里已经找不到任何一个还在职的作者。它每天要处理几十万笔订单但它的核心表结构还停留在第一次上线时的设计连订单和用户之间的关联都是靠一个varchar字段存JSON。最典型的一个问题就是订单详情接口的响应时间在高峰期会超过三秒但没有任何人说得清楚这时间花在了哪里。因为系统没有日志链路只有一个很原始的log文件里面混杂着业务日志、框架错误和定期任务的输出。Dirk在现场描述了一个场景运维想查一个用户投诉的订单问题得先SSH到服务器上用grep翻几万行日志再凭着经验猜是哪一段逻辑出了问题。这个场景其实很多团队都经历过但很少有人意识到系统的技术债务已经严重到连排查问题本身都变成了一种高风险操作。那为什么一直没重构团队的理由很典型业务不能停、领导不想冒险、核心开发不敢接手。这其实是很多老项目的死循环——系统越烂越没人敢动越没人动系统越烂。Linus在节目里说了一句话我很认同重构最大的阻力从来不是技术难度而是团队对改动这件事已经形成了恐惧。这个恐惧会让所有人选择在烂代码上继续打补丁直到某一天补丁也打不上去了。真正让团队下定决心重构的不是技术部门的推动而是一次线上事故。某个大促活动的前一天晚上系统突然出现大量订单超时用户重复提交产生了大量脏数据最后导致第二天早上对账报表直接跑不出来。这个事故的根因其实很简单就是一条SQL在数据量增长后索引失效但因为没有监控、没有告警、没有链路团队花了四个小时才定位到。那个晚上之后管理层终于同意立项做技术改造条件是不能停业务、不能丢数据、不能影响现有的对外接口。这个前提条件非常关键。它决定了整个项目的技术选型和改造路径也让Dirk和Linus在节目里给出了一个明确建议如果你的重构项目也有不能停这个前提那微服务拆分基本不要考虑模块化单体加数据层面的渐进式迁移才是正路。这期节目后半段所有的讨论其实都是围绕这个前提展开的。2. 复盘那次把数据库连接池打爆的深夜完整排查链路节目里最有价值的一段是Dirk讲他亲历的一次事故。当时他在某团队帮忙做重构的前期评估系统还没正式迁移只是先加了一层读写分离结果上线当晚数据库连接池就被打爆了。这个事故特别典型因为它不是重构本身造成的而是大家在重构前最容易忽略的一个环节虽然业务代码没有变但基础设施的调用方式变了导致原本的瓶颈被放大。当晚的现象是这样的数据库连接池的活跃连接数从平时的两百多一下子飙到两千多数据库CPU长时间跑满部分订单接口开始超时。团队的第一反应是加连接数结果越加越糟因为数据库端已经没有余力处理这么多连接了。这时候如果继续往上堆资源只会让系统更快崩溃。Dirk说他接手排查之后做的第一件事不是看代码而是把所有监控指标按时间轴对齐画出一张从变更发生到故障出现的关联图。因为只有先搞清楚是什么变更触发了故障才不会被表象带着走。排查过程大概是这样的第一步确认变更范围。当晚唯一的上线动作是开启了读写分离业务代码没有发布新版本所以问题大概率出在数据访问层。第二步对比变更前后的慢查询日志。结果发现一个现象变更前很多查询虽然慢但都走的是主库变更后这些查询被路由到了从库但部分从库的配置远低于主库导致本身就慢的查询变得更慢。第三步追踪连接池的获取等待时间。发现大量线程在等待获取数据库连接而这个等待时间又反过来占用了Tomcat的线程池导致HTTP请求也大面积堆积。第四步拿到完整证据链后回滚读写分离开关。系统在十五分钟内恢复正常。这个事故的根因并不复杂就是读写分离的灰度策略没有做好。当时团队把所有读流量一次性切到了从库没有分批、没有观察期、没有自动回滚机制。而更底层的问题在于连接池的最大连接数设置是从旧系统里继承下来的从来没有人重新算过。Linus在节目里补了一句很扎心的话这个事故本来可以用一个很简单的开关避免但因为没有压测没有人知道从库的承受上限是多少上线的时候就只能靠赌。所以他在节目里给了一个很具体的建议任何涉及基础设施变更的上线都必须先在预发环境做一次全链路的压测压测的流量模型要和生产保持一致不能只测接口要看数据库连接数、线程池活跃数、GC停顿和CPU的联动关系。这段内容单独拿出来看也成立。如果你现在正准备做读写分离、分库分表、缓存改造这类基础层变更我建议你先把上面这几步走一遍。不要觉得我们改动很小不会出问题事故往往就发生在没问题这个判断上。3. 技术选型复盘为什么放弃微服务选择了模块化单体这期节目花了很大的篇幅讨论一个核心问题为什么这个项目最后没有选择微服务架构。这个讨论的背景是当时团队里有很多人主张直接把系统拆成十几个微服务认为这样才能一劳永逸。Dirk和Linus都没有完全否定微服务但他们对这个老项目的判断非常一致在业务团队只有五六个人、没有专职运维、也没有完整可观测性体系的条件下微服务化大概率会加速系统的崩溃。他们的分析逻辑是这样的微服务化表面上解决的是代码耦合问题但它同时引入了三个新问题——网络调用带来的延迟和故障点、分布式事务的复杂度、以及服务间依赖关系的治理成本。这三个问题在没有足够基础设施支撑的团队里几乎是无解的。而模块化单体可以保留单体架构在部署、调试、事务上的优势同时通过代码层面的模块边界来逐步梳理业务逻辑让团队在没有太大运维压力的情况下完成结构性的优化。这是一个非常务实的选型判断。我把他们在节目里给出的对比整理成了一个表格项目 | 微服务拆分 | 模块化单体 团队成本 | 需要运维、中间件、链路追踪等基础能力 | 现有团队即可承接 事务处理 | 需引入分布式事务方案风险高 | 原生的本地事务简单可靠 部署方式 | 多服务独立部署依赖关系复杂 | 单应用部署回滚成本低 演进路径 | 从初期就要确定服务边界 | 模块边界可逐步调整 适合团队 | 有专门平台团队支撑 | 业务研发团队为主这个表格的信息密度很高但我建议你关注的不是哪个更好而是哪个更适合你当前的处境。模块化单体的核心不是不分拆而是先把代码的逻辑边界搞清楚再决定物理边界。这也是他们给那个团队定的第一阶段目标不急着把系统拆成多个服务而是先在同一代码库内把订单、用户、商品、支付这些业务模块彻底隔离模块之间只允许通过接口通信禁止跨模块直接访问数据库表。这个阶段看起来不起眼但实际上最难。因为老系统最糟糕的往往不是代码质量而是数据的耦合。比如订单模块和用户模块共用一套表支付记录又散落在好几个表里。这种数据层面的耦合不通过代码层面的模块化梳理根本发现不了。所以那期节目里他们把这种改造叫做先立规矩再拆楼。他们在节目里还反复强调了一个观点模块化单体不是终态它是一个过渡态。将来如果某个模块的流量和团队规模确实大到需要独立部署了由于模块边界已经清晰拆出去的成本会低很多。但如果你一开始就硬拆边界还没想清楚就把代码拆到不同仓库那后面想合并回来就难了。这段选型分析对于正在做架构决策的团队非常有参考价值。它提醒我们架构选型不是选择题而是条件题。脱离团队现状和基础设施条件去谈架构容易掉进别人都在用所以我们也要用的陷阱。4. 从代码到数据重构过程中最容易被低估的三个隐藏成本节目后半段讨论了一个很有深度的话题重构的真正成本往往不在代码重写而在代码之外。Dirk和Linus花了很长时间聊这个因为他们发现大多数重构项目延期不是因为写代码慢而是因为一些没有提前预估的工作突然冒出来。第一个隐藏成本是数据迁移。很多重构项目在启动时只考虑了代码层面的改动没有意识到历史数据的新旧结构转换本身就是一个大工程。比如这个订单系统旧表里存了十几年未清理的日志型数据光是把这些数据按新结构重新归档和清洗就花了团队整整三周。而且这个过程还不能影响线上读写只能在夜间任务里分批跑。Dirk在节目里打了个比方你以为你在装修房子结果发现地基底下埋着一堆前人留下的管道光是摸清楚这些管道的走向就花了一个月。第二个隐藏成本是接口兼容。由于系统不能停所有旧的对外接口都必须继续工作。这就意味着你在改造内部结构的同时还要维护一套兼容层。比如用户手机上装的老版本App还在用旧的接口协议你不能因为重构就把这个协议改掉。最麻烦的是那些你根本不知道谁在调的接口可能某个合作方三年前接入后就再也没更新过但你还是得保证它不报错。这个工作量在项目评估时几乎不会被人想到但它真实存在而且常常会拖到最后才爆发。第三个隐藏成本是数据的语义对齐。这是最抽象、也最容易翻车的一环。老系统经过多年迭代同一个字段在不同时期被赋予了不同的含义。比如状态这个字段最早只有待支付和已支付两个值后来加上了已退款部分退款售后中再后来又混入了来自另一个系统的状态码。当重构团队想把这些状态统一成一套新枚举的时候就必须逐条核对历史数据弄清楚每一个值在每一个时期到底代表什么。这个过程没有算法可以替代只能靠人肉梳理。很多重构项目卡住就是因为没人愿意做这个脏活但你又绕不开它。Linus在节目里给了一个特别实用的建议在做重构排期的时候不要只排代码开发的时间要把数据迁移、接口兼容、语义对齐这三件事单独拿出来排期。而且这三件事的工作量估计要乘以二因为老系统的数据永远比你想象的更脏老接口的调用方永远比你记的更多。他说完这句话之后Dirk接了一句如果排期里没有这三项那说明你还没完全理解你接手的这套系统。这句话可能有点绝对但我自己做过几个类似的改造项目之后真的觉得它是一句大实话。5. 节目之外的思考重构中最容易被低估的人的问题写到这里我发现这期节目还有一条暗线值得单独说一说。Dirk和Linus在节目里虽然没有专门为人的问题开一个章节但他们在很多细节里都在讲同一件事技术债务的根源往往是组织和协作的问题而不只是代码的问题。比如他们提到这个订单系统之所以会积累这么多技术债是因为很长一段时间里团队没有明确的代码负责人。每个人都是临时改一下改完之后也不知道自己改的是什么模块久而久之代码的所有权就彻底消失了。所以他们在给团队的第三阶段建议里特别强调了一件事每个模块必须有一个明确的负责人这个负责人要对模块的代码质量、数据结构和对外接口负责。这不是一个管理上的形式主义而是技术系统能够长期健康运行的必要条件。另外他们还观察到一个现象重构项目最容易失败的节点不是技术攻坚期而是业务方的耐心耗尽期。因为重构不像新功能上线业务方看不到立竿见影的效果他们只会觉得你们改了半天用户没感受到任何变化。这时候如果技术团队不能用业务语言解释重构的价值项目很容易被叫停或者被无限期搁置。Dirk给了一个很实际的做法给重构项目设定一个业务可感知的里程碑不要埋头做一年才汇报一次。比如先把最影响用户体验的订单详情接口响应时间降低到200毫秒就是一个好的里程碑而不是我们完成了模块化拆分的第一阶段。因为前者业务方能听懂后者只有技术团队自己知道含义。如果你正好也在推动一个重构项目这个建议可以直接用。还有一个细节让我印象很深。节目里有人问Dirk你做了这么多年的重构项目有没有一个判断标准能提前知道一个项目会不会成功。他想了一下说看这个团队接到一个线上问题时的第一反应。如果第一反应是先查一下谁改的那这个重构大概率会失败。如果第一反应是先看监控、看日志、看链路定位问题的影响范围那这个重构就有戏。因为前者代表团队还在用追责的方式处理问题后者代表团队已经开始用系统的方式解决问题。重构的本质其实就是把系统从靠人记、靠经验猜的状态变成靠工具、靠流程、靠可观测性的状态。这个观点我越想越觉得准确。做技术的人往往容易陷入对工具和架构的执念却忽略了一个基本事实系统是人在维护的人的协作方式决定了系统的演进方式。工具再好如果团队没有一个正向的协作习惯技术债早晚还会长回来。这也是这期节目除了那些硬核技术点之外最值得带走的收获。