当前位置: 首页 > 科技观察

十张图带你彻底了解限流、熔断、服务降级

时间:2023-03-13 14:13:57 科技观察

在分布式系统中,如果某个服务节点出现故障或者网络异常,都可能导致调用方阻塞等待。如果超时时间设置的很长,调用者的资源很可能会被耗尽。进而导致调用方上游系统资源耗尽,最终导致系统雪崩。如下图所示:如果服务D失败无法响应,服务B在调用D时只能阻塞等待。如果服务B调用服务D并设置超时时间为10秒,请求速率为100次/秒,那么1000个请求线程将被阻塞并在10秒内等待。资源枯竭,无法对外提供服务。进而影响入口系统A的服务,最终导致整个系统崩溃。提高系统的整体容错能力是防止系统雪崩的有效手段。在MartinFowler和JamesLewis的文章《Microservices: a definition of this new architectural term》[1]中,提出了微服务的九大特性,其中之一就是容错设计。为了防止系统中的雪崩,容错设计是必要的。如果流量突然增加,一般的做法是对非核心业务功能采取熔断和服务降级措施来保护核心业务功能的正常服务,对核心功能服务则需要采取限流措施.今天我们就来聊聊系统容错中的限流、熔断和服务降级。当系统的处理能力无法应对外部请求流量的突然增加时,为了防止系统崩溃,必须采取限流措施。1.1限流指标1.1.1TPS系统吞吐量是衡量系统性能的一个关键指标,按照完成交易的数量来限流是最合理的。但从实用性来说,按交易限流不太现实。在分布式系统中完成一个事务需要多个系统的协作。例如,我们在电子商务系统中购物时,订单、库存、账户、支付等多项服务需要协同完成。有些服务需要异步返回,因此可能需要很长时间才能完成一个事务。如果按照TPS进行限流,时间粒度可能会很大,很难准确评估系统的响应性能。1.1.2每秒HPS请求数是指服务器每秒从客户端接收到的请求数。?如果一个请求完成一个事务,那么TPS和HPS是等价的。但是在分布式场景下,可能需要多次请求才能完成一个事务,所以TPS和HPS指标不能一视同仁。?1.1.3QPSserver每秒可以响应的客户端查询请求数。?如果后台只有一台服务器,HPS和QPS是等价的。但是在分布式场景下,每个请求都需要多个服务器协同完成响应。??目前主流的限流方式大多采用HPS作为限流指标。?1.2限流方法1.2.1流量计数器这是最简单直接的方法,比如限制每秒请求数为100,拒绝超过100的请求。但是这种方法有两个明显的问题:单位时间(比如1s)很难控制,如下图:这张图中,从下面的时间看,HPS没有超过100,但是从上面看,HPS超过了100。有一段时间,流量超限,可能真的没必要限流,如下图,系统HPS限制为50,虽然前3s的流量超限了,如果读超时设置为5s,有无需限制电流。1.2.2滑动时间窗滑动时间窗算法是目前比较流行的限流算法,主要思想是把时间看成一个向前滚动的窗口,如下图所示:开始时,我们将t1~t5视为一个时间窗,每个窗为1s。如果我们设置一个每秒50个请求的限流目标,那么窗口t1~t5的总请求数不能超过250。这个窗口是滑动的,下一秒窗口就变成了t2~t6。此时丢弃t1时间片的统计,增加t6时间片统计。这段时间窗口内的请求数不能超过250,滑动时间窗口的好处是解决了流量计数器算法的缺陷,但是也有两个问题:流量超过了就必须丢弃或退化。流量控制不够精细,不能局限于短期1.2.3漏桶算法漏桶算法的思想如下图所示:在客户端的请求发送到服务器之前,它是首先用漏桶缓存。这个漏桶可以是一个定长的Queue,这个队列中的请求被均匀的发送到服务器。如果客户端请求速度过快,导致漏桶队列满,则会被拒绝或走降级处理逻辑。这样服务器就不会受到突发流量的影响。漏桶算法的优点是实现简单,可以利用消息队列削峰填谷。但是,需要考虑三个问题:漏桶的大小过大,可能会给服务器带来较大的处理压力;过小,可能会丢弃大量请求。漏桶向服务器发送请求的速率。使用缓存请求的方法会使请求响应时间变长。?leakybucketsize和sendrate这两个值在项目初期都会有。根据测试结果选择一个值,但是随着架构的改进和集群规模的扩大,这两个值也会发生变化。?1.2.4令牌桶算法令牌桶算法就像患者去医院看病一样,需要先挂号再找医生,医院每天发的号码是有限的。当天的号用完了,第二天再放一批号。该算法的基本思想是周期性地执行以下过程:当客户端发送请求时,需要先从令牌桶中获取令牌。如果获取到,可以向服务器发送请求,获取不到token,只能拒绝或者遵循服务降级的逻辑。如下图所示:?令牌桶算法解决了漏桶算法的问题,实现起来并不复杂。它可以通过使用信号量来实现。在实际限流场景中使用最多。比如tokenbucket算法就是在google的guava中实现的,对限流感兴趣的可以研究一下。?1.2.5分布式限流在分布式系统场景下,上面介绍的四种限流算法是否仍然适用?以令牌桶算法为例,如果客户在电商系统中下单,如下图所示:分布式系统,则客户端调用组合服务时,组合服务在调用订单、库存、账户服务时需要与令牌桶进行交互,交互次数大幅增加。一种改进是,客户端在调用组合服务前先获取4个token,调用组合服务时减去1个token,传递3个token给组合服务,组合服务调用后面3个服务时依次消费1个token。1.2.6hystrix限流hystrix可以使用信号量和线程池来限流。1.2.6.1信号量限流hystrix可以使用信号量进行限流,比如在提供服务的方法中添加如下注解。这样,只有20个并发线程可以访问这个方法,超过的会被转移到errMethod的降级方法中。@HystrixCommand(commandProperties={@HystrixProperty(name="execution.isolation.strategy",value="SEMAPHORE"),@HystrixProperty(name="execution.isolation.semaphore.maxConcurrentRequests",value="20")},fallbackMethod="errMethod")1.2.6.2线程池限流hystrix也可以使用线程池进行限流,在提供服务的方法中添加如下注解,当线程数@HystrixCommand(commandProperties={@HystrixProperty(name="execution.isolation.strategy",value="线程")},threadPoolKey="createOrderThreadPool",threadPoolProperties={@HystrixProperty(name="coreSize",value="20"),@HystrixProperty(name="maxQueueSize",value="100"),@HystrixProperty(name="maximumSize",value="30"),@HystrixProperty(name="queueSizeRejectionThreshold",value="120")},fallbackMethod="errMethod")?注意这里:在java的线程池中,如果线程数超过coreSize,创建线程的请求会先进入队列。如果队列已满,则继续创建线程,直到线程数达到maximumSize,然后执行拒绝策略。但是在hystrix配置的线程池中多了一个参数queueSizeRejectionThreshold。如果queueSizeRejectionThresholdmaxQueueSize,当队列数量达到maxQueueSize时,maximumSize有效,系统会继续创建线程,直到数量达到maximumSize。Hytrix线程池设置坑[2]?对于断路器相信大家都不陌生。它相当于一个开关,打开后可以阻止流量的流动。例如,当电流过大时,保险丝会熔断,以免损坏元器件。服务熔断是指当调用者访问服务时,熔断器作为代理访问服务。断路器将继续观察服务返回的成功和失败状态。当故障超过设定的阈值时,断路器打开,请求无法访问。服务。为了更好的理解,我画了下面的时序图:?可以参考MartinFowler的论文《CircuitBreaker》[3]。?2.1断路器的状态断路器有3种状态:CLOSED:默认状态。断路器观察到请求失败的比例没有达到阈值,断路器认为被代理的服务状态良好。OPEN:断路器观察到请求失败的比例已经达到阈值,断路器认为被代理的服务出现故障,打开开关,请求不再到达被代理的服务,而是快速失败。HALFOPENCLOSEDOPEN断路器的状态转换图如下:2.2需要考虑的问题使用断路器需要考虑的一些问题:对于不同的异常,定义不同的熔断后处理逻辑。设置熔断时长,超过此时间切换到HALFOPEN重试。记录请求失败日志以供监控。connectiontimeouttelenetHALFOPEN补偿接口,断路器可以提供补偿接口供运维人员手动合闸。重试的时候,可以使用之前失败的请求进行重试,但是一定要注意业务上是否允许这样做。2.3当场景使用服务失败或升级时,让客户端快速失败。故障处理逻辑易于定义。响应时间长,客户端设置的读超时时间会比较长,防止客户端大量重试请求导致连接和线程资源无法释放。3服务降级我们前面讲了限流和熔断,但是相比之下,服务降级是从系统整体的角度来考虑的。服务中断后,一般会以预先配置的方式处理请求。这种处理方式是一种降级逻辑。服务降级是非核心、非关键服务的降级。3.1使用场景服务处理异常,直接将异常信息反馈给客户端,而不是使用其他逻辑服务处理异常,缓存请求,返回一个中间状态给客户端,之后重试缓存的请求。监控系统检测到异常增加流量,为避免非核心业务功能消耗系统资源,关闭这些非核心功能的数据库请求压力,可以考虑将缓存中的数据返回。对于比较耗时的写操作,可以改为异步写,暂时关闭批处理运行任务,节省系统资源3.2使用hystrix降级3.2.1异常降级hystrix降级时,可以忽略异常,在方法:以下代码定义了降级方法为errMethod,并没有降级ParamErrorException和BusinessTypeException这两个异常。@HystrixCommand(fallbackMethod="errMethod",ignoreExceptions={ParamErrorException.class,BusinessTypeException.class})3.2.2调用超时降级专门针对调用第三方接口超时降级。下面的方法是调用第三方接口,3秒内没有响应,降级为errMethod方法。@HystrixCommand(commandProperties={@HystrixProperty(name="execution.timeout.enabled",value="true"),@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000"),},fallbackMethod="errMethod")限流、断路器和服务降级是系统容错的重要设计模式。从某种意义上说,限流和熔断也是一种服务降级的手段。熔断和服务降级主要针对非核心业务功能,如果核心业务流程超过预估峰值,则需要限流。对于限流,选择合理的限流算法非常重要。令牌桶算法优势明显,也是目前使用最多的限流算法。在系统设计时,这些模式需要根据业务量预估和性能测试数据配置相应的阈值,这些阈值最好保存在配置中心,以便实时修改。