前言
分布式系统中经常会出现某个基础服务不可用造成整个系统不可用的情况, 这种现象被称为服务雪崩效应. 为了应对服务雪崩, 一种常见的做法是手动服务降级. 而Hystrix的出现,给我们提供了另一种选择.
将单体应用迁移到分布式框架后,很大可能会遇到这样的问题:系统仅有一个控制单元,它会调用多个运算单元,如果某个运算单元(作为服务提供者)不可用,将导致控制单元(作为服务调用者)被阻塞,最终导致控制单元崩溃,进而导致整个系统都面临着瘫痪的风险。
服务化后面临的挑战:
-
服务管理:敏捷迭代后的微服务可能越来越多,各个业务系统之间的交互也越来越多,如何做高效集群通信方案也是问题。
-
应用管理: 每个业务系统部署后对应着一个进程,进程可以启停。如果机器掉电或者宕机,如何做无缝切换都需要强大的部署管理机制。
-
负载均衡:为应对大流量场景及提供系统可靠性,同一个业务系统也会做分布式部署即一个业务实例部署在多台机器上。如果某个业务系统挂掉,如何按需做自动伸缩分布式方案也需要考虑。
-
问题定位:单体应用的日志集中在一起,出现问题定位很方便,而分布式环境的问题定界定位,日志分析都较为困难。
-
雪崩问题:分布式系统都存在这样一个问题,由于网络的不稳定性,决定任何一个服务的可用性都不是 100% 的。当网络不稳定的时候,作为服务的提供者,自身可能会被拖死,导致服务调用者阻塞,最终可能引发雪崩效应。
很多提高系统可用性的模式,其中非常重要的两条是:使用超时策略和使用熔断器
-
超时策略:如果一个服务会被系统中的其它部分频繁调用,一个部分的故障可能会导致级联故障。例如,调用服务的操作可以配置为执行超时,如果服务未能在这个时间内响应,将回复一个失败消息。然而,这种策略可能会导致许多并发请求到同一个操作被阻塞,直到超时期限届满。这些阻塞的请求可能会存储关键的系统资源,如内存、线程、数据库连接等。因此,这些资源可能会枯竭,导致需要使用相同的资源系统的故障。在这种情况下,它将是优选的操作立即失败。设置较短的超时可能有助于解决这个问题,但是一个操作请求从发出到收到成功或者失败的消息需要的时间是不确定的。
-
熔断器模式:熔断器的模式使用断路器来检测故障是否已得到解决,防止请求反复尝试执行一个可能会失败的操作,从而减少等待纠正故障的时间,相对与超时策略更加灵活。
雪崩问题的本质:
考虑到应用容器的线程数目基本都是固定的(比如Tomcat的线程池默认200),当在高并发的情况下,如果某一外部依赖的服务(第三方系统或者自研系统出现故障)超时阻塞,就有可能使得整个主线程池被占满,增加内存消耗,这是长请求拥塞反模式(一种单次请求时延变长而导致系统性能恶化甚至崩溃的恶化模式)。
更进一步,如果线程池被占满,那么整个服务将不可用,就又可能会重复产生上述问题。因此整个系统就像雪崩一样,最终崩塌掉。
雪崩效应产生的几种场景
-
流量激增:比如异常流量、用户重试导致系统负载升高;
-
缓存刷新:假设A为client端,B为Server端,假设A系统请求都流向B系统,请求超出了B系统的承载能力,就会造成B系统崩溃;
-
程序有Bug:代码循环调用的逻辑问题,资源未释放引起的内存泄漏等问题;
-
硬件故障:比如宕机,机房断电,光纤被挖断等。
-
线程同步等待:系统间经常采用同步服务调用模式,核心服务和非核心服务共用一个线程池和消息队列。如果一个核心业务线程调用非核心线程,这个非核心线程交由第三方系统完成,当第三方系统本身出现问题,导致核心线程阻塞,一直处于等待状态,而进程间的调用是有超时限制的,最终这条线程将断掉,也可能引发雪崩;
常见解决方案
针对上述雪崩情景,有很多应对方案,但没有一个万能的模式能够应对所有场景。
-
针对流量激增,采用自动扩缩容以应对突发流量,或在负载均衡器上安装限流模块。
-
针对缓存刷新,参考Cache应用中的服务过载案例研究
-
针对硬件故障,多机房容灾,跨机房路由,异地多活等。
-
针对同步等待,使用Hystrix做故障隔离,熔断器机制等可以解决依赖服务不可用的问题。
流量控制 的具体措施包括:
-
网关限流
-
用户交互限流
-
关闭重试
因为Nginx的高性能, 目前一线互联网公司大量采用Nginx+Lua的网关进行流量控制, 由此而来的OpenResty也越来越热门.
用户交互限流的具体措施有: 1. 采用加载动画,提高用户的忍耐等待时间. 2. 提交按钮添加强制等待时间机制.
改进缓存模式 的措施包括:
-
缓存预加载
-
同步改为异步刷新
服务调用者降级服务 的措施包括:
-
资源隔离
-
对依赖服务进行分类
-
不可用服务的调用快速失败
资源隔离主要是对调用服务的线程池进行隔离.
服务雪崩的过程
一组简单的服务依赖关系A,B服务同时依赖于基础服务C,基础服务C又调用了服务D
服务D是一个辅助类型服务,整个业务不依赖于D服务,某天D服务突然响应时间变长,导致核心服务C响应时间变长,其上请求越积越多,C服务也出现响应变慢的情况,由于A,B强依赖于服务C,故而一个无关紧要的服务却影响整个系统的可用。
影响了整个系统
雪崩是系统中的蝴蝶效应导致其发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。从源头上无法完全杜绝雪崩源头的发生,但是雪崩的根本原因来源于服务之间的强依赖,所以可以提前评估,做好熔断,隔离,限流。
熔断器
第一时间会想到Hystrix。下面来看下熔断器的实现原理
服务的健康状况 = 请求失败数 / 请求总数,
熔断器实际上是一个简单的有限状态机(Finite State Machine)
1.请求错误率达到某一阈值,熔断器全开,产生熔断(熔断期间会对所有请求采用降级处理) 2.到熔断时间窗口之后,熔断器会进入半开状态,此时hystrix会放过1个试验性请求 3.如果该试验性请求成功,熔断器进入关闭状态 4.如果该试验性请求失败,熔断器重新进入全开状态
应用界别隔离手段有线程池隔离,信号量隔离,连接池隔离,hystrix实现前2种,其各自优缺点如下
在一个高度服务化的系统中,实现的一个业务逻辑通常会依赖多个服务,比如: 商品详情展示服务会依赖商品服务, 价格服务, 商品评论服务. 如图所示:
调用三个依赖服务会共享商品详情服务的线程池. 如果其中的商品评论服务不可用, 就会出现线程池里所有线程都因等待响应而被阻塞, 从而造成服务雪崩. 如图所示:
Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离, 从而避免服务雪崩.
如下图所示, 当商品评论服务不可用时, 即使商品服务独立分配的20个线程全部处于同步等待状态,也不会影响其他依赖服务的调用
命令模式
Hystrix使用命令模式(继承HystrixCommand类)来包裹具体的服务调用逻辑(run方法), 并在命令模式中添加了服务调用失败后的降级逻辑(getFallback).
同时在Command的构造方法中可以定义当前服务线程池和熔断器的相关参数. 如下代码所示:
Hystrix有两个请求命令 HystrixCommand、HystrixObservableCommand。
HystrixCommand用在依赖服务返回单个操作结果的时候。又两种执行方式
-execute():同步执行。从依赖的服务返回一个单一的结果对象,或是在发生错误的时候抛出异常。
-queue();异步执行。直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象。
使用Command模式构建服务对象之后, 服务便拥有熔断器和线程池的功能.
【文章参考以下链接】
https://segmentfault.com/a/1190000005988895