查看原文
其他

人间真实!一行代码引发的恐惧

大飞码字 程序人生 2019-04-08

作者 | 大飞

本文经授权转自大飞码字



我工作的前 5 年,都是从事基础系统研发相关的工作。做过后台的接入层、后台的存储系统、RPC 框架。说来不怕你笑话,那个时期里,我对代码一直有一种恐惧感。这种恐惧是怎么来的呢?且让我慢慢道来。

我们所构建的基础系统,都是使用在亿级甚至十亿级用户产品的业务系统之上的。从客户端(前端)到后台业务逻辑层,再到基础架构层,所写的代码是跑在整个调用链路的最后端的。

你可以认为,几乎每个用户的每个请求都会跑到我们写的那部分代码。

这个对系统带来的影响是: ① 代码出问题后,影响的用户范围会很大;② 在这亿级甚至十亿级用户量的情况下,每天所带来的请求可能是千亿级,万亿级的,在如此庞大请求量的情况下,几乎各种奇葩的异常,你都会遇到,代码要极其的健壮,一个小异常没处理好就会带来大麻烦。

这让我想起我们故障时候的情形。

几年前,我们每做完一次版本变更,晚上基本都会睡不好,担心变更的代码有问题。对手机的报警短信特别敏感,一有风吹草动,立马就会打开电脑 VPN 看看,即使是在深夜凌晨的时候。

我自己有个习惯,每次变更完,都要间隔几个小时去看看监控曲线和日志,看看有没有异常的苗头,一旦发现有不对劲地方,就会立即着手排查,直到确保没有问题为止。

不过即使如此,还是不可避免的会出现问题。

半夜两三点的时候,你的手机突然响起,报警语音机器人跟你说,你有一个重要的监控曲线出现异常,请查看。然后你的血压立马升高,心跳加速,你从床上,一跃而起,打开电脑,连上公司的 VPN,立马着手排查起来。

几分钟后,QA(质量工程师)电话过来,告知你,这个故障目前已经上报到部门故障系统,目前影响的用户有 XXX 的数量,请你加快处理速度,然后你的心跳再次加速。

半小时后,终于有了眉目,这时,你的 leader 电话过来,询问你是怎么回事,大概还需要多长时间才能处理完毕。待你语焉不详地回复完你的 leader, 你又开始埋头,一行行地排查故障。

一个小时后,你终于将问题定位出来,执行了故障处理方案,例如回滚新的代码,或者屏蔽某些机器等。你才终于有了喘息的时间。

P.S. 这里正确的流程是,出问题后,立马回滚代码,但存储系统因为数据的关系,在没有确定原因前不太敢回滚,怕对数据有影响。

你赶紧爬上床去,睡上 2-3 个小时,因为第二天还要早起,赶到公司,去处理故障的后遗症,数据损坏和数据错乱。



那个时期,我们写代码都特别小心,变更更是极度的谨慎。所以使得自己对代码变更有了一种焦虑和恐惧的心理。至少在那时候,写代码不是一件轻松的事情。

这个事情,我现在回过头来看。你可以认为有一部分是人的原因,但仔细的想想,写代码不出 bug ,几乎也是极难做到,所以这里在研发流程上,其实也是有缺失的。

前期因为业务发展太快,团队的整体人力跟不上,所以,一开始很多流程都是很原始的,那时候,是想做但客观条件不允许。

后来,业务稳定了,流程就规范了不少。比如引入了 Coverity 的代码检查,也推行过测试用例覆盖、持续集成等。

但最终,并不是所有的流程都延续了下去。比如,代码测试用例覆盖,有的团队到后面就放弃了,需求变化太快,测试用例成本太高。

Coverity 倒是自动化程度高,没啥人力投入,执行了下来。

但我相信不是所有公司或所有团队,都会有这种规范的流程。一个是研发流程成熟度建设的问题,但除此,还有成本、业务迭代速度。在互联网,产品高速迭代的时候,产品都还没有存活下来,成熟流程就更不太可能有了。

综合来看,一种规范但相对较重的研发流程的建立,应该也是根据具体情况而定的。需要考虑产品的形态、产品迭代的速度、团队的人力预算成本、产品的生命周期等等。当然,无论怎么说,反正这不是个人可以决定的事情,如果你所在的团队有完善的研发流程,那最好,但如果没有那么完善,自己又能够做些什么呢?

我的经验来看,以下的一些措施,对于个人而言也有不错的效果:

测试驱动的开发(TDD)

有段时间,因为业务高速发展,对性能的要求不断提高,存储模型也跟随着不断迭代改进,所以那段时间的代码修改是比较多的,焦虑感也特别重。

我记得是在 《重构:改善既有代码的设计》中了解到 TDD 的。

简单来说, 就是先构建测试用例,再开始写你的功能代码。在设计测试用例之前,你需要先定义好模块对外的接口,包括接口的种类、参数、返回值等。

然后,你针对定义好的接口,编写测试用例。这过程中,你可能会发现接口设计不合理的地方,也需要随着修改。待你测试用例写完,基本你的接口也被修改得比较好了,所以 TDD 还能改善你的接口设计。

后续再为每个接口实现特定的代码逻辑。

我当时将这种方法运用到了一个磁盘存储引擎中,发现相当不错。我特地花了一周左右的时间写测试用例。后面,每天实现部分的功能后,都立马跑测试用例,每次跑完通过,你的心里都有稳了的感觉,好像妈妈再也不担心我写的代码有 bug——被老板怼——导致扣工资了。

因为有了完善的测试用例,而且随着你测试用例不断增加和覆盖,你的信心会越来越足,焦虑自然减少了很多。

不过这种方式,比较适合底层的系统和核心稳定的系统。对于需求多变的系统,构建测试用例的人力付出太大,而且需求一变,已有的测试用例可能失效,导致投入产出比不够高。

灰度发布

简单来说,就是一个特性要上线的时候,不是一下就开放给所有的用户使用。有点像产品上的内测,只不过是用在技术上。

比如我新增加了一个产品需求,例如就微信里面的 “看一看”入口,不是一开始就对所有用户开放的。

首先会上线一个新的客户端版本,代码逻辑已经预埋,但设计了一个开关,对所有用户都是关闭的。前期,可能会找个千分之一,甚至万分之一的用户(随机或者特定的用户群体),让他们使用。

这过程中,收集各种 Log、监控、用户的反馈。来确认和 fix 系统存在的各种问题。一般经过两三周后,如果没有大问题,就会进一步放开使用的用户。比如变成百分之一、十分之一,一直迭代,直至覆盖全部用户。

灰度这个思想,在互联网特别常用。客户端、前端、后台都可以使用。比如后台,上线一个新修改后,也不是一下就开放给所有用户。而是按照某种规则,例如以 QQ 用户为例,可能是这种规则:

计算Hash值(QQ号) % 1000 <= 灰度用户的比例(取0 --- 1000)

放量的最小力度就是千分之一,被灰度到的用户,看到新功能,没灰度到的用户不受影响。

这招用在新功能、系统优化、代码重构上都很不错。

付出的额外成本不大,有的公司有自研的灰度系统,那最好。没有的话,在重大且没有把握的功能上,自己加上几行灰度控制代码也不难。

监控和 Log

监控和 Log 不是什么新鲜的东西。

工作第一年,我们的技术总监在一次会议上跟我们说:你写完的代码是死的,只有在线上跑的代码是活的。监控和 Log(特别是监控),就像是你代码的体征信息,随时反应着你代码在实际环境中的运行情况,要高度重视。

这段话,在后面,我深有感触。通过完善设计的监控和 Log,预先发现了很多的问题,也避免了很多或代码 bug 或系统设计缺陷导致的重大故障。

后面,监控和 Log 的设计,也成了我们方案设计的一部分。一般都会在方案最后加上必须的监控的点和 Log 点,例如请求数、成功数、失败数、各种异常数、极端逻辑执行次数等等。

你应该要意识到监控和 Log 的重要性,而且应该要花时间特别设计。

经过良好设计的监控和 Log,能发挥的价值,是那种凭感觉随便加的监控 Log 不可比拟的。

双写,双读验证

这招,新业务代码用的不多。更多用于基础系统或者核心系统的优化和重构。而且有前置条件,需要一个操作可以重复执行(例如只读操作和幂等的数据操作)。

简单来说,就是将新旧代码,划分为两个流程(两个接口),上线到实际环境,然后在同个模块里调用。

一个请求进来后,两个流程分别执行一次,逐字节做对比(例如 memcmp)新旧流程的结果。新流程的结果只用于对比,返回的依旧是旧流程的结果,所以不影响线上业务。

如果对比失败,就可能存在异常,要查找并解决,在实际环境跑了几天后,都没问题,就可以采用灰度的方式,进一步放量。不过,一般业务不常使用,在基础系统上使用比较多,这里就不展开了。

其他

另外,对于客户端,还有热补丁机制,客户端 Log 收集系统等。不过这种需要的开发量比较大,一个人不一定可以搞定,可能需要有个小团队来完成。

最后,软件工程是个庞大的话题,我也没能力论述这么大的话题。这里给大家讲了个以前的故事,并且分享了我常用的一些低成本,但可以提高线上代码质量的方法,给大家参考参考。大家有好的做法,也欢迎在留言里分享出来。

声明:本文经作者授权转载,如需转载请联系原作者。


 热 文 推 荐  

戳他↓↓↓

☞ 工作 996,生病 ICU!这一次,程序员不忍了!

 Python 爬取 394452 条《都挺好》弹幕数据,发现弹幕比剧还精彩?

甜蜜暴击!程序员为媳妇儿做了个记录心情的小程序

JavaScript 力压 Java 成最受欢迎编程语言,TypeScript 大涨!

☞ 14 岁编程神童?假的!

☞ NLP泰斗董振东老师与他的知网 | 纪念

☞ 现实!程序员只有跳槽才能涨薪吗?

☞“V神给了我1000个ETH, 我用来招了两个程序员” 独立开发者做到极限就是Paul Hauner | 人物志

System.out.println("点个在看吧!");
console.log("点个在看吧!");
print("点个在看吧!");
printf("点个在看吧!\n");
cout << "点个在看吧!" << endl;
Console.WriteLine("点个在看吧!");
Response.Write("点个在看吧!");
alert("点个在看吧!")
echo "点个在看吧!"

你点的每个“在看”,我都认真当成了喜欢

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存