使用微服务架构后,拆分出来的服务越来越多,随之而来的困扰是:
- 由于某一个服务的崩溃,导致所有用到这个服务的其他服务都无法正常工作。一个点的错误经过层层传递,最终波及到调用链上与此有关的所有服务,导致雪崩。
- 服务虽然没有崩溃,但由于处理能力有限,面临超过预期的突发请求时,大部分请求直至超时都无法完成处理。这导致请求全部阻塞在一处,阻塞得越多,要疏解就越难。
防止雪崩、阻塞是微服务架构容错性设计的基本原则,否则服务化程度越高,整个系统反而越不稳定。
服务容错
一个大的服务集群中,程序可能崩溃、节点可能宕机、网络可能中断,这些“意外情况”其实全部都在“意料之中”。信息系统设计成分布式架构的主要动力之一就是为了提升系统的可用性,最低限度也必须保证将原有系统重构为分布式架构之后,可用性不出现倒退下降。
在分布式架构中要保证系统的可用性,必须得进行容错性设计,这也是微服务架构设计中的一个核心原则。
容错策略
故障转移(Failover)
高可用的服务集群中,多数的服务——尤其是那些经常被其他服务所依赖的关键路径上的服务,均会部署有多个副本。这些副本可能部署在不同的节点(避免节点宕机)、不同的网络交换机(避免网络分区)甚至是不同的可用区(避免整个地区发生灾害或电力、骨干网故障)中。
故障转移是指如果调用的服务器出现故障,系统不会立即向调用者返回失败结果,而是自动切换到其他服务副本,尝试其他副本能否返回成功调用的结果,从而保证了整体的高可用性。
故障转移的容错策略应该有一定的调用次数限制,譬如允许最多重试三个服务,如果都发生报错,那还是会返回调用失败。原因不仅是因为重试是有执行成本的,更是因为过度的重试反而可能让系统处于更加不利的状况
快速失败(Failfast)
有一些业务场景是不允许做故障转移的,故障转移策略能够实施的前提是要求服务具备幂等性,对于非幂等的服务,重复调用就可能产生脏数据,引起的麻烦远大于单纯的某次服务调用失败,此时就应该以快速失败作为首选的容错策略。
比如,在支付场景中,需要调用银行的扣款接口,如果该接口返回的结果是网络异常,程序是很难判断到底是扣款指令发送给银行时出现的网络异常,还是银行扣款后返回结果给服务时出现的网络异常的。为了避免重复扣款,此时最恰当可行的方案就是尽快让服务报错,坚决避免重试,尽快抛出异常,由调用者自行处理。
容错设计****模式
断路器****模式
断路器模式是微服务架构中最基础的容错设计模式,基本思路是通过代理(断路器对象)来一对一地(一个远程服务对应一个断路器对象)接管服务调用者的远程请求。
断路器会持续监控并统计服务返回的成功、失败、超时、拒绝等各种结果,当出现故障(失败、超时、拒绝)的次数达到断路器的阈值时,它状态就自动变为“OPEN”,后续此断路器代理的远程访问都将直接返回调用失败,而不会发出真正的远程服务请求。
通过断路器对远程服务的熔断,避免因持续的失败或拒绝而消耗资源,因持续的超时而堆积请求,最终的目的就是避免雪崩效应的出现。本质上是一种快速失败策略的实现方式。
断路器是一种有限状态机,断路器模式是根据自身状态变化自动调整代理请求策略的过程,一般要设置以下三种断路器的状态:
- CLOSED:表示断路器关闭,此时的远程请求会真正发送给服务提供者。断路器刚刚建立时默认处于这种状态,此后将持续监视远程请求的数量和执行结果,决定是否要进入 OPEN 状态。
- OPEN:表示断路器开启,此时不会进行远程请求,直接给服务调用者返回调用失败的信息,以实现快速失败策略。
- HALF OPEN:这是一种中间状态。断路器必须带有自动的故障恢复能力,当进入 OPEN 状态一段时间以后,将“自动”(一般是由下一次请求而不是计时器触发的,所以这里自动带引号)切换到 HALF OPEN 状态。该状态下,会放行一次远程调用,然后根据这次调用的结果成功与否,转换为 CLOSED 或者 OPEN 状态,以实现断路器的弹性恢复。
值得关注的是CLosed和Open状态的转换。最简单直接的方案是只要遇到一次调用失败,就默认以后所有的调用都会接着失败,断路器直接进入 OPEN 状态。但这样方式体验极差,很大概率会造成误判。
现实中,比较可行的办法是在以下两个条件同时满足时,断路器状态转变为OPEN:
- 一段时间(譬如 10 秒以内)内请求数量达到一定阈值(譬如 20 个请求)。
- 一段时间(譬如 10 秒以内)内请求的故障率(发生失败、超时、拒绝的统计比例)到达一定阈值(譬如 50%)。
服务熔断和服务降级
服务熔断是一个动作,服务熔断之后一定时间内不再访问该服务,是一种快速失败策略。
服务降级是在服务熔断之后执行的一个备用方案,熔断之后的变化是,从直接调用服务接口转变为调用服务降级程序。
流量控制
一个系统的运算、存储、网络资源是有限的,当系统资源不足以支撑外部超过预期的突发流量时,应该建立面对超额流量自我保护的机制,也就是限流。在系统中的不同位置,限流有不同的关注点。
- 在系统入口,如nginx/网关处,更关注对系统级别的流量、请求控制,例如限制并发连接数、限制每秒请求速率等。
- 在系统内部,如单个服务中,更关注对某个接口的请求控制,例如限制每秒执行请求数、限制最大同时执行请求数等。
在软件系统中,限流是控制系统并发访问量、防止过载的重要手段。
Nginx
- 基于连接数限流:Nginx可以通过limit_conn模块限制并发连接数,防止系统被过多连接占用资源。
- 基于请求速率限流:Nginx可以通过limit_req模块实现对请求速率的限制,控制单位时间内的请求数量。
- 基于IP地址限流:Nginx可以使用ngx_http_limit_conn_module模块限制单个IP地址的连接数,防止恶意请求。
网关
- 基于API调用次数限流:API网关可以根据API的调用次数进行限流,防止某个API被过度调用。
- 基于用户身份限流:根据用户身份信息进行限流,防止某个用户对系统的访问量过大。
- 基于请求速率限流:类似Nginx,网关也可以实现对请求速率的限制,控制单位时间内的请求量。
服务
在服务级别进行限流有两种方式,一是单机限流,二是分布式限流。
典型的场景一是控制接口每秒并发数,例如每秒执行十个并发请求;二是控制接口同时处理请求数,例如接口十分消耗内存资源,最多同时执行十个请求。
单机限流
单机限流常用的方案有计数器、令牌桶算法。
令牌桶算法中,只有获得令牌的请求才能被处理,通过限制令牌的数量,就可以达到限制请求速率的效果。这种限流方式适用于单机、并发量小的情况。
- 使用Guava包中的RateLimiter进行限流
// 每秒发放5个令牌
private final RateLimiter rateLimiter = RateLimiter.create(5.0);
// 获取令牌,如果无法获取则等待,直到获得令牌
rateLimiter.acquire();
分布式****限流
分布式限流常用的方案是通过分布式缓存,维护一个计数器。
标签:调用,服务,请求,断路器,治理,OPEN,限流 From: https://www.cnblogs.com/cd-along/p/18213952