查看原文
其他

报警不响,黄金万两的“稳定性成熟度”干货

贾正荣 极客人生THE GEEKS 2022-09-09
小编推荐:关于稳定性,以新的视角和维度,去阐述是什么,为什么,怎么解决,怎么建设,怎么划分等问题,并且尝试了去建立一个成熟度模型,希望对开发的同学有一些帮助和启发。
本篇文章基本的思路是,就稳定性列举出需要解决的问题,然后列举出各种问题的解决手段,最后划分稳定性的阶段,不同的阶段对应上不同的解决手段。最后推导出一个稳定性框架——稳定性成熟度。期望在一定范围达成共识,一起推进稳定性的建设。

1.
什么是稳定性

稳定性指在应用变化,外界和环境变化,以及随着时间推移下,应用可以正常稳定的提供服务。

停留:怎么去保证你的服务尽可能停留在100%。

偏离:如果偏离100%,如何让它恢复的足够快。



2.
需要解决什么问题


可以把中间件服务、MQ、ZK 等当做一个服务。

可以把资源型服务、MySQL、MongoDB、Redis 等当做一个服务。

可以把所有本服务所需要的外部服务全部 “抽象” 成下游服务。
在以下因素的作用下,会影响系统的稳定性。

3.

怎么解决

上面列举了5类问题,基本上囊括了影响服务稳定性的所有因素,我们可以通过以下6个手段全面的解决以上问题。

包括:研发管理、变更管理、隔离、冗余、容错容灾和演练。

3.1.隔离

  • 隔离的手段

把经常变化和不变的分开,把重要的和不重要的分开,把核心的和非核心的分开。

把消耗CPU的和消耗内存的分开,把用户接口和后台接口分开,总之隔离就是要区分核心和非核心,稳定和不稳定。保障核心服务的稳定性,避免相互影响。

隔离是在架构设计和服务部署的时候就该考虑的问题。
  • 隔离的手段

一般通过解耦,异步化来达到隔离的目的。

  • 隔离解决的问题
核心和非核心,稳定和不稳定隔离开来。
  • 隔离划分
需要先划分出你的最小核心MVP和非核心系统。这个是最重要的隔离,也是后续隔离的前置条件。 
  • 例子

运行时隔离:比如慢SQL,比如热点隔离(全部资源中部分资源高频率使用)。更典型的就是线程隔离,一个性能较低的服务会“霸占” 容器中绝大多数线程,而其它性能正常的服务的请求则需要等待线程资源的释放。最后,整个容器会崩溃,Hystrix的舱壁模式就是为此而生,运行时隔离线程。

进程隔离:比如运营使用的后台系统API 和 用户使用的API接口,因为变更频率,稳定性要求,请求量完全不一样,我们常常把他们分开部署到不同的进程中,达到进程隔离的目的。 

机器隔离:比如当前需要上线一个秒杀系统,并发量很高。那么他就不能和核心服务放在一起,甚至不该和任何其他正常服务放在一起(进程隔离)。部署的时候也应该和核心服务的机器分开开部署。再比如在一台机器上同时存在两个一样的CPU密集(IO密集)型服务应该分开,或者CPU密集和IO密集部署在一起的也应该分机器部署,理由是把一台机器的某个性能或者多个性能用到极限,一旦需求变更上线,场景变更,变数是不可控的。同时这样的机器和系统也经受不起波动和突刺的冲击。

3.2冗余

隔离之后,我们可以排除掉大部分不稳定因素,保证核心服务的稳定性,但是当核心服务本身和所在的环境出现问题,隔离就没有办法解决了。

  • 定义

准备多份服务或者环境,当核心服务出现问题的时候,可以通过流量切换,把流量切到正常服务的节点和环境里。
  • 冗余解决的问题

核心服务本身出现问题和部署环境变化时的稳定性应对,比如服务故障、网络故障、机房故障等。

  • 冗余的手段

当故障发生时,通过流量调度机制,切换流量到正常节点和环境。

  • 冗余要求

可独立部署:可以直接在运维平台打包部署,而不需要依赖其他服务部署完成后才能部署运行。

无状态:服务设计时候,需要保证服务是无状态的,可以随时水平扩展。

无依赖:为了尽量避免冗余同时失效的情况,冗余副本之间需要相互独立,完全对等,不能相互依赖,机房内副本跨交换机部署,如果有多机房冗余的情况,各机房独立,不能有完全相同的依赖。

自动化:需要支持灵活的流量调度策略,包括负载均衡,服务发现,流量路由等。当节点故障时,通过健康检查,故障节点剔除、动态路由切换等机制,可以平滑自动的地将流量从故障节点切到冗余节点,保证故障不会扩散和不会影响系统整体稳定性。

  • 冗余级别

冗余细分其实也可以分为资源冗余和应用冗余,或者软件冗余和硬件冗余,这里我就按照服务粒度来统一划分。

同机房冗余N+2,如果一个服务需要 1 个实例正常提供服务,那么应该部署 1 + 2 = 3 个节点。如果是N+1发布的时候就失去作用了。如果一个服务需要10个实例正常提供服务,同时部署了 10+2 =12个节点,代表此服务最多容忍2个节点同时出现问题,如果超过2个节点同时出现问题就无法正常响应所有请求。

集群级别和机房级别的冗余需要保证,当 “1”挂了,剩余 N 可以承接住所有流量,提供正常服务。

所以 +2 或者 +1 代表的都是 最小冗余数量,表示最多可以承受同时出现问题的 服务/机器/集群/机房 数量。
而为何会有最少+2 和+1 的不同 是因为成本的区别太大。

3.3 容灾容错
通过隔离解决非核心服务对核心服务的影响,通过冗余我们解决了核心服务和其部署环境不稳定的影响。
但是当上游突发大流量,异常输入,下游异常,和服务本身异常的时候。系统还是可能被击垮。
  • 容灾容错定义

广义上讲,隔离、冗余都属于容灾。
容灾:面对外部输入(上游)的变化,有一套完整的应对办法,保证核心服务可以持续稳定提供服务。
容错:服务本身要有容错设计,服务对依赖的下游要为失败而设计。
  • 容灾容错解决的问题

容灾:解决上游输入变化时的稳定性问题,比如:突发大流量、异常请求、安全攻击等。
容错:解决服务本身异常和下游服务异常,比如服务内部出错、异常,服务处理延迟,服务处理过载,超时,服务依赖链中部分依赖SLA不达标,造成整体服务不可用,服务链条过长,造成SLA整体不可控等。
  • 解决手段     

  • 例子

限流例子:随机拒绝请求、拒绝低优先级系统调用,拒绝低级别用户调用,根据白名单或黑名单规则拒绝特定用户请求调用,对失败率高或响应超时系统调用拒绝调用,利用线程池队列排队处理调用,拒绝超出处理能力调用等

降级例子:功能禁用(功能降级)。使用本地缓存而不是调用外部服务、不调用下游依赖服务(上游接口降级)。个别接口消耗大量资源的停用接口(接口降级)。非核心服务消耗大量数据库资源停用服务(服务降级),服务异常时采用默认数据或兜底数据,同步变异步调用,减少定时任务执行频率或减少业务峰值时间定时任务的执行等。

3.4 研发管理

服务的稳定性有先天性,先天性是在项目开发过程中就被决定的。项目研发,是一个多方交互、多阶段、长时间的一个过程。在研发过程中,需要通过制度和规范,过滤和减少出错。同时每一个阶段每一个点,又可能是一套庞大的体系。

比如代码开发中《编码规范》、《POM依赖规范》、《中间件使用规范》等展开都是一大片。

研发管理是一个技术、管理和效率综合衡量的问题,不可一刀切。
  • 解决问题

解决服务本身问题,包括代码质量、逻辑质量、架构设计质量、稳定性设计质量等。

  • 研发过程

3.5 变更管理

严格来讲,变更管理属于研发管理的一部分,之所以单独拎出来。是因为我们线上的绝大多数问题,都是因为变更而引起,所以单独成项,规范化体系化管理。
  • 解决问题

解决因为变跟而引起的问题。

  • 变更划分

服务变更,配置变更,数据变更,环境变更。

  • 要求

环境完备:完善的的线下、预发、灰度环境。每一个服务、接口,都需具备完善的环境。
测试严格:每一项变更,都需要严格的走完所有环境。
小流量实验:采用灰度机制,灰度观察没有问题后再放量。
开关控制:关键特性需要设置相应的特性开关,方便根据需要灵活地对特性进行开启和关闭。
可回滚:所有的变更操作均可回滚。
每一项变更,和每一项要求,都可体系化的建立规范,保证服务的稳定性。同时建立反面的教材库,以示警惕。
  • 例子——反面的教材库

代码搭车上线:比如由于缺乏有效的代码修改管理机制,某产品线由于代码搭车上线,出现过多次线上故障,并且由于变更时涉及的修改比较多,导致问题定位和追查时非常困难。

服务回滚时遗漏回滚代码:某业务功能上线时,有bug,发现后马上回滚了服务,但是代码没有回滚,第二天其它功能上线时候,带上了此代码上线,导致服务再次异常。因此,服务回滚之后,必须第一时间回滚代码,保证主线代码任何时候都是干净没有问题的。

服务上线预发之后未上线到生产:某业务代码合并到master之后,上预发验证时间过长一直停留在预发。后续业务变更在不知情情况下,继续合并到master,预发验证之后,带着上一版本一起上线到生产。由于上一版本的线上配置不全,业务未验证。导致上线之后报错。因此针对代码分支管理一定要有master lock 规范。

服务启动或者回滚时间过长:某服务上线异常,回滚时单个服务回滚时间太长,导致未能短时间内快速止损。经排查,回滚过程中部署服务都存在耗时过长的现象,由于服务回滚速度比较慢,产生了较大的线上服务故障。定期检查和优化服务的启动和回滚时间,保证出现故障时可以第一时间完成回滚操作。

配置文件缺少有效的校验机制:配置文件由模型产出,数据配送系统实时配送到线上服务,模型产生的配置文件有问题,引发线上故障。因此,针对配置文件,尤其是策略模型产出的配置文件,需要建立严格的检查和校验机制。

缺少索引:某服务业务变更,新增/修改 sql语句,上线之后,在业务高峰期,服务过载,响应变慢。经排查新sql 没有走索引,导致sql耗时增加,服务响应变慢,吞吐量降低。因此涉及大表,SQL改动,code review 时候,一定要检查索引,评估SQL响应时间和业务的请求量。

小流量后的修改没有经过严格的测试和灰度验证:某服务经过小流量灰度后,代码又有少量修改,再次上线时未灰度,导致线上故障。再小的变更,都要进行测试、灰度和双重检查。修改一行代码,也可能导致线上的稳定性故障。

3.6 演练

通过以上的5项手段,我们可以一一解决之前列出的影响系统稳定性的5个因子。但是在故障发生以前,我们依然无法校验系统的稳定性。也不知道上面所采取的措施那些存在隐患,以及当前的系统可以正常服务多大规模流量,多大流量该限流,多长时间该超时等,我们都不知道。所以我们需要演练。

  • 演练解决的问题

检测系统稳定性,发现隐患,明确系统瓶颈,验证稳定性手段。
  • 演练的手段

放火/压测:在生产环境,通过模拟故障,模拟流量请求检测我们的稳定性,和发现遗漏的隐患。以达到优化系统稳定性的目的。

  • 划分



4.

怎么建设

以上6个手段,或者说只是6个分类。虽然基本囊括了所有解决问题途径,但也仅仅只是个分类,没有实际解决我们项目中遇到的问题。

那么有了这6个手段(分类)之后,我们该如何去建设我们解决手段的框架呢?

4.1 体系化

使用类比,解决问题的手段是一个完整的整体,解决方法有六大分类即6个面,每一个分类里有无数的关键项组成即线,每一个关键项都有很多的关键点组成即点。

我只需要按照每一个分类找出那些关键项,慢慢建设一个个关键点就可以一步去步完善解决问题的体系。

  • 例子一

面:研发管理

线:《maven使用规范》

点:(1)公共API必须使用BOM 管理;(2)线上API 必须使用RELEASE;(3)新版本迭代只修改顶层POM中的版本。

  • 例子二

面:变更管理

线:《回滚规范》

点:

a. 设计、开发时候就考虑好兼容性问题
如:数据库改字段变为加字段;也要在变更实施准备好兼容的回滚脚本,否则不给发布。
b. 开关,变更拆分

如:某个变更删除了数据,直接回退数据没了。办法:分成两半。第一半禁止访问这个数据,发布验证没问题之后,再发布第二半真真删除数据。

c. 协议变更
如:A依赖B,接口协议变更。上线按照先B再A上线,回滚按照先A后B回滚。
4.2 留同存异

稳定性的本质是来自于,无数的踩坑血泪史,然后想出的一系列解决办法。而不同的服务形态,阶段不同,导致的踩坑不一样。而众多的服务又存在很多相似采坑经历。

所以仅仅在小范围内建设稳定性,是消耗巨大,而且不完全的,可能别人已经走过坑,并且提出解决办法的坑,你正兴高采烈的跳进去。
但是全部统一的稳定性建设又低效,遭到业务抗拒的。

所以拿出那些可以达成共识的,重要的,必须的作为统一的规范,然后准许不同的业务服务制定自己独特的标准和办法,达到留同存异的目的。

4.3手段优劣
解决一个问题,我们会有很多的手段,这个时候自然而然,我们会给每一个手段一个优劣的定位,以供我们选择。
比如需要杀死一只鸡,有很多种办法,用刀砍,用斧凿,用枪打,用炮轰。在有限制性条件下我们都可以定义最优解。
在稳定性建设中,我们在理想条件下限定:可以不入侵业务,业务无感知的,平衡、快速、无感知的解决问题的为最优解。

然后我们得出以下稳定性手段优劣的评判


5.

怎么描述


描述稳定性,我们需要通过服务的可用程度,故障发生时候的定级,不同服务不同的要求来阐述。

5.1 业务可用性

所谓业务可用性即系统正常运行时间的百分比

  • 故障时间=故障修复时间 - 点故障发现(报告)时间点
  • 服务年度可用时间% =(1 - 故障时间/年度时间)× 100%


 

5.2 故障的度量

需要明确的是,我们的线上服务不是每一次都会整个服务挂掉,线上的故障总是五花八门的,我们需要分类我们的故障和一个指标来度量我们的故障。
定义5个P1 相当于一个P0。
P0 = 5 * P1 = 20 * P2 = 100 * P3
所以在业务可用性计算中,我们很自然推导出 
故障时间 =(故障修复时间点-故障发现时间点)*  故障权重
  • 故障权重


       

  • 关于响应时间

从用户开始请求,到用户看到内容,都经历了那些过程?
用户响应时间 = 展示耗时 + 网络传输耗时 + 应用处理耗时

       

展示耗时取决于设备差异,前端代码。

网络传输耗时取决于接入网络性质,不同网络提供商,和地域性网络差别。

应用处理耗时就是,请求的服务端响应时间,也是我们关注的重点。

 

  • 用户响应时间

有追求的用户响应时间

 

 
级别
 
用户端
 
内部后台系统

描述
描述
优秀
time <= 1000ms
time <= 2000ms
及格
1000ms < time <=2000ms
2000ms < time <=3000ms
不及格
time > 2000ms
time >3000ms

 

  • 应用处理耗时

有追求的应用处理耗时

 

 
级别
 
对端接口
 
服务间接口

描述
描述
优秀
time <= 100ms
time <= 10ms
良好
100ms < time <=200ms
10ms < time <=20ms
及格
200ms < time <=1000ms
20ms < time <=100ms
不及格
time>1000ms
time > 100ms

5.3 服务分类

全篇我们经常提到一个词 “核心服务”。而且我们在故障分类的时候也是和核心服务挂钩的。那服务就是简单的划分为核心或者非核心服务吗?
在一个业务线,或者一个部门中,都存在着形形色色的服务。不能一刀切全部把业务可用性提到统一的一个标准,也不是非此即彼的核心非核心可概括。

我们需要根据业务重要程度,影响面大小,去划分我们的服务。

       

 

6. 

怎么划分


现在假设我的服务需要使用全部的手段,只是所处的阶段不同,导致使用解决问题的手段有强弱不同而已。

6.1 阶段划分
衡量系统适用于,或者需要达到那个稳定性阶段,取决于请求量和业务的重要性,与投入产出比。
请求量:一般来说请求量就是对系统最大的外界影响。不同的请求量下,对系统的架构,技术的选型,稳定性的要求完全是不一样的。
换句话说,如果你的请求量没达到一定级别,很多稳定性的手段是没用或者收益很小的
业务重要性:你的业务重要性,也决定了我们对这个系统稳定性的定位。
投入产出比:普遍来说越低级别投入资源,技术,人力越低,产出越高。
高一级的实现,必须在低一级已经实现的基础上。

6.2 强弱划分

每一个阶段都有完整的上述6中解决手段(即六个面),但是每个阶段中,对每种解决手段(面),解决手段的关键项(线),关键项的关键点(点)需求都是不一样的

我们以:高一级继承低一级,高一级补充或加强低一级,为强弱原则。

 


7. 

成熟度模型

        
--------- PUHUI TECH ---------
本文作者
-

贾正荣

滴滴 | 资深软件开发工程师
就职于公共平台部-carbo服务者生态,对平台化,稳定性有丰富经验。喜欢运动,历史。
 
编辑 | 钱维
-
推荐阅读

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

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