01 背景
京东的期中考试:当时618即将到来,各个团队都在进行期中考试前的模拟考试:军演压测,故障演练,系统的梳理以检测系统的稳定性以应对高可用,高性能,高并发。我们知道系统的稳定性建设是贯穿整个研发流程:需求阶段,研发阶段,测试阶段,上线阶段,运维阶段;整个流程中的所有参与人员:产品,研发,测试,运维人员都应关注系统的稳定性。业务的发展及系统建设过程中,稳定性就是那个1,其他的是1后面的0,没有稳定性,就好比将万丈高楼建于土沙之上。本篇文章主要从后端研发的视角针对研发阶段和上线阶段谈下稳定性建设,希望起到抛砖引玉的作用,由于本人的水平有限,文中难免有理解不到位或者不全面的地方,欢迎批评指正。
02 研发阶段
背景
京东的期中考试:当时618即将到来,各个团队都在进行期中考试前的模拟考试:军演压测,故障演练,系统的梳理以检测系统的稳定性以应对高可用,高性能,高并发。我们知道系统的稳定性建设是贯穿整个研发流程:需求阶段,研发阶段,测试阶段,上线阶段,运维阶段;整个流程中的所有参与人员:产品,研发,测试,运维人员都应关注系统的稳定性。业务的发展及系统建设过程中,稳定性就是那个1,其他的是1后面的0,没有稳定性,就好比将万丈高楼建于土沙之上。本篇文章主要从后端研发的视角针对研发阶段和上线阶段谈下稳定性建设,希望起到抛砖引玉的作用,由于本人的水平有限,文中难免有理解不到位或者不全面的地方,欢迎批评指正。
研发阶段
研发阶段主要参与人员是研发,主要产出物是技术方案设计文档和代码,一个是研发阶段的开始,一个是研发阶段的结束,我们要把控好技术文档和代码质量,从而减少线下bug率及线上的故障。
2.1 技术方案
2.1.1 技术方案评审
技术文档的评审需要有本团队的架构师和相关研发,测试,产品,上下游系统的研发同学参与,这样能够最大限度的保证技术方案的实现和产品需求对齐,上下游系统同学也知道我们的实现,采取更加合理的交互方式,测试同学也可以从测试视角给出一些风险点建议,架构师可以确保我们的实现和业界最佳实践的差异,确保合理性,避免过度设计;我们所要做的是开放心态采取大家的意见,严控技术文档的质量;
技术文档的评审可以采用提问的方式,会议开始前可以将技术文档分享给大家,让大家先阅读10分钟,所有同学开始提问,技术文档设计人其实不用读自己的技术文档给大家介绍,只要将大家的问题回答完,并能够思考下大家的建议,合理的采纳后,其实技术文档的质量就有了很大的保证,有的同学在技术文档评审时,比较反感大家的提问,总感觉在挑战自己,有些问题回答不上来,其实可以换种思路:有些问题回答不上来是正常的,可以先将大家的建议采纳了,会后再思考下合理性;大家对自己技术方案是建言献策,是保证自己技术方案的质量,避免在技术方案阶段就存在重大的线上隐患。
2.1.2 技术方案关注点
当我们遇到一个问题的时候,首先要思考的这是一个新问题还是老问题,99.99%遇到的都是老问题,因为我们所从事的是工程技术,不是科学探索;我们所要做的就是看下国内外同行针对这个问题的解法,learn from best practices;所以技术方案的第一步是对标,学习最佳实践,这样能让我们避免走弯路;
同时根据奥卡姆剃刀原理,我们力求技术方案简单,避免过度设计,针对一个复杂的问题,我们的技术方案相对复杂些,简单的问题技术方案相对简单些,我们所要追求的是复杂的问题通过拆解划分,用一个个简单的技术方案解决掉。同时技术文档不仅关注功能的实现,更重要的是关注架构,性能,质量,安全;即如何打造一个高可用系统。打造一个高可用的系统是进行系统稳定性建设的前提,如果我们的系统都不能保证高可用,又谈何系统稳定性建设,下面介绍下进行系统稳定性建设我们在技术方案中常用的方法及关注点。
2.1.2.1 限流
2.1.2.2 熔断降级
人工降级:人工降级一般采用降级开关来控制,公司内部一般采用配置中心Ducc来做开关降级,开关的修改也是线上操作,这块也需要做好监控;
2.1.2.3 超时
超时时间在设置的时候需要遵循漏斗原则,从上游系统到下游系统设置的超时时间要逐渐减少,如下图所示。为什么要满足漏斗原则,假设不满足漏斗原则,比如服务A调取服务B的超时时间设置成500ms,而服务B调取服务C的超时时间设置成800ms,这个时候会导致服务A调取服务B大量的超时从而导致可用率降低,而此时服务B从自身角度看是可用的。
2.1.2.4 重试
所以在和外部系统的一次请求交互中,我们系统是希望尽最大努力得到想要的结果,但往往事与愿违,由于不可靠网络的原因,我们在和下游系统交互时,都会配置超时重试次数,希望在可接受的SLA范围内一次请求拿到结果,但重试不是无限的重试,我们一般都是配置重试次数的限制,偶尔抖动的重试可以提高我们系统的可用率,如果下游服务故障挂掉,重试反而会增加下游系统的负载,从而增加故障的严重程度。在一次请求调用中,我们要知道对外提供的API,后面是有多少个service在提供服务,如果调用链路比较长,服务之间rpc交互都设置了重试次数,这个时候我们需要警惕重试风暴。如下图service D 出现问题,重试风暴会加重service D的故障严重程度。对于API的重试,我们还要区分该接口是读接口还是写接口,如果是读接口重试一般没什么影响,写接口重试一定要做好接口的幂等性。
2.1.2.5 兼容
我们在对老系统,老功能进行重构迭代的时候,一定要做好兼容,否则上线后会出现重大的线上问题,公司内外有大量因为没有做好兼容性,而导致资损的情况。兼容分为:向前兼容性和向后兼容性,需要好好地区分他们,如下是他们的定义:
向前兼容性:向前兼容性指的是旧版本的软件或硬件能够与将来推出的新版本兼容的特性,简而言之旧版本软件或系统兼容新的数据和流量。
向后兼容性:向后兼容性则是指新版本的软件或硬件能够与之前版本的系统或组件兼容的特性,简而言之新版本软件或系统兼容老的数据和流量。
根据新老系统和新老数据我们可以将系统划分为四个象限:第一象限:新系统和新数据是我们系统改造上线后的状态,第三象限:老系统和老数据是我们系统改造上线前的状态,第一象限和第三象限的问题我们在研发和测试阶段一般都能发现排除掉,线上故障的高发期往往出现在第二和第四象限,第二象限是因为没有做好向前兼容性,例如上线过程中,发现问题进行了代码回滚,但是在上线过程中产生了新数据,回滚后的老系统不能处理上线过程中新产生的数据,导致线上故障。第四象限是因为没有做好向后兼容性,上线后新系统影响了老流程。针对第二象限的问题,我们可以构造新的数据去验证老的系统,针对第四象限的问题,我们可以通过流量的录制回放解决,录制线上的老流量,对新功能进行验证。
2.1.2.6 隔离
2.1.2.6.1 系统层面隔离
我们知道系统的分类可以分为:在线的系统,离线系统(批处理系统),近实时系统(流处理系统),如下是这些系统的定义:
在线系统:服务端等待请求的到达,接收到请求后,服务尽可能快地处理,然后返回给客户端一个响应,响应时间通常是在线服务性能的主要衡量指标。我们生活中在手机使用的APP大部分都是在线系统;
离线系统:或称批处理系统,接收大量的输入数据,运行一个作业来处理数据,并产出输出数据,作业往往需要定时,定期运行一段时间,比如从几分钟到几天,所以用户通常不会等待作业完成,吞吐量是离线系统的主要衡量指标。例如我们看到的报表数据:日订单量,月订单量,日活跃用户数,月活跃用户数都是批处理系统运算一段时间得到的;
近实时系统:或者称流处理系统,其介于在线系统和离线系统之间,流处理系统一般会有触发源:用户的行为操作,数据库的写操作,传感器等,触发源作为消息会通过消息代理中间件:JMQ, KAFKA等进行传递,消费者消费到消息后再做其他的操作,例如构建缓存,索引,通知用户等。
2.1.2.6.2 环境的隔离
从研发到上线阶段我们会使用不同的环境,比如业界常见的环境分为:开发,测试,预发和线上环境;研发人员在开发环境进行开发和联调,测试人员在测试环境进行测试,运营和产品在预发环境进行UAT,最终交付的产品部署到线上环境提供给用户使用。在研发流程中,我们部署时要遵循从应用层到中间件层再到存储层,都要在一个环境,严禁跨环境的调用,比如测试环境调用线上,预发环境调用线上等。
2.1.2.6.3 数据的隔离
随着业务的发展,我们对外提供的服务往往会支撑多业务,多租户,所以这个时候我们会按照业务进行数据隔离;比如我们组产生的物流订单数据业务方就包含京东零售,其他电商平台,ISV等,为了避免彼此的影响我们需要在存储层对数据进行隔离,数据的隔离可以按照不同粒度,第一种是通过租户id字段进行区分,所有的数据存储在一张表中,另外一个是库粒度的区分,不同的租户单独分配对应的数据库。
2.1.2.6.4 核心,非核心隔离
我们知道应用是分级的,京东内部针对应用的重要程度会将应用分为0,1,2,3级应用。业务的流程也分为黄金流程和非黄金流程。在业务流程中,针对不同级别的应用交互,需要将核心和非核心的流程进行隔离。例如在交易业务过程中,会涉及到订单系统,支付系统,通知系统,那这个过程中核心系统是订单系统和支付系统,而通知相对来说重要性不是那么高,所以我们会投入更多的资源到订单系统和支付系统,优先保证这两个系统的稳定性,通知系统可以采用异步的方式与其他两个系统解耦隔离,避免对其他另外两个系统的影响。
2.1.2.6.5 读写隔离
应用层面,领域驱动设计(DDD)中最著名的CQRS(Command Query Responsibility Segregation)将写服务和读服务进行隔离。写服务主要处理来自客户端的command写命令,而读服务处理来自客户端的query读请求,这样从应用层面进行读写隔离,不仅可以提高系统的可扩展性,同时也会提高系统的可维护性,应用层面我们都采用微服务架构,应用层都是无状态服务,可以扩容加机器随意扩展,存储层需要持久化,扩展就比较费劲。除了应用层面的CQRS,在存储层面,我们也会进行读写隔离,例如数据库都会采用一主多从的架构,读请求可以路由到从库从而分担主库的压力,提高系统的性能和吞吐量。所以应用层面通过读写隔离主要解决可扩展问题,存储层面主要解决性能和吞吐量的问题。
2.1.2.6.6 线程池隔离
线程是昂贵的资源,为了提高线程的使用效率,避免创建和销毁的消耗,我们采用了池化技术,线程池来复用线程,但是在使用线程池的过程中,我们也做好线程池的隔离,避免多个API接口复用同一个线程。
2.2 代码Review
codeReview是研发阶段的最后一个流程,对线下的bug率和线上质量及稳定性有着重要的作用,针对于代码如何review,谈一些自己的看法:
形成团队代码风格:首先一个团队的代码应该形成该团队的代码风格,这样能够提高codeReview的效率及协作的效率,作为新加入的成员,应该遵循团队的代码风格规范。
Review的关注点:代码review切记不要陷入细节,主要以review代码风格为主,如果一个团队形成统一的代码风格,我们通过review风格就能将大部分问题发现,在关注功能的同时,再关注下性能,安全。
结对编程:在代码编写过程中,我们要培养结对编程的习惯,这样针对某次需求,codeReview时,熟悉该模块的同事把控下细节,架构师把控风格。
控制每次review代码量:每次提交代码进行review时,不要一次性提交review大量的代码,要将review的内容细分,比如一个方法的实现,一个类等。
开放心态:review的过程其实是学习提升的过程,通过代码review,虚心接受别人的意见,学习优雅代码的编写方式,提高自己的代码水平。
上线阶段
3.1 上线三板斧
3.1.1 可监控
3.1.2 可灰度
3.1.3 可回滚
3.2 线上问题应对
3.2.1 常见问题分类
针对线上的问题,我们第一步是识别出是什么问题,然后才能解决问题,针对线上各种各样的问题我们可以进行聚合,归并分类下,针对每种问题去参考业界的处理方法和团队的内的紧急预案,做到临阵不乱。
3.2.2 问题生命周期
当出现问题时,我们也需要清楚一个线上问题的生命周期:从问题发生,到我们发现问题,进而进行响应处理,观测问题是否修复,服务是否恢复正常,到最终针对该问题进行复盘,当发生系统发生问题时,我们越早发现问题,对业务的影响越小,整个流程如下图所示。
3.2.3 如何预防问题
就像人的身体生病一样,当问题发生已经晚了,我们要投入更多时间和精力到如何预防中,就像扁鹊的大哥一样治未病,防患于未然。根据破窗原理,一个问题出现了,如果放任不管,问题的严重性会越来越大,直到不可挽回。我们可以从研发的规范,研发的流程,变更流程这几个方面进行预防。
3.2.4 如何发现问题
3.2.5 如何响应问题
出现线上问题后,我们个人对问题的认知是非常有限的,并且这个时候人处于一种高度紧张的状态,所以这个时候一定要群里周知自己的leader,将情况如实表达,不要夸大和缩小问题的范围和影响,同时将问题进行通告。整个问题的响应过程包含以下几步:
1.保留现场:问题发生的现场是我们排查问题的依据,所以要将现场的日志,数据等信息保存好,比如内存dump, 线程dump,避免机器重启后这些信息的丢失;
2.提供信息:提供自己所知道的信息,协助排查,不要扩大和缩小问题;
3.恢复服务:当出现线上问题时,我们追求的是以最快的速度恢复服务,快速止损,业界有快速止血,恢复服务的几板斧:回滚:服务回滚,数据回滚,重启,扩容,禁用节点,功能降级;
4.双重确认:服务恢复后,我们需要确认是否恢复了,可以通过观察:业务指标是否正常,技术指标是否正常,数据是否正常,日志是否正常等来观测问题的恢复情况;
3.2.6 如何定位问题
工具:工欲善其事,必先利其器,工程师要善于借助公司工具来提高解决问题的效率,熟练使用公司各种中间件工具,公司已经有的中间件,优先使用公司的中间件,公司内一个中间件团队维护的中间件工具要优于业务研发小组内维护的中间件工具,不要小组内部,或者团队内部重复造轮子,并且小组内人员的流动变更,容易造成中间件没人维护。下图是公司常用的中间件工具:
3.2.7 如何修复问题
有了知识,工具和方法后,其实我们很快的就定位到问题了,定位到问题后,我们就要想办法如何去把问题修复了,以下是问题修复的流程:
3.2.8 如何复盘问题
参考业界的5WHY分析法剖析问题的根因
5WHY分析法:5代表的是问题的深度,而不是问题的数量
基于问题的答案继续进行提问,5个问题是有关联的,层层递进的,找到问题的根因
参考资料
https://itrevolution.com/articles/20-years-of-google-sre-10-key-lessons-for-reliability/
https://learn.microsoft.com/en-us/previous-versions/msp-n-p/jj591573(v=pandp.10)?redirectedfrom=MSDN
https://sre.google/books/