Skip to main content

一行代码引发的恐惧

一行代码引发的恐惧

大飞码字 程序员的那些事 Yesterday
(给程序员的那些事加星标
转自:大飞码字,作者:大飞

我工作的前 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

Popular posts from this blog

CKA Simulator Kubernetes 1.22

  https://killer.sh Pre Setup Once you've gained access to your terminal it might be wise to spend ~1 minute to setup your environment. You could set these: alias k = kubectl                         # will already be pre-configured export do = "--dry-run=client -o yaml"     # k get pod x $do export now = "--force --grace-period 0"   # k delete pod x $now Vim To make vim use 2 spaces for a tab edit ~/.vimrc to contain: set tabstop=2 set expandtab set shiftwidth=2 More setup suggestions are in the tips section .     Question 1 | Contexts Task weight: 1%   You have access to multiple clusters from your main terminal through kubectl contexts. Write all those context names into /opt/course/1/contexts . Next write a command to display the current context into /opt/course/1/context_default_kubectl.sh , the command should use kubectl . Finally write a second command doing the same thing into ...

OWASP Top 10 Threats and Mitigations Exam - Single Select

Last updated 4 Aug 11 Course Title: OWASP Top 10 Threats and Mitigation Exam Questions - Single Select 1) Which of the following consequences is most likely to occur due to an injection attack? Spoofing Cross-site request forgery Denial of service   Correct Insecure direct object references 2) Your application is created using a language that does not support a clear distinction between code and data. Which vulnerability is most likely to occur in your application? Injection   Correct Insecure direct object references Failure to restrict URL access Insufficient transport layer protection 3) Which of the following scenarios is most likely to cause an injection attack? Unvalidated input is embedded in an instruction stream.   Correct Unvalidated input can be distinguished from valid instructions. A Web application does not validate a client’s access to a resource. A Web action performs an operation on behalf of the user without checkin...