一、概述
1.1 背景
过载保护是微服务系统无法绕过的技术难题,本文对过载保护的原因、解决方案、实施与测试闭环进行全流程的研究。
1.2 过载分析
过载后如果不进行保护,会导致资源耗尽,进而导致雪崩。过载有很多原因,大致如下:
(1)资源不足,例如cpu、内存、io、存储空间、PID不足;
(2)设计缺陷,例如代码逻辑问题导致处理效率低;
(3)几率性业务重合,例如很多高io业务集中到一个节点上;
(4)用户侧突发压力,例如大量用户在同一时间使用相同功能;
(5)异常业务场景压力增加,例如补偿类的业务造成双倍压力,清理策略失效导致文件量激增;
(6)下游服务异常导致上游业务阻塞,例如网络/存储io缓慢导致上游业务压力变大;
1.3 现状
从软件外部故障和系统测试故障分析,因过载导致的故障占了很大比例,可以说过载是无法避免的问题。本文不讨论业务逻辑,仅讨论的是过载本身的通用解决方案。
二、解决方案
下图是过载保护的几种手段,第1种是通过提高服务能力解决压力不够的问题;第2/3/4种都属于服务降级,即牺牲一部分服务能力;第5种熔断是为了防止压力传导(同时也能防止故障传导);三者之间是不冲突的。
2.1 增加资源
当发生过载时,首先考虑的是通过增加资源,这样做可以提高服务能力,但也面临很多问题:
- 资源投入增大,如果是突发压力,会使资源平均使用率下降;
- 伸展之后还要考虑收缩;
- 弹缩时机和业务行为强相关,策略不好选择,容易导致延迟伸缩和误判;
- 能力有上限。
2.2 服务降级
当发生过载之后,服务降级是最经济、高效的手段,可以有效避免服务完全不可用和雪崩,利用有限的资源提供最大的服务能力。
服务降级有很多策略、方法,主要包含如下三种:
1、关闭非核心业务
在资源有效的情况下,保留核心业务、关闭非核心业务是一种常用手段,这种方法很合理,但是实施比较复杂,一方面要提前识别哪些是核心业务、哪些是非核心业务,另一方面,不好评估关闭非核心业务会降低多少压力,以及何时恢复非核心业务。因此关闭非核心业务经常使用手动方式,手动埋点、手动打开/关闭开关。
2、限流
微服务架构下很多服务都是单一功能,不适合关闭非核心业务,因此限流成为主流方法。设置一个线程池/消息队列,超出队列直接拒绝,从而减轻服务的压力。
在实际项目程中,为了应对突发压力,往往会在执行队列的基础上再增加一个缓冲队列,超出执行队列后先放入缓冲队列,少量突发压力会先放入缓冲队列,缓冲队列也满了才会拒绝。
3、降低一致性
降低一致性是一种特殊的服务降级方法,方式有很多(包括上面的缓冲队列也是其中一种)。
(1)同步改为异步,强一致性改为最终一致性
这里主要指请求方的同步改异步,请求发出去之后,等待时间T之后如果没有收到返回值,则不再等待,并给用户返回“请求已经提交,由于系统繁忙,需要排队执行,请勿重复操作。”
这种方式实时性得不到保障,一致性也是通过事务完整性来保障的,不适用所有业务,需要根据实际业务情况选择。
异步只能解决突发压力增加,如果长时间压力大,下游任务依然会积压,最终无法执行成功。并且,如果实现最终一致性,任务失败后会做重试,重试本身又会增加系统压力。
(2)增加缓冲队列
缓冲队列主要用来应对突发压力,当执行队列满了之后,先把请求存入缓存队列。缓冲队列只是“权宜”之计,超出队列的请求依然要进行限流。实际运用中,缓冲队列和限流是不冲突的。
缓冲队列一般使用FIFO的方式进行存取。缓冲队列设置太短起不到很好的效果,会很快充满。设置过长,又会导致部分请求等待时间过长,这些请求执行完成返回给上游时已经超时,上游服务认为这是无效请求(此情况只会出现在上游是同步请求的场景)。这种情况持续一段时间后,每个请求都会超时。因此队列的长度设计要适中,还要考虑实际业务规律。
- 多副本强一致性,改为主用同步、备用异步
这是一种特殊场景。
根据CAP原则,一致性、可用性、分区数三者必须牺牲一个,在满足CP的业务场景下,除了可用性下降之外,还会导致响应时间增加、对底层压力贡献增加,这时可以通过策略修改一致性,修改为主用同步、备用异步。
总结:降低一致性的三种方式都是为了解决突发压力,第二种方法是常用方法,第一种方法可以和第二种方法配合使用,第一种方法适用范围有限,要根据实际业务决定是否采用。
2.3 熔断
如上图所示,服务A向服务B发请求,当请求出现大量超时、失败、拒绝时,上游服务A应该停止发送请求,这种行为称为熔断。熔断可以减少对下游施加的压力,也可以防止下游故障向上游传导。
不难看出,服务降级是指当服务压力过大时,本服务通过减少工作量来降低压力,而熔断是通过减少无效请求来降低下游压力,二者的区别主要是实施对象不一样。
三、落地
下图为闭环流程:
先由项目架构师给出统一的设计原则,然后由产品架构师进行设计、DEV进行开发、测试人员进行测试,然后进行迭代改进。
3.1 设计原则
通过前面的分析,我们对设计原则给出如下使用建议:
1、有性能风险的服务,必须要有弹性,支持垂直或水平弹缩。(现状:水平弹缩目前失效性不满足要求,垂直弹缩场景java微服务的内存无法收缩。)
2、有性能风险的服务,必须要有服务降级能力,关闭非核心业务和限流二选一,降低一致性建议选用缓冲队列,队列长度设置适中,同步/异步要根据实际业务需求选择。(现状:目前没发现关闭非核心业务的微服务,部分业务进行了限流,缓冲队列长度未严格要求。)
3、熔断建议全部使用。(现状:目前基本都没使用)
4、注意:开发团队根据以上原则进行方案设计、开发,针对弹缩、限流、熔断等功能,建议从框架给出解决方案,开发团队只需要根据自己的业务特点填写模板参数即可。
3.2 设计
设计阶段需要给出各项指标,包括弹性伸缩的阈值、弹性伸缩的时间指标、业务并发度指标、缓冲队列长度、熔断阈值和熔断检测周期。这些指标是开发的依据,也是测试的依据。
3.3 开发
根据设计进行开发。
3.4 测试分析
根据4.2设计的进行测试验证,验证既要包含功能是否实现,又要包含效率是否达成。
1、弹性能力验证
(1)垂直弹缩,验证业务闲时资源资源使用率不超过request,忙时超过request但达不到limit。
(2)水平弹缩,验证点是是否能弹、是否能缩,以及时效性是否满足指标要求
2、并发度验证
白盒测试主要在FT覆盖,通过打桩的方式对执行队列和缓冲队列进行压测。
黑盒测试是从业务视角看,执行队列和缓冲队列是无法区分的,可以逐渐增加并发度,看最大并发度是多少。
并发度测试的时候,还要关注①在过负荷的时候是否会限流;②进入队列的任务是否能够全部成功;③长时间过负荷测试服务是否会崩溃。
- 熔断功能验证
熔断的测试方式包含几部分:
- 下游挂了之后,上游请求一直失败,上游服务是否会熔断;
- 上游请求超时,是否会熔断;
- 熔断开启状态,在下游恢复之后,能不能通过有效的检测机制恢复上游业务。
3.5 测试设计与测试用例
1、前端接口级过载测试
针对微服务接口级的测试,要设计相应的压力测试,测试标准参考为3.4测试分析。
2、后端指标类过载测试
针对指标文档中的高并发类指标,后端设计相应的业务并发测试,参考3.4测试分析的第2节并发度验证。