【实践】故障演练+服务治理方案
背景
与程序员和架构师们难舍难分的三高:高并发、高性能、高可用。
其中高可用性通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性,是分布式系统架构设计中必须考虑的因素之一。
如何验证及保障系统高可用性,既是本文重点描述的内容:故障演练及服务治理。
产品现状
(涉及到公司私密信息,已删减)
目前部门产品情况、故障演练覆盖情况:**。
随着业务发展,**产品后端架构逐步拆分成100+服务,服务之间调用越来越复杂,且依赖服务较多,包括中间件服务(底层缓存、数据库等)以及外部服务,同时新需求、系统重构、技术优化每天都在进行,使系统的复杂度逐步加剧。
对于一个用户场景,往往依赖数个到数十个后端API,而一个后端API又依赖数个到数十个微服务应用及外部依赖,当下游发生一些状况时,系统反应不可预知,风险越来越大。
列举几条2019年遇到的线上故障如下:
1)ZK leader节点宕机,所有loops节点都广播了下线事件,导致整体拒绝服务。
2)因线上停物理机进程维护的操作,宿主机的云主机异常关机,应用层ZK无法及时感知,导致http请求调dubbo方法出现少量报错,报错持续时间4~6分钟
3)云主机宕机,NLB长连接无法主动自动踢掉,业务不清楚NLB处理逻辑,未对该场景做兼容处理,导致链路异常。
……
出现线上事故时,在许多情况下由于处理预案的缺失或者预案本身的不可靠,以及开发人员经验的缺失,贻误了时机,可能导致影响范围持续增大。
这就需要我们有规范化常态化的故障演练机制,确保服务能在正常情形下表现出正常的行为,在异常状况下有预期中的、可控的表现。
故障演练目的,是服务治理、预案验证,即验证系统的故障恢复能力,根据故障用例和故障恢复预案,模拟真实故障情况的演习。
目前存在的问题
1、服务模块之间故障影响级别判断、是否需要修复、如何修复缺少统一的标准
2、全链路故障演练执行一次成本较高,未形成常态化流程
3、目前的故障手段及场景设计不够全面
目前的故障注入比较常规且单一,应用层仅覆盖了网络延迟及服务启停等,但是多机房分布式架构、健康检查以及部分基础服务的高可用特性已经能够自动容灾常见故障,性能QA需要细化故障场景,挖掘非常规故障场景。
4、故障异常表现的问题定位手段不够
5、故障异常期间的监控不够细致,不够自动化
6、故障复盘不够细致
(1)故障的现象和原因 (2)影响范围 (3)故障发生原因 (4)开发测试错漏问题原因 (5)故障解决说明 (6)改进措施
项目实践
一、目的
1)梳理应用模块间的依赖关系,治理不合理的依赖(强弱依赖、反向依赖、环状依赖)
2)根据等级制定不同的稳定性指标和手段,资源隔离等(软件层面的容灾)
预期目标:
-
- 减少P0级应用
为了防止影响范围过大,产品核心功能建议不超过六个
-
- 等级高的服务不强依赖与任何等级低的服务。
同等级服务之间的强依赖根据实际业务情况判断是否需要治理。(此规则适用于业务服务,工具类服务除外。工具类服务做好容错,不依赖业务服务。)
-
- 消除非必要依赖,尽量减少强依赖,不能的话需要做好容错。
二、服务分级
P级定义标准:(根据产品具体情况设定)
P0: 系统级基础服务
P1: 应用基础服务和核心功能
P2: 应用非核心功能
P3: 内部支撑业务
三、调用链路梳理
本文不展开
四、故障演练执行
1、演练用例
本次仅关注应用服务间的依赖故障场景,暂不考虑业务场景、基础服务、第三方服务的依赖故障,以及系统级和应用本身的故障。
| 服务 | 故障场景 | 故障描述 | 故障注入 |
| 依赖应用服务 | 网络丢包 | 网络丢包50% | ./blade create network loss |
| 网络延迟 | 网络延迟300s | ./blade create network delay | |
| 服务无响应 | 网络丢包100% | ./blade create network loss | |
| 服务假死 | kill -19 | ||
| 服务挂掉 | kill -9 |
2、执行故障注入
3、结果记录
模板:
| 应用模块 | 优先级 | 接口 |
依赖模块
| 优先级 | 被调用方法 | 结果依赖 |
RT依赖
| 容错框架&手段 | 问题单 |
|---|---|---|---|---|---|---|---|---|---|
| 【P0】 | /list | 【P0】 |
|
强依赖or弱依赖 |
强依赖or弱依赖 |
【hystrix限流】 | |||
| 【P0】 | |||||||||
| /get | 【P1】 |
三、治理不合理的依赖
1、标准制定
1)什么是强弱依赖?
有2种维度:
-
- 服务强弱依赖:接口和应用维度,体现服务上下游之间的强弱关系
强依赖:下游依赖发生故障时,接口出现异常,异常类型包括ERROR 异常、RT增加、错误率增加等。
弱依赖:下游依赖发生故障时,接口未出现异常。
例如A异步请求服务B,A服务无需回调结果,即使B服务挂了也不会影响A服务,那么这种情况,我们也可以说A弱依赖于B。
局限性:
服务依赖治理一般针对服务端,缺少面向用户的可用性指标
服务依赖治理一般面向单个接口,一个场景包含多个服务接口可能相互影响,需要综合考虑
-
- 业务场景强弱依赖(暂不考虑):
体现业务场景与服务之间的强弱依赖关系,明确业务影响范围。一个用户场景往往包含多个接口,比如,工单管理页面可以看到,依赖了至少7个后端API,我们要梳理客户端场景依赖关系,就需要开展场景依赖治理。
强依赖:下游依赖发生故障时,用户有无感知/核心业务有损失
弱依赖:下游依赖发生故障时,用户有无感知/核心业务无损失
2)如何判断强弱依赖?
手动:对照代码梳理依赖调用方式以及是否已添加容错措施,将强弱依赖结论以文本或者图的方式记录下来。(效率低,不考虑)
自动:结合故障注入引擎,在集成测试的过程中通过自动注入故障,根据预设的断言得出组件间的依赖关系。
(1)接口维度强弱依赖
强弱依赖结果判定参考3条规则:
- 请求链路是否有变化。如果应用层面添加了缓存,再次请求不会经过原有链路,可以给出“未经过”的判定结果,否则进一步参考规则2.
- 是否抛异常。如果接口没有做好容错处理,故障注入后再请求时可能直接抛异常,那么判定为强依赖。
异常类型包括:ERROR 异常、RT增加等
如果未出现异常,参考规则3.
- 返回结果是否符合断言预期,如果断言通过,则判定为弱依赖,反之判定为强依赖。
(2)应用维度强弱依赖
(3)业务场景强弱依赖
依赖服务挂了只会导致部分业务内容不展示,但是并不影响主流程业务,则为弱依赖。
从如下2个维度判断依赖是否合理:
(1)服务依赖
以一个简单的调用链路为例,应用间的调用关系有3种:
(1 高等级服务依赖低等级服务,如上图链路1
是否合理的判断标准:高等级服务不能依赖低等级服务(反向依赖)
例如:A应用等级P0,B应用等级P2,A应用的接口强依赖于B的rpc接口,属于不合理依赖。
(此规则适用于业务服务,工具类服务除外。工具类服务做好容错,且不能依赖业务服务。)
(2 低等级服务依赖高等级服务,如上图链路2
是否合理的判断标准:
-
- 依赖是否必要
根据具体业务判断
-
- 如果是强依赖:是否可以弱化,如果不能,是否有保护措施
根据具体业务判断
-
- 如果是弱依赖:是否有保护措施
(3 同等级的服务间依赖,如上图链路3
同上。
(2) 业务场景依赖
即业务上要求的依赖关系和技术上实际实现的依赖关系不一致。例如我们和业务方确认,订单流程可以弱依赖于运费服务,结果开发实现时做成强依赖了,那么我们认为这就是不合理的依赖。
-
- P0:主业务功能不可用
- P1:影响部分业务功能
- P2:基本不影响,或影响边缘业务
(3)服务级依赖治理和业务级依赖治理的关系
服务级依赖治理是从系统层面保护业务层面的稳定性。后台系统的故障,往往通过上一层的业务故障表现出来。
比如页面依赖A服务,A服务依赖B服务,如果B挂掉可能会导致A挂掉,从而导致页面无法展现。
那么服务级依赖治理是熔断B防止A挂掉,从而使页面正常展现;
业务级依赖治理是直接熔断A,防止页面挂掉,并不代表系统的稳定性提升。
4)依赖治理的优先级
依赖治理不能脱离业务实际情况,治理的优先级可以参考如下几个方面来定:
1、上层服务接口的线上流量
2、上层服务是否核心服务,服务的接口是否核心接口
3、是否强依赖
2、治理不合理的依赖
-
服务间依赖
1)环状&反向依赖治理
根据业务情况评估是否可以去反向依赖,方法:
优化应用等级划分;
消除非必要依赖,参考2)小节。
2)消除非必要依赖
根据具体业务判断依赖是否必要,如果是,需要判断是强依赖、 还是弱依赖,针对性的增加保护措施,具体措施参考3)、4)小节。
如果不是,建议解耦。
3)强依赖治理
(1)去强依赖
治理方案推荐:根据业务情况评估是否可以去强依赖,例如改为异步处理、消息处理等。转弱依赖后,需要增加弱依赖保护,参考4)小节。
(2)强依赖加保护措施:
治理原则:假设A强依赖B,当B发生故障,虽然A系统不能正常处理业务,但是A不能挂掉,一旦B系统恢复,A系统也要做到立即恢复。同时A有责任对B要进行流量保护,防止过大的流量导致系统崩溃,而不是对B进行摧残。
治理方案推荐:
依赖服务降级:在服务调用失败、超时等异常情况发生时,直接降级。降级后的处理方案:默认值、缓存(需要增加服务缓存)、自动切换到“失败时调用的方法”,并进行必要的补偿逻辑或异常记录。保证服务链路走通的同时,配合接口报警机制,即时发现依赖服务的问题。
依赖服务熔断:当依赖服务出现异常,比如暂时性的不可用,熔断器就会打开,对上游服务进行调用短路,此时,上游服务就不会再发起远程调用,而是直接走向降级逻辑。
依赖服务缓存:强依赖的接口,容易做缓存的接口设置后置缓存,当访问服务失败后能够请求缓存数据,同时每次请求成功需要更新缓存。
服务线程隔离:强依赖可以通过配置并发线程数隔离来限制不稳定的强依赖并发数,隔离强依赖。当请求数超过阈值时,将拒绝多余的请求,直到堆积的线程处理完成,以此来达到信号量隔离的效果。
服务限流:核心服务可以增加限流,依赖服务出现异常时,高流量可能会把服务应用线程池打满,通过限流措施截断流量,这时上游服务使用后置缓存正常返回,如果没有限流,服务导致线程池被打满,整体系统将陷入不可用状况。
4)弱依赖治理
治理原则:假设A强依赖B,当B发生故障,A有责任对B要进行流量保护,防止过大的流量导致系统崩溃,而不是对B进行摧残。
这种场景一般是异步请求。例如:启动单独的线程进行服务B调用;在当前线程中发消息,在消息消费线程中访问服务B。
治理方案推荐:为了防止请求/消息堆积,需要控制队列的大小;做好监控及报警,及时发现依赖服务的故障并及时处理。
-
业务场景依赖
1)修改客户端实现
例如修改为异步请求。
2)调整接口等级
三、评估标准
1)故障表现定级:待定
参考系统高可用性中的故障级别
服务间故障:
故障等级的制定一般按照模块的核心程度、影响范围、影响时间几个维度划分。
| 故障等级(ABCD) | 模块等级 | ||
|---|---|---|---|
| 影响程度 | P0(核心基础模块) | P1(核心非基础模块) | P2(边缘模块) |
|
请求成功率下降>=20%
|
A | B | C |
| 5%<请求成功率下降<20% |
不可自动恢复或恢复时长超过1min,A 可自动恢复,B |
不可自动恢复或恢复时长超过1min,B 可自动恢复,C |
不可自动恢复或恢复时长超过1min,C 可自动恢复,D |
| 请求成功率下降<=5% |
不可自动恢复或恢复时长超过1min,B 可自动恢复,C |
不可自动恢复或恢复时长超过1min,C 可自动恢复,D |
D |
业务场景:
按照功能的核心程度、影响范围、影响时间、用户感知几个维度划分。
| 故障等级(ABCD) | 业务场景等级 | ||
|---|---|---|---|
| 用户感知 | P0 | P1 | P2 |
| 用户有感知 |
业务功能完全不可用,A 业务非核心功能不可用,B |
业务功能完全不可用,B 业务非核心功能不可用,C |
业务功能完全不可用,C 业务非核心功能不可用,D |
|
用户感知不明显 |
不可自动恢复,B 1min内自动恢复,C
|
不可自动恢复,B 1min内自动恢复,C
|
D |
| 用户无感知 |
C |
B | D |
2)服务质量评估标准(应用级)
性能指标:满足性能要求的接口占比
错误率(告警次数):统计告警次数、请求失败率
故障次数


浙公网安备 33010602011771号