22 处理连锁故障 [color=rgba(0, 0, 0, 0.75)] - 如果请求没有成功,以指数型延迟重试。
- 为什么人们总是忘记增加一点点抖动因素呢?
- 连锁故障产生的原因和如何从设计上避免
- 服务器过载
- 资源耗尽。某一种资源的耗尽可以导致高延迟、高错误率,或者低质量回复的发生。这些的确是在资源耗尽时应该出现的情况:在负载不断上升到过载时,服务器不可能一直保持完全正常。
- CPU。如果CPU资源不足以应对请求负载,一般来说所有的请求都会变慢。这个场景会造成一系列的副作用:
- 正在处理的请求数量上升
- 队列过长
- 线程卡住
- CPU死锁活着请求卡住
- RPC超时
- CPU缓存率下降
- 内存。就算没有其他富足用,同时处理的请求数量升高也会消耗更多的内存用于存放请求、回复以及RPC对象。
- 任务崩溃。
- JAVA垃圾回收(GC)速率加快,从而导致CPU使用率上升。一个糟糕透顶的场景:由于CPU资源减少,请求处理速度变慢,内存使用率上升,调职GC触发次数增多,导致CPU资源进一步减少。我们将此称之为“GC死亡螺旋”,
- 缓存命中率下降。可用内存的减少可能会导致应用缓存的命中率降低,导致向后端发送更多的RPC,可能会导致后端任务过载。
- 线程。线程不足可能会导致错误或者导致健康检查失败。如果服务器为此增加更多线程,这些线程可能会占用更多内存。在极端情况下,线程不足可能会导致进程ID数不足(Linux的进程ID数是有限的)。
- 文件描述符。文件描述符(File Description) 不足可能会导致无法建立网络连接,今儿导致健康检查失败。
- 资源之间的相互依赖。很多资源的耗尽都会导致其他资源出现问题–某个服务过载经常会出现一系列次级现象看起来很像根本问题,这会使定位问题更困难。
[color=rgba(0, 0, 0, 0.75)] 假设如下场景:1. 某JAVA前端服务器GC参数没有被调优2. 在高负载(但是在期待范围内)情况下,情断由于GC问题导致CPI不足3. CPU不足导致请求处理变慢4. 同时处理的请求增多导致内存使用上升。5. 内存压力上升,同时由于固定内存分配比例的原因,用于缓存的内存数量减少6. 缓存数量降低意味着缓存中键值数量下降,从而导致命中率下降7. 缓存命中率下降导致更多的请求被发往后端进行处理。8. 后端服务器CPU或者线程不足。9. CPU不足导致健康检查失败,从而触发了连锁故障。总结:在上述这个复杂情境下,发生故障时可能没有时间仔细分析因果关系。尤其是在前端和后端由不同团队运维时,判断你后端崩溃是由于前端缓存命中率下降可能非常困难。[color=rgba(0, 0, 0, 0.75)] [color=rgba(0, 0, 0, 0.75)] 如果一个服务在1w qps下工作正常,到1.1w qps的时候进入连锁故障模式,降低负载到0.9w qps通常也无法恢复。这是因为这时该服务仍然处于容量不足的状态;通常仅仅只有一小部分的软件服务器可以正常处理请求。正常的软件服务器数量通常取决于:系统重启任务的速度,该任务进入正常工作的时间和新启动的任务能够承受过载请求的时间。在这个例子里,如果10%的任务目前可以正常处理请求,那么请求速率必须降低到1kQPs才能使整个系统恢复稳定。同样的,这些软件服务器可能对负载均衡来说是处于不健康状态,从而导致负载均衡可用容量的降低:软件服务器可能进入跛脚鸭状态或者无法处理健康检查。这种情况和软件服务器崩溃很类似,越来越多的软件服务器呈现不健康状态,每个仍甲亢的软件服务器都在很短的一段时间内接受大量请求而进入不健康状态,导致能够处理请求的软件界服务器越来越少。会自避免产生错误的软件服务器负载均衡策略会将这个问题加剧--某几个后端任务产生了错误,会导致负载均衡不再向它们发送请求,今儿使得其余软件服务器的负载上升,从而再次触发滚雪球效应。[color=rgba(0, 0, 0, 0.75)] - 防止软件服务器过载。
- 使用负载压力测试得出服务器的极限,同时测试过载情况下的失败模式。除非在真实环境下测试,否则很难预测哪种资源会耗尽,以及资源耗尽产生的效果。
- 给用户返回低质量的,但是更容易计算的结果。这里的策略取决于每个服务自己。
- 在过载情况下主动拒绝请求
- 上层系统应该主动拒绝请求
- 在反向代理层,通过针对请求的某种特性进行数量限制(如IP地址),来环节和避免拒绝服务供给,避免攻击性客户端的影响。
- 在负载均衡层,在服务进入全局过来时主动丢弃请求。
- 在灭个任务自身,避免负载均衡层的随机扰动导致软件服务器过载。
- 进行容量规划。如果每个集群的临界点是5kqps,服务负载平均分布,而该服务的峰值负载时19kqps,我们则需要大约6个集群来以N+2模式运行该服务。
- 队列管理。如果请求速率和单个请求处理耗时是固定的,那么排队就没偶必要:同时运行的线程数量是固定的。在这个理想化的情境下,只有在请求速率超过单个请求的处理速率时,请求才会进入队列,这种情况会导致线程池和队列的同时饱和。在队列中的排队请求消耗内存,同时使延迟升高。例如,如果队列大小是线程数量的10倍,而单个线程处理单个请求的耗时是100ms。如果队列处于满载状态,每个请求都需要1.1s才能处理完成,大部分时间都小号在排队过程中。对一个流量基本稳定的服务来说,队列长度比线程池大小更小会更好(如50%或更小),当服务处理速度无法跟上请求达到速率时,尽早拒绝请求会更好。而另外一些经常有“突发性”负载的系统,或者请求模式经常变动的软件服务器通常给予线程数量,请求处理时间和突发性流量的频率和大小来计算队列长度。
- 流量抛弃和优雅降级。
- 流量抛弃。在软件服务器临近过载时,主动抛弃一定量的负载。避免该软件服务器内存朝鲜,健康检查失败,延迟大幅升高,或者其他过载造成的现象。也就是使软件服务器在负载极限时,尽可能地再多做一些有用的工作。一种简单的流量抛弃实现方式是根据CPU使用量,内存使用量及嘟列长度等进行节流。例如,一个有效的办法是当同时处理的请求超过一定量时,开始直接针对心情求返回HTTP 503(服务不可用)。
- 优雅降级(graceful degradation)。例如,一个搜索类型的应用可能在过载情况下仅仅搜索保存在内存中的数据,而不是搜索全部存在硬盘上的数据。该服务或者可以擦用一种不那么精确(但是更快)的算法来进行结果排序。
- 当我们评估流量抛弃或者优雅降级时,需要考虑一下几点:
- 确定具体采用哪个指标作为流量评估和优雅降级的决定性指标(如,CPU用量,延迟、队列长度、线程数量、是否该服务可以自动进行降级,或者需要人工干预)。
- 当服务进入降级模式时,需要执行什么动作?
- 流量抛弃或者优雅降级应该在服务的哪一层实现?是否需要在整个服务的每一层都实现,还是可以选择某个高层面的关键字节点来实现?
- 当评估和具体实施的时候,还需要考虑:
- 优雅降级不应该经常被处罚–通常处罚条件显示了容量规划的失误,或者是出现了意料之外的负载转移。整个降级系统应该简单,易懂,尤其是在不经常使用的情况下。
- 记住,代码中平时不会使用的代码路径(通常来说)是不工作的。在稳定运行状态下,优雅降级不会经常处罚,意味着在这个模式下的运维经验很少,对这个模式的问题也不够熟悉,这就会升高这种模式的危险性,我们可以通过定期针对一小部分的服务压力测试以便更多地处罚这个模式,保证这个模式还能正常工作。
- 监控系统应该在进入这种模式的软件服务器过多时报警。
- 复杂的流量抛弃和优雅降级系统本身就可能造成问题–国语复杂的逻辑可能会导致软件服务器意外地服务进入降级模式运行,甚至进入反馈循环。设计时应该事先一种简单的关闭降级模式,或者是快速条件参数的方式。将这个配置文件存储在一个强一致性的存储系统(如Ch y)中,每个软件服务器都可以订阅改变,可以提高部署速度,但是同时也会增加整个系统的同步性风险(如果配置文件有问题,全部服务器同时都会受到影响)
- 重试。
- 这一章的东西非常多,也都是干货,有单深,除非接触过大型系统,否则看起来会有点吃力,就不在此啰嗦了。
[color=rgba(0, 0, 0, 0.75)] 管理关键状态:利用分布式共识来提高可靠性[color=rgba(0, 0, 0, 0.75)] [color=rgba(0, 0, 0, 0.75)] 1. 哪个进程目前是改组进程的领头人2. 本组中都包含哪些进程3. 是否已经将某个消息成功地插入了某个分布式队列4. 某个进程母亲啊是否还持有租约5. 数据存储中的某个键对应的值是什么[color=rgba(0, 0, 0, 0.75)] [color=rgba(0, 0, 0, 0.75)] CAP理论论述了一个分布式系统不可能同时满足以下三个要求:1. 每个节点上所见数据是一致的2. 每个节点都可以访问数据3. 可以承受网络分区问题[color=rgba(0, 0, 0, 0.75)] [color=rgba(0, 0, 0, 0.75)] 23 分布式周期性任务系统
|