作者简介: 魏智博,卡耐基梅隆大学毕业,有多次创业经历,并且在硅谷大小公司有六七年的工作经验。回国加入达达后主要负责业务架构和后端的重构工作。
对于很多互联网公司来说,每次大型活动都是对内部系统的一次大考验。就好像明星八卦之于新浪微博,双十一对于达达京东到家来说,也是检验我们内部系统的一次好机会。
我之前曾经参与过多个大型系统(包括日访问量十亿级别的系统)的重构,并且加入达达京东到家后的第一件事就是投身于各大业务系统的重构中。
很高兴的看到这些重构后的系统在双十一中经受住了考验,在各方面指标表现都显著优于老系统的同时,也证明了我们耗费时间人力去做系统重构的意义所在。所以也想借这个机会和大家聊聊我对系统重构的一些感悟,和可能决定重构项目最后是否成功的几个关键问题。
构建一个系统就好比是在建房子。项目初期因为时间、人力和资金限制,以及需求简单,大部分时候大部分团队都会选择搭建一个可以快速投入运作的小草房。并且随着公司的发展以及业务的增长,团队会逐步在小草房上进行修缮,努力让他符合公司发展的需要。
但是在真实的世界中,我们很快会发现我们心爱的小草房被打上了一层一层的补丁,并且由于住进了更多的人,他会变的越来越不牢固,每次进行修缮的风险和成本也直线上升,每每需要新加一块砖的同时都会承担着一面墙倒塌的危险。
更糟糕的是,在公司的年度计划里,我们的小草房在年底需要变得像军用碉堡一样牢固,而且要和竞争对手那98层的摩天大楼有相同的承载人数能力。
这可如何是好 - 设计阶段
“重构” 这两个字一定是大多数有经验的工程师对上面出现的问题给出的答案。遗憾的是,在如何进行重构的细节计划这个问题上,一些工程师只会给出工具层面的答案,比如使用混凝土砖来代替我们之前所用的黏土砖,或者使用昂贵的钢筋水泥结构。
但是其实除了工具层面的问题,还有很多其他更重要的问题需要去仔细考虑和设计:
1. 生产资源限制
每次重构都会一定程度上减慢实现新的需求的速度,但是从市场角度上来说,产品需要快速迭代才能保证公司不会在竞争中处于下风。所以在大部分情况下留给重构的时间并不会很充裕。在时间面前,其实我们会发现很多技术问题其实根本都不能称之为问题。
同时也需要考虑团队内的人手和人员构成:创业公司招人一般都不是很容易,而且新进来的同学也不能快速投入生产。所以如何在时间人力都有限制的情况下,对重构的计划进行现实合理的规划是最先需要考虑的问题。
我亲眼所见一些失败的重构项目都是因为在有限的资源下却设置了激进的计划,导致在摩天大楼盖到一半的时候大部分团队已经累瘫在了路上。有时在计划中适当的做减法也是确保最终重构可以高质量按时顺利完成上线的必要措施。
2. 老系统的痛点
每个系统都会有自己的特点,并且在我们决定对其进行重构时,我们肯定是因为有想要通过重构急需去解决的问题。就达达的老订单系统举例来说,由于业务逻辑流程异常复杂,但是各个模块耦合在一起,导致现在每次新加新的功能所需要花费的时间和承担的风险都非常大,已经到了几乎无法维护的阶段;以及我们在双十一期间的流量将会暴增,一旦系统崩溃就会导致线上线下的混乱,所以我们需要保证系统可以在大流量的情况下也能平稳运行。所以我们在重构时着重对可维护扩展性以及系统性能做了非常深入的设计,以保证重构后的系统可以达到我们设置的目标。
我们开始吧 - 开发阶段
在梳理完需求,做完最初的系统性设计之后,我们终于开始进入正式的编码阶段了。
重构系统的底线是支持原系统的所有核心功能,换句话说,如果重构后的系统还不如老系统的话,那他一定是失败的。为了确保我们重构的项目最终能至少取得最基本的“成功”,抛开技术细节不谈,在整个的开发过程中,我们需要重点关注几件事情:
1. 单元测试的防护罩
为了让老系统的业务逻辑都能在重构后系统中无误运行,第一件要做的事情就是为老房子加一层防护脚手架,以保证重构过程中不会坍塌,也不会让砖头飞到大街上砸到行人。
在计算机系统重构中,这层防护罩就是单元测试。如果老系统的所有关键逻辑点都通过单元测试保护起来,并且重构过程中自始至终都能确保这些测试在新系统中也都能通过的话,我们基本上可以有80%的信心可以确认这次重构可以达到最基本的目标。
2. 分层与解耦
如前所述,重构项目过程会大幅减缓新产品需求的实现,尤其是在创业公司中,每一次重构代码提交都是和时间赛跑。所以我们要极力避免任何可以降低工程师效率的事情。
在构建一个大型工程的过程中,最常出现的是工程师之间互相阻塞的情况,举例来说,如果一个工程师需要实现的模块如果强依赖于另外一个工程师正在编写的代码的话,那么很多的生产力就会被浪费。
解决这个问题的一个好的办法是在项目开工伊始就通过空接口和空方法将整个项目“起承转合”的结构定义明晰,并且尽最大努力确保需要实现的不同模块之间不会有任何强耦合(比如可以使用临时的中间数据容器的方式来保证下层模块的输入不直接依赖于上层的输出)。只要在我们所要构筑的房子中搭建起了这样清晰的层级关系,那么在每层工作的工程师大部分时间都完全不需要去关心别的工程师都在做什么,只需要自由的发挥自己的能力去完成自己本层的工作就可以。
行百里者半九十 - 测试上线阶段
编码工作终于完成,我们的单元测试也全部通过,就差让我们老破小的草房停工并且向客户交我们的新房了,大家以为可以长出一口气好好休息一下了。可惜事情永远没有想象中的那么美好。
在开始交付新房的那一刻起,你会发现各种意想不到的问题会源源不断的发生,比如突然发现某层楼无法正常供水,顶楼的网络在住户多了的情况下会变得非常不稳定,或者七楼的卫生间在一个月后开始出现裂缝并且漏水的情况。很快的有一些住户开始抱怨新房住起来还不如老的房子舒服,甚至有一些极端的住户开始嚷嚷着要退房退款。
重构后的新系统和老系统的交接过程,就好像在比赛的过程中为F1赛车更换轮胎一样,我们不应该因为更换轮胎而对比赛进程产生任何负面影响。有几种非常有效的方法可以帮我们做到这一点:
1. 宏观与微观的监控
拥有在第一时间洞悉系统中任何一个角落发生的事情的能力,对于任何系统来说都是非常重要的。尤其对于重构系统的交接过程来说,所有监控的数据都会成为我们评估效果,快速发现并且解决问题的重要依据。
监控需要包括宏观与微观两个不同维度,也就是说不但需要在各个重要角落都加上摄像头,存储所有的监控视频(并且保证硬盘不会损坏),还需要对可能影响住户满意度的各项重点参数有一个宏观的把握,比如室内的平均温度,物业服务的平均响应速度等等。
2. 实验组与对照组
在真正进行新老系统交接之前,我们可以进行两阶段的对比实验来确保新系统对用户可能产生的负面影响最小,并且能保证新老系统的交接可以平滑进行。
a. 第一阶段可以称为隐身模式,在老系统仍在线上接受全部请求的同时,我们可以将请求同时克隆一份发至新系统,然后通过对比新老系统对于同一份输入的输出是否相同来判断是否新系统可以如预想一样正常工作。(这里可能需要对新系统做一些特殊处理以保证某些关键路径如扣费,发通知等不会被触发多次)
b. 第二阶段可以称为灰度模式,在第一阶段顺利通过后,新系统可以开始逐步分担一部分线上流量。举例来说,刚上线时新系统可以先承担百分之一的线上流量,通过密切监控日志和用户反馈等信息来决定是需要增大还是减少流量,一步一步直到我们的新系统完全替换老系统为止。这样做的好处是在一方面可以将可能的负面影响降到最低,另一方面可以尽早的发现性能方面的可能缺陷(因为性能问题是我们在之前阶段都很难顾及和测试到的),并且在其产生大面积影响之前可以进行及时修复。
重构只是对现有系统进行改造,并不涉及功能性的调整,所以从短期看来他不会带来任何直接效益。但是从长远来看,系统重构却能显著提高系统的可维护性,以及为公司业务的增长提供更坚实的保障。