通用的服务可用性治理手段
- 一、上游保护下游
- 1、重试
- 2、熔断
- 3、资源
- 二、下游自我保护
- 1、幂等
- 2、限流
- 3、降级
内容总结自《亿级流量系统架构设计与实战》
一、上游保护下游
1、重试
接口超时情况:
- 请求发送超时,下游服务并没有接收到请求
- 请求处理超时,下游服务接收到请求,但是处理时间超时
- 响应请求超时,下游服务已经处理完请求,但是响应报文超时未回复
失败是否重试:
- 不重试
a. 下游服务返回业务异常,即业务请求不符合业务逻辑而返回的错误 - 重试
a. 服务质量异常错误,如反映下游服务稳定性的相关错误
b. 网络错误,如请求超时、数据丢包、网络抖动、连接断开
重试的退避策略:
- 无退避策略:请求失败后立即重试
- 线性退避策略:请求失败后等待固定的时间后再重试
- 随机退避策略:再一个时间范围内随机选择一个时间等待重试
- 指数退避策略:对一个请求连续重试,每次等待时间都是上一次的两倍
- 综合退避策略:指数退避策略与随机退避策略结合的形式,各开源消息中间件再对应消息失败时常使用此策略
重试风险:
- 不限制重试次数,会导致请求量被无限放大,对下游服务影响也会无限放大
- 下游系统因为服务容量不足导致服务负载升高,质量下降。上有服务继续重试不仅无法解决问题,反而会使下游服务负载压力雪上加霜
- 重试风暴,即请求链路上涉及多级的服务调用,如A->B->C->D,如果每个接口都具备重试特性,底层服务D的请求压力是指数级上升
规避重试风险:
- 非关键下游服务,失败不重试
- 当遇到下游服务为限流错误或熔断错误时,不再重试
- 防止重试风暴,在接收到上级请求后,检查这个请求是否为上级请求的重试请求,如果是,则在调用下游服务遇到失败时不再重试请求,防止请求量被级联放大。重试请求需要额外携带重试请求标记
- 计算一段时间内,重试请求总数与正常请求总数的比例(重试请求比),小于某个特定值时允许重试
2、熔断
由于网络原因或服务设计问题,微服务一般很难保证100%对外可用。如A->B->C->D,D因为请求量突增或设计不合理导致宕机,导致C服务请求大量阻塞,最终拖垮B和A
熔断器(在业务上游)三种状态
- Closed:默认关闭,此时认为下游服务可正常提供服务
- Open:当服务失败率到达一定阈值后,则会开启,此时认为下游服务不可用,即不再对此下游服务进行请求
- Half-Open:熔断器Open一段时间后,会进入该状态,此状态允许一个请求尝试调用下游服务,如果下游调用成功,则状态置为Closed,如果下游服务失败,则状态置为Open
3、资源
资源共享:
- 多个服务共用线程池资源
资源隔离:
- 新开线程发起服务调用,达到调用下游服务资源的隔离
- 上游服务对下游调用服务进行PV信号量的并发控制(类比上游对下游请求的限流)
二、下游自我保护
1、幂等
接口必备幂等特性(读接口天然具备幂等)
幂等性 = 请求接口携带唯一ID + 下游接口具备幂等校验策略
请求接口携带唯一ID:
- 请求接口生成UUID
- 请求接口携带业务参数具备唯一性(业务key、序列化请求参数)
- 预请求下游服务,获取唯一ID
下游接口具备幂等校验策略:
- 请求处理前,将请求的唯一ID写入Reids
- 借助业务数据表,数据插入报错
- 创建请求处理记录表,指定请求的唯一ID为记录表的唯一索引
2、限流
单机实例(单服务、服务集群)限流+固定阈值:某机器实例(单服务、服务集群)在N秒内可处理M个请求:
- 固定窗口
- 滑动窗口
- 漏桶算法
- 令牌桶算法
自适应限流:
- 借助消息队列:注意服务过载(消息堆积)
- 基于请求排队时间:如微信的过载控制系统Dagor,优先保证业务优先级或用户优先级更高的请求被允许通过,而低业务优先级、低用户优先级的请求被丢弃。排队时间=请求开始被处理时间-请求到达服务时间,设置平均排队时间阈值是20ms,如果请求排队时间超过20ms,则认为服务过载,触发限流
- 基于延迟比率:如Netflix的自适应限流组件concurrency-limits,其借鉴TCP拥塞控制的部分思想,其中的gradient算法实现如下,new_limit(真是的限流) = current_limit(当前限流窗口的大小) * (RRT_noload(无负载时最佳请求延迟) ➗ RRT_actual(当前请求采样请求延迟)) + queue_size(允许一定程度的排队,一般为current_limit的平方根)
- 其他:bilibili的Kratos微服务中的BBR limiter,其算法实现时使用CPU的负载做启发阈值,
a. 判断CPU负载是否超过默认值80%
b. 如果超过阈值,则判定服务当前正在处理的请求数是否大于服务最近最大吞吐量
ⅰ. 是,则请求丢低
ⅱ. 否,请求可以被处理
c. 如果没有超过阈值,则判定上次请求被丢失的时间距现在是否拆过1s
ⅰ. 超过1s,则请求可以被执行
ⅱ. 没有超过1s,继续判定服务当前正在处理的请求数是否大于服务最近最大吞吐量。是则请求被丢弃,否则请求可以被处理
3、降级
服务依赖度降低:
- 二元:强依赖(出现故障时业务不可接受)和弱依赖(出现故障时业务可暂时接受);
- 三元:一级依赖(故障导致服务完全不可用)、二级依赖(故障基本不影响服务的可用性,会有少许可接受的用户投诉)和三级依赖(故障不影响服务的可用性,没有用户投诉)
读请求降低:
- 缓存:可以在请求链路上(客户端、接入层网关、HTTP/RPC服务)设置本地缓存,并在配置中心中动态控制是否使用缓存
- 兜底数据:使用另一个数据源(离线数据)作为兜底数据;使用静态兜底数据
写请求降低:
- 异步写:将写操作转变为用户提交写请求和查询结果两阶段
- 聚合写:预处理一定周期内同一条数据的多个操作,最终合并为一个操作,降低写请求压力
- 丢弃写:大数据量场景,且业务场景对数据一致性要求不高,可以控制一定比例的写丢失