微服务可用性设计
引言
当项目架构演进到微服务的时候,系统分布式部署,传统单个流程的本地 API 调用被拆分成多个微服务之间的跨网络调用,由于引入了网络通信、序列化和反序列化等操作,系统发生故障的概率提高了很多。而业界通常用多少个9来衡量系统的可用性,如99.99%表示一年中有1小时左右的不可用时间。那么如何有效确保微服务架构的可用性呢?
微服务可用性设计
微服务的可用性通常可以从隔离、超时控制、过载保护、限流、熔断、降级、重试、负载均衡
隔离
微服务隔离,本质上是对系统或者资源进行分割,从而实现当系统发生事故的时候可以限制传播范围和影响范围,只有发生故障的服务不可用,而其他服务依旧可以提供服务。
- 服务隔离
- 动静分离
动静分离:动静分离很好理解,现在的软件架构大部分都是前后端分离的模式,而前端的静态资源如图片、音视频等资源可以存储到oss云存储服务器,动态API则部署在ECS上来做到动静分离,静态文件访问负载全部通过CDN加速。
- 读写分离
读写分离:如数据库主从读写分离,DDD的CQRS模式
- 动静分离
- 轻重隔离
- 核心隔离
核心隔离:根据业务进行资源划分,核心业务与非核心业务进行机器资源、依赖资源隔离,核心业务可以搭建多集群通过冗余资源来提升吞吐和容灾能力。 - 热点隔离
- 快慢隔离
- 核心隔离
- 物理隔离
- 线程
- 进程
- 集群
- 机房
超时控制
超时控制,在微服务的调用中,我们希望组件能够快速失效,不希望等到实例连接超时而导致的页面无响应和请求挂起。这样不仅浪费资源同时也会导致用户体验感下降。
在实际业务开发中,各微服务的超时策略并不一定都清楚或者随着业务迭代超时策略发生变动,意外导致超时策略失效,就需要一个保底的机制,在基础库中设置默认超时策略来进行配置的防御保护。同时API服务的提供者需要定义好latency SLO,所有调用者都应遵守其超时策略的规定。
当然,在某些时候我们期望即便超时了,他也能够继续执行后续的事情,而不是直接中断在某一步,后续通过重试策略和幂等机制来保证业务的唯一性,这样大大提高了服务的访问成功率。
- 超时传递:如果一个请求有多个阶段,比如由一系列 RPC 调用组成,那么我们的服务应该在每个阶段开始前检查截止时间以避免做无用功,也就是要检查是否还有足够的剩余时间处理请求。
总而言之,超时控制是微服务可用性的第一道关,良好的超时策略可以尽可能的保证服务不堆积请求,不会挂掉,只有服务不挂掉才有能够执行后续的限流、熔断、降级、重试等策略
过载保护
在微服务中由于服务间相互依赖很容易出现连锁故障,连锁故障可能是由于整个服务链路中的某一个服务出现故障,进而导致系统的其他部分也出现故障。之前讲的超时控制是为了保证服务在出现连接超时能够快速失败,消除请求积压,而过载保护则是在服务高负载的情况下的一种保护策略。
限流
所谓限流是指在一段时间内,定义某个客户或者应用可以接收或者处理多少请求,通过限流,我们可以确保服务不会被高流量冲垮导致连锁故障,确保应用程序在自动扩展失效前都不会出现过载的情况。
限流算法:
- 令牌桶
令牌桶算法的核心是系统会以一定的速率往桶里添加令牌,处理请求前,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则返回失败。
所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
获取不到令牌,则请求返回失败
根据限流大小,设置按照一定的速率往桶里添加令牌;
桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
- 漏斗桶
漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,而当入小于出的情况下,漏桶不起任何作用。可以看出漏桶算法能强行限制数据的传输速率.示意图如下:
- 固定窗口
使用固定窗口实现限流的思路大致为,将某一个时间段当做一个窗口,在这个窗口内存在一个计数器记录这个窗口接收请求的次数,每接收一次请求便让这个计数器的值加一,如果计数器的值大于请求阈值的时候,即开始限流。当这个时间段结束后,会初始化窗口的计数器数据,相当于重新开了一个窗口重新监控请求次数。 - 滑动窗口
滑动窗口为固定窗口的改良版,解决了固定窗口在窗口切换时会受到两倍于阈值数量的请求,滑动窗口在固定窗口的基础上,将一个窗口分为若干个等份的小窗口,每个小窗口对应不同的时间点,拥有独立的计数器,当请求的时间点大于当前窗口的最大时间点时,则将窗口向前平移一个小窗口(将第一个小窗口的数据舍弃,第二个小窗口变成第一个小窗口,当前请求放在最后一个小窗口),整个窗口的所有请求数相加不能大于阀值。
- BBR
不管是漏斗桶还是令牌桶,缺点都太过明显,不能快速适应流量变化,限流的阈值需要人工设置,因此需要一种自适应的限流算法,这就是BBR动态流控算法。
通常BBR结合CoDel来实现过载保护
熔断
熔断机制其实是参考了我们日常生活中的保险丝的保护机制,当电路超负荷运行时,保险丝会自动的断开,从而保证电路中的电器不受损害。而服务治理中的熔断机制,指的是在发起服务调用的时候,如果被调用方返回的错误率超过一定的阈值,那么后续的请求将不会真正发起请求,而是在调用方直接返回错误。
降级
举个耳熟能详的例子,淘宝双十一购物节期间,用户只能够进行购买而不能进行退款操作,这就是典型的降级处理,当高并发高负载的场景下,服务可以通过降低回复来减少工作量,或者丢弃不重要的请求。
降级的本质实为提供有损的服务,通过一定的降级策略来保护服务不会被大流量冲垮而导致连锁故障。
重试
当请求返回错误例如请求超时、内部错误、配额不足,这时候使用重试机制可以提高请求的最终成功率,减少故障影响,让系统运行更稳定,但是需要留意重试带来的流量放大。
如下图,假设 A 服务调用 B 服务,重试次数设置为 r(包括首次请求),当 B 高负载时很可能调用不成功,这时 A 调用失败重试 B,B 服务的被调用量快速增大,最坏情况下可能放大到 r 倍,不仅不能请求成功,还可能导致 B 的负载继续升高,甚至直接打挂。
因此重试只应该在失败的这一层进行,当重试仍然失败,全局约定错误码直接返回避免级联重试,重试需要配合限流,防止重试造成雪崩的可能。
负载均衡
在理想情况下,负载完全均匀的分发给后端微服务进行处理,而工作分担到多个微服务上是为负载均衡,负载均衡是最基本的分流策略,如何将负载均匀的分配大致以下六种算法:
1、轮询法
将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。
2、随机法
通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,
其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。
3、源地址哈希法
源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。
4、加权轮询法
不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。
5、加权随机法
与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。
6、最小连接数法
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前
积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。
负载均衡策略众多,这里就不一一介绍了。
References
https://developer.aliyun.com/article/59008
https://mp.weixin.qq.com/s/5Q05d6OwvS-Zj-yNGwoh8A
https://learnku.com/articles/61838
https://www.cnblogs.com/duanxz/p/4123068.html
https://blog.csdn.net/weixin_41247920/article/details/100144184
https://blog.csdn.net/dog250/article/details/72849893
https://www.cnblogs.com/yuemoxi/p/15227815.html
https://my.oschina.net/u/4628563/blog/4692603
https://blog.csdn.net/ityouknow/article/details/81230412
https://blog.51cto.com/u_7117633/2864527