在分布式系统中,如果某个服务节点出现故障或者网络异常,都可能导致调用方阻塞等待。如果超时时间设置的很长,调用者的资源很可能会被耗尽。进而导致调用方上游系统资源耗尽,最终导致系统雪崩。如下图所示:如果服务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。如果queueSizeRejectionThreshold
