假设我们有如下倚赖关系的3个服务,其中A与B是系统的核心服务,C是非核心服务。
如果系统某天需要做活动,预估流量可能是平时的3倍,那么我们可能会扩容服务A与B,由于C不是非核心服务,而选择不扩容服务。那么当活动开始时,请求蜂拥而至,服务C达到了系统的瓶颈,从而出现了大量的慢查询,继而导致服务B的大量请求一直在等待C的返回,久而久之服务B的大量资源耗在等待服务C的调用,最终导致服务B出现问题,紧接着服务A也出现了问题。导致了系统的雪崩。一个非核心服务C的慢请求最终演变成了怎个系统的雪崩。
所以在分布式环境下,系统最怕的反而不是某一个服务或者组件宕机,而是最怕它响应缓慢,因为,某一个服务或者组件宕机也许只会影响系统的部分功能,但它响应一慢,就会出现雪崩拖垮整个系统。
这个问题的解决的思路是在检测到某一个非核心服务的响应时间出现异常时,切断调用它的服务与它之间的联系,让服务的调用快速返回失败,从而释放这次请求持有的资源。这个思路也就是我们经常提到的降级和熔断机制。
熔断
熔断这个引用于电路的保险丝机制,当电路的功率超过一定的阈值,保险丝就会熔断以保护电路。在服务治理中熔断指的是当服务的提供方返回连续几次错误或者超时达到一定的阈值就触发熔断,则后续的请求不在发给服务提供方而是直接返回错误。熔断是调用方的一种自我保护策略。
服务的调用方需要维护熔断的三种状态:关闭、半关闭与打开。其状态的流转如下图
1、当服务调用失败或超时的次数达到一定的阈值,熔断进入打开的状态,后续的请求调用方直接返回错误,这里还有一个地方需要注意的,当服务返回成功了,应该重置失败次数;
2、半打开状态下,服务的调用方可以发送少量请求到服务提供方,如果调用超时或者失败,则重新回到打开状态;如果连续多次请求都成功,则回到关闭状态。需要注意的是,如果回到关闭状态时,如果全部流量都请求到被调用方,可能会把服务又打死,服务的提供方应该做好限流或者服务的调用方应该缓慢切换流量。
降级
降级是站在整体系统负载的角度上,放弃部分非核心功能或者服务,保证整体的可用性的方法,是一种有损的系统容错方式。这样看来,熔断也是降级的一种。常用的降级策略是开关降级。
开关降级指的是在代码中预先埋设一些“开关”,用来控制服务调用的返回值。比方说,开关关闭的时候正常调用远程服务,开关打开时则执行降级的策略。这些开关的值可以存储在配置中心中,当系统出现问题需要降级时,只需要通过配置中心动态更改开关的值,就可以实现不重启服务快速地降级远程服务了。
我们在设计开关降级预案的时候,首先要区分哪些是核心服务,哪些是非核心服务。因为我们只能针对非核心服务来做降级处理,然后就可以针对具体的业务,制定不同的降级策略了。常用的降级策略有:
1、针对读取数据的场景,我们一般采用的策略是直接返回降级数据。比如,如果数据库的压力比较大,我们在降级的时候,可以考虑只读取缓存的数据,而不再读取数据库中的数据;如果非核心接口出现问题,可以直接返回服务繁忙或者返回固定的降级数据;
2、对于一些轮询查询数据的场景,比如每隔 30 秒轮询获取未读数,可以降低获取数据的频率;
3、对于写数据的场景,一般会考虑把同步写转换成异步写,这样可以牺牲一些数据一致性保证系统的可用性。
也就是返回降级数据,限频与同步转异步。
另外需要切记的是,开关一定是需要经过测试且经常演练了的,别只是添加了开关,等到真正需要用的时候,确不起作用。