一行代码引发的恐惧
(给程序员的那些事加星标)
转自:大飞码字,作者:大飞
1
我工作的前 5 年,都是从事基础系统研发相关的工作。做过后台的接入层,后台的存储系统,RPC 框架。说来不怕你笑话,那个时期里面,我对代码一直有一种恐惧感。这种恐惧是怎么来的呢?且让我慢慢说来。
我们所构建的基础系统,都是使用在亿级甚至十亿级用户产品的业务系统之上的。从客户端(前端)到后台业务逻辑层,再到基础架构层,所写的代码是跑在整个调用链路的最后端的。
你可以认为,几乎每个用户的每个请求都会跑到我们写得那部分的代码。
这个对系统带来的影响是: 一,代码出问题后,影响的用户范围会很大;二,在这亿级甚至十亿级用户量的情况下,每天所带来的请求可能是千亿级,万亿级的,在如此庞大请求量的情况下,几乎各种奇葩的异常,你都会遇到,代码要极其的健壮,一个小异常没处理好就会带来大麻烦。
这让我想起来,我们故障时候的情形。
几年前,我们每做完一次版本变更,晚上基本都会睡不好,担心变更的代码有问题。对手机的报警短信特别敏感,一有风吹草动,立马就会打开电脑vpn看看,即使是在深夜凌晨的时候。
我自己有个习惯,每次变更完,都要间隔几个小时去看看监控曲线和日志,看看有没有异常的苗头,一旦发现有不对劲地方,就会立即着手排查,直到确保没有问题为止。
不过即使如此,还是不可避免的会出现问题。
半夜两三点的时候,你的手机突然响起,报警语音机器人跟你说,你有一个重要的监控曲线出现异常,请查看。然后你的血压立马升高,心跳加速,你从床上,一跃而起,打开电脑,连上公司的VPN,立马着手排查起来。
几分钟后,QA(质量工程师)电话过来,告知你,这个故障目前已经上报到部门故障系统,目前影响的用户有XXX的数量,请你加快处理的速度,然后你的心跳再次加速。
半小时后,终于有了眉目,这时,你的leader, 电话过来,询问你是怎么回事,大概还需要多长的时间,才能处理完毕。待你语焉不详地回复完你的leader, 你又开始埋头,一行行的排查故障。
一个小时后,你终于,将问题定位出来,执行了故障处理方案,例如回滚新的代码,或者屏蔽某些机器等。你才终于有了喘息的时间。
(ps: 这里正确的流程是,出问题后,立马回滚代码,但存储系统因为数据的关系,在没有确定原因前不太敢回滚,怕对数据有影响)
你赶紧爬上床去,睡上2-3个小时,因为第二天还要早起,赶到公司,去处理故障的后遗症,数据损坏和数据错乱。
2
那个时期,我们写代码都是特别小心的,变更,更是极度的谨慎。所以使得自己对代码变更有了一种焦虑和恐惧的心理。至少在那时候,写代码不是一件轻松的事情。
这个事情,我现在回过头来看。你可以认为有一部分是人的原因,但仔细的想想,写代码不出bug ,几乎也是极难做到,所以这里在研发流程上,其实也是有缺失的。
前期因为业务发展太快,团队的整体人力跟不上,所以,一开始很多流程,都是很原始的,那时候,是想做但客观条件不允许。
后来,业务稳定了,流程就规范了不少。比如引入了coverity的代码检查,也推行过测试用例覆盖,持续集成等。
但最终,并不是所有的流程都延续了下去。比如,代码测试用例覆盖,有的团队到后面就放弃了,需求变化太快,测试用例成本太高。
coverity倒是自动化程度高,没啥人力投入,执行了下来。
但我相信不是所有的公司,所有的团队,都会有这种规范的流程。一个是研发流程成熟度建设的问题,但除此,还有成本,业务迭代速度。在互联网,产品高速迭代的时候,产品都还没有存活下来,成熟流程就更不太可能有了。
综合来看,一种规范,但相对较重的研发流程的建立,应该也是根据具体情况而定的。需要考虑产品的形态,产品迭代的速度,团队的人力预算成本,产品的生命周期等等。当然,无论怎么说,反正这不是个人可以决定的事情,如果你所在的团队有完善的研发流程,那是最好的事情,但如果没有那么完善,自己又能够做些什么呢?
我的经验来看,以下的一些措施,对于个人而言也有不错的效果:
测试驱动的开发(TDD)
有段时间,因为业务高速发展,对性能的要求不断提高,存储模型也跟随着不断迭代改进,所以那段时间的代码修改是比较多的,那个时间的焦虑感也特别重。
我记得是在 《重构:改善既有代码的设计》中了解到TDD的。
简单来说, 就是先构建测试用例,再开始写你的功能代码。在设计测试用例之前,你需要先定义好模块对外的接口,包括接口的种类,参数,返回值等。
然后,你针对定义好的接口,编写测试用例。这过程中,你可能会发现接口设计不合理的地方,也需要随着修改。待你测试用例写完,基本你的接口也被修改的比较好了,所以TDD还能改善你的接口设计。
后续再为每个接口实现特定的代码逻辑。
我当时将这种方法运用到了一个磁盘存储引擎中,发现相当不错。我特地花了一周左右的时间写测试用例。后面,每天实现部分的功能后,都立马跑测试用例,每次跑完通过,你的心里都有稳的一B的感觉。有种妈妈再也不担心我写的代码有bug,被被老板叼,导致扣工资了。
因为有了完善的测试用例,而且随着你测试用例不断的增加和覆盖,你的信心会越来越足,焦虑自然减少了很多。
不过这种方式,比较适合底层的系统和核心稳定的系统。对于需求多变的系统,构建测试用例的人力付出太大,而且需求一变,已有的测试用例可能失效,导致投入产出比不够高。
灰度发布
简单来说,就是一个特性要上线的时候,不是一下就开放给所有的用户使用。有点像产品上的内测,只不过是用在技术上。
比如我新增加了一个产品需求,例如就微信里面的 “看一看”入口,不是一开始就对所有用户开放的。
首先会上线一个新的客户端版本,代码逻辑已经预埋,但设计了一个开关,对所有用户都是关闭的。前期,可能会找个千分之一,甚至万分之一的用户(随机或者特定的用户群体),让他们使用。
这过程中,收集各种log ,监控,用户的反馈。来确认和fix 系统存在的各种问题。一般经过两三周后,如果没有大问题,就会进一步的放开使用的用户。比如变成百分之一,十分之一,一直迭代,直至覆盖全部用户。
灰度这个思想,在互联网是特别常用的。客户端,前端,后台都可以使用。比如后台,上线一个新修改后,也不是一下就开放给所有用户。而是按照某种规则,例如以QQ用户为例,可能是这种规则
计算Hash值(QQ号) % 1000 <= 灰度用户的比例(取0 --- 1000)
放量的最小力度就是千分之一,被灰度到的用户,看到新功能,没灰度到的用户不受影响。
这招用在新功能,用在系统优化,代码重构上都很不错。
付出的额外成本不大,有的公司有自研的灰度系统,那最好。没有的话,在重大且没有把握的功能上,自己加上几行灰度控制代码也不难。
监控和log
监控和log不是什么新鲜的东西。
工作第一年,我们的技术总监在一次会议上跟我们说:你写完的代码是死的,只有在线上跑的代码是活的。监控和log(特别是监控),就像是你代码的体征信息,随时反应着你代码在实际环境中的运行情况,要高度的重视。
这段话,在后面,我深有感触。通过完善设计的监控和log,预先发现了很多的问题,也避免了很多,或代码bug,或系统设计缺陷导致重大故障。
后面,监控和log的设计,也成了我们方案设计的一部分。一般都会在方案最后,加上必须的监控的点和log点,例如请求数,成功数,失败数,各种异常数,极端逻辑执行次数等等。
你应该要意识到监控和log的重要性,而且应该要花时间特别地设计。
经过良好设计的监控和log,能发挥的价值,是那种凭感觉随便加的监控log不可比拟的。
双写,双读验证
这招,新业务代码用的不多。更多用于基础系统或者核心系统的优化和重构上面。而且有前置条件,需要一个操作可以重复执行(例如只读操作和幂等的数据操作)。
简单来说,就是将新旧代码,划分为两个流程(两个接口),上线到实际环境,然后在同个模块里面调用。
一个请求进来后,两个流程分别执行一次,逐字节做对比(例如 memcmp)新旧流程的结果。新流程的结果只用于对比,返回得依旧是旧流程的结果,所以不影响线上业务。
如果对比失败,就可能存在异常,要查找并解决,在实际环境跑了几天后,都没问题,就可以采用灰度的方式,进一步放量。不过,一般业务不常使用,在基础系统上使用比较多,这里就不展开了。
其他
另外,对于客户端,还有热补丁机制,客户端log收集系统等。不过这种需要的开发量比较大,一个人不一定可以搞定,可能需要有个小团队来完成。
最后
软件工程是个庞大的话题,我也没能力论述这么大的话题。这里给大家讲了个以前的故事,并且分享了我常用的一些低成本,但可以提高线上代码质量的方法,给大家参考参考。大家有好的做法,也欢迎在留言里分享出来。
Comments
Post a Comment
https://gengwg.blogspot.com/