前言
关于微服务项目中的服务保护,我们会使用第三方的组件,例如Sentinel或者Hystrix,今天我来总结一下我对于Sentinel的学习。
为什么要进行服务保护
在微服务的架构中,服务之间可以进行远程调用,如果服务1包含服务2和服务3,服务1调用服务2,如果服务1调用服务2的并发量太高,不仅会影响性能,可能还会因为耗尽服务器资源从而影响服务1和服务3,造成雪崩。
因此我使用Sentinel对服务的调用做出一些保护措施。
请求限流:限制接口的每秒QPS阈值
线程隔离:设置服务调用不同服务的并发线程数量的阈值,防止远程调用使用过多线程导致系统资源耗尽
服务熔断:给服务设置熔断规则,当慢调用/异常数量/异常比例达到一个阈值之后,直接走降级措施(直接返回,提高调用性能)
线程隔离原理
对于线程隔离的底层实现,Sentinel和Hystrix不同。
基于线程池
Hystrix底层基于线程池实现线程隔离,当设置线程隔离即会给服务调用的业务分配一个线程池,来限制每个业务使用的并发线程数量。
优点:1、隔离性好,每一个服务调用业务都有独立的线程池。
2、可以异步调用
缺点:线程池的创建和使用会带来额外的开销
基于信号量
Sentinel底层基于信号量来实现,信号量是保证线程并发安全的一个并发工具类,可以限制访问特定资源的最大线程数量。
原理:初始化一个计数器即令牌数量,每有一个线程都需要获取令牌才能运行,运行结束后释放令牌,控制访问特定资源的最大线程数量。具体可见:并发工具类:Semaphore、CountDownLatch、CyclicBarrier的作用以及用法
优点:通过计数器实现,无额外开销
缺点:隔离性一般,不支持异步调用
请求限流原理
对于请求限流,即限制请求每秒的QPS,我们先来了解固定时间窗口算法。
固定时间窗口算法
固定时间窗口算法即统计接口每一秒的请求数。
如上图,每一个窗口都是一个固定的时间段,当请求到达时判断所在时间段的计数是否到达阈值即可。
存在的问题:但是也存在很明显的问题,例如一个在1.5s到达的请求,对于每秒QP的限制,我们应该查看从0.5s到1.5s之间的请求数量,因此我们应该动态的滑动这个窗口。
滑动时间窗口算法
对应Sentinel的每秒QPS统计和开启熔断后慢时长以及异常数的统计都采用了滑动时间窗口算法。
我们采用滑动窗口算法,将每一秒分为多个窗口,我们以500ms一个窗口举例。
每秒请求数统计:对于请求数的统计,如果我们精确的统计每个时间点的请求,当一个在n秒的请求到达,只需要计算在n-1s到n这个时间点内的请求即可,但是毫无疑问,这将会占用额外的内存空间,因此为了避免内存的开销,我们只能牺牲一些准确度。而滑动时间的请求数统计则是从n-1s时刻后的第一个时区开始到该请求所在的时区里所有的请求数。
例如上图,对于一个900ms的请求,900ms-1s=-100ms,而-100ms后的第一个窗口则是0到500ms,那么其QPS则是0-500ms + 500-1000 的请求数。
存在的问题:如果一个1250ms的请求,统计的就是500-1500ms内的请求数,如果250ms到500ms以内还存在请求是不是就存在误差了?
问题解决:对于这个问题,误差不可避免,但是如果我们可以再次划分窗口,将窗口划分的更小更细,就会减少误差,但是也带来了内存的开销,误差的避免和内存的开销相反。
令牌桶算法
对于Sentinel限流,底层使用了滑动时间窗口算法,对于滑动窗口算法,我们需要保证其误差更小,因此也带来额外的内存开销,如果我们想要对一些热点参数做出一些限制,比如一些热点商品的秒杀,我们需要做出限流,但是热点参数比较多,如果我们都使用滑动窗口算法将会带来比较大的内存开销,对于Sentinel热点参数的限流,底层使用了令牌桶的算法。
原理:定义一个令牌桶(令牌计数器),例如我们限制每秒的QPS为5,那么就以200ms生成一个令牌的速度,将生成的令牌放入令牌桶中(计数器+1),当桶满就会停止生成。每当一个请求需要通过时,只需要获取令牌(尝试让计数器-1),由于每秒产生固定的令牌数,而请求有需要获取令牌,就能控制每秒QPS,并且采用计数器,内存占用较少。
存在的问题:如果0-1s内生成5个令牌没有请求到达,到1-2s内到达大量请求,就可能回导致20QPS
问题解决:设置令牌上限时预留一定的波动空间,来应对突发流量
漏桶算法
对于令牌桶算法,其所使用的资源较少,但是也存在问题,例如当流量突发不稳定时,QPS波动较大,就可以使用漏桶算法让每秒QPS更加平稳。
实现原理:定义一个漏桶,当请求到达时,将请求放入漏桶的队列中,而漏桶会以一定速率释放请求,即如果设置QPS为5,那么漏桶就会每200ms释放一个请求,使得请求运行,这就可以让每秒的QPS更加稳定。我们还可以设置请求的过期时间,例如设置为2000ms,第n个请求的预期执行时间为(n-1)*200 ms,如果请求预期执行时间大于2000ms,那么新的请求就会被拒绝。
优点:请求限流,让整体QPS更加平稳