初识Sentinel
流量控制
隔离和降级
授权规则
规则持久化
雪崩问题及解决方案
服务保护技术对比
Sentinel介绍和安装
微服务整合Sentinel
如果服务D出现了问题,那么当服务A调用服务D时,会导致线程阻塞,资源得不到释放;随着时间的推移,越来越多访问服务D的线程都被阻塞,导致服务A的资源被耗尽。服务D出现问题,结果导致调用它的服务A也出现了问题。
微服务调用链路中,由于某个服务故障,进而引起整个链路中所有的服务都不可用,这就是雪崩。
(1)超时处理
客户端设置调用远程服务的超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止的等待。
它可以缓解雪崩问题,但是不能根本解决雪崩问题。如果请求qps量大于超时的释放量,最终也会导致雪崩。
(2)舱壁模式
限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,也叫线程隔离。
会有一定的资源浪费,比如业务2的10个线程由于服务C异常而导致不能处理任务业务而被浪费。
客户端进行处理
(3) 熔断降级
由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
客户端统计然后处理。
(4)流量控制
限制业务访问的QPS,避免服务因流量的突增而故障。
服务端处理。预防性质
以上四种方式可以结合使用。
方式 | 处理端 | 缺点 | Sentinel是否支持 |
---|---|---|---|
超时处理 | Client | 不能杜绝 | N |
线程隔离 | Client | 资源浪费 | Y |
熔断降级 | Client | 复杂,需要统计,而且需要设置恢复访问的规则 | Y |
流量控制 | Server | 预防性质,防止服务因突发流量而故障 | Y |
Sentinel是CS架构,需要安装server端
下载地址:https://github.com/alibaba/Sentinel/releases/tag/1.8.1
启动:java -jar xxx.jar
访问:localhost:8080
用户名密码:sentinel/sentinel
(1)引入依赖
:::info
com.alibaba.cloud:spring-cloud-starter-alibaba-sentinel
:::
(2)配置Sentinel Server地址
:::info
spring.cloud.sentinel.transport.dashbord=localhost:9090
:::
(3)访问微服务的任务断点(controller),触发sentinel监控
快速入门
流控模式
流控效果
热点参数限流
就是项目内的调用链路,链路中被监控的每个接口就是一个资源。默认情况下,sentinel会监控springMVC的每个端点(Controller),因此springmvc的每一个端点就是调用链路中的一个资源。
流控和熔断降级都是针对簇点链路中的资源来设置的,
(1)流控
QPS就是指限流的大小,单机阈值为1表示每秒只能处理一个请求,多出来的请求不再处理,返回429,flow limiting
针对来源,表示哪些访问该资源的请求进行限制,default表示不做区分,所有的请求都受这个规则控制。
从上面的描述可以看出,这个流控是在server端设置的,可以针对不同的client端进行单独设置。
统计当前资源的请求,触发阈值时对当前资源直接限流,是默认模式。
统计与当前资源相关的另一个资源,触发阈值时,对当前资源进行限流。
使用场景:比如用户支付时需要修改订单状态,同时用户需要查询订单。查询和修改订单会竞争,但明显修改订单的优先级要高于查询订单,因此,当修改订单业务触发阈值时,对查询订单进行限流。
统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流
只针对从/test2链路进来的请求进行流量控制
(1)定义资源
:::info
@SentinelResource
:::
(2)sentinel默认会将controller方法做context整合,导致链路模式的流控失效,需要修改配置
:::info
spring.cloud.sentinel.web-context-unify=falses
:::
包括三种流控效果:
(1)快速失败:达到阈值后,新的请求立即拒绝并抛出FlowException异常,这是默认的处理方式
(2)warm-up:预热模式,对超出阈值的请求同样是拒绝并抛出异常,但这种模式的阈值在刚启动阶段会缓慢增加,知道设置的最大值;
(3)排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长,超过最大等待时间的请求被直接拒绝。
设置两个值:threshold,coldFactor和 阈值时长,请求阈值初始值是threshold/coldFactor
当请求超过qps阈值时,先让请求进入一个队列,然后按照阈值允许的时间间隔一次执行。后来的请求必须等待前面的执行完成,如果请求预期的等待时间超过最大时长,则会被拒绝。
流量整形
之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。
默认参数设置:
代表对该资源host的 0号参数进行统计,每一秒相同参数值的请求不超过5.
高级配置:
设置2个例外:
(1)如果参数为100,那么qps为10
(2)如果参数为101,那么qps为15
注意:
热点参数限流对默认的springmvc资源无效,需要使用sentinelResource注解参能生效。
FeignClient整合Sentinel
线程隔离(舱壁模式)
熔断降级
当服务出现故障时,容易传染给调用它的服务。隔离和降级是对客户端(调用方)的保护,相关的代码写在服务的调用方。
(1)修改配置文件,开启Feign的Sentinel功能
:::info
feign.sentinel.enabled=true
:::
(2)给FeignClient编写失败后的降级逻辑
1)FallbackClass,无法对远程调用的异常做处理
2)FallbackFactory,可以对远程调用的异常做处理,优选方案
实现方法:
线程池隔离,信号量隔离(sentinel只支持这种方式)
线程池隔离中,会给每一个远程调用创建独立的线程,可以独立控制这个远程调用,如果超时,则终止这个线程,实现主动超时。
对于信号量隔离,调用远程服务时,使用的是原来的线程,也就无法实现主动超时,也不能异步调用。
配置限流规则时,阈值类型选择线程数就是在使用信号量隔离模式。(名字取的太恶心了)
为什么服务端的限流不能用线程数呢?
这里的线程数是指客户端在请求服务端时,最多同时能有5个请求在执行,当一个请求返回时,信号量回收一个,下一个请求可以利用该回收的信号量去请求服务端。
但服务端的限流是qps,也就是每秒钟能接收多少请求进来,即使这些请求需要处理很长时间,但至少进入下一秒,上一秒的请求即使全部都还没有处理完毕,也能接着接收请求。
由断路器统计服务调用的异常比例和慢请求比例,如果超出阈值则会熔断该服务,即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。
该功能是由Sentinel内部的状态机实现的。
断路器熔断策略有三种:慢调用,异常比例,异常数
业务的响应时长RT大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定阈值,则触发熔断。
解读:RT超过500ms的调用时慢调用,统计最近10000ms内的请求,如果请求量超过10次,并且慢调用比例不低于0.5,则触发熔断,熔断时长为5s,然后进入half-open状态,放行一次请求做测试。
统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。
统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于0.4,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。
授权规则
自定义异常结果
对网关漏洞的补救。
授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。
白名单:来源(origin)在白名单内的调用者允许访问
黑名单:来源(origin)在黑名单内的调用者允许访问
例如,我们限定只允许从网关过来的请求访问orderservice,那么流控应用中就填写网关的名称
Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求来源。
默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方,而且异常默认都是限流异常,flow limiting,这样不太太友好。如果想要自定义异常时的返回结果,需要实现BlockExceptionHandler接口:
BlockException包含很多子类:
规则管理模式
实现push模式
规则管理有3中模式:
原始模式:默认,将规则保存在内存中,重启服务会丢失
pull模式:控制台将配置规则推送到客户端,而客户端会将配置规则保存在本地文件或者数据库中,以后会定时去本地文件或数据库中查询,更新本地规则。
push模式:推荐模式。控制台将配置规则推送到远程配置中心,例如nacos。客户端监听nacos,获取配置变更的推送消息,完成本地配置更新。
Sentinel的配置默认是推送到客户端的,如果需要推送到nacos,那么需要修改源码。
限流的常见算法:计数器(窗口计数器,滑动窗口计数器),令牌桶(Token bucket),漏桶(Leaky bucket)
(1)将时间划分成多个窗口, 窗口时间跨度成为Interval,比如1000ms;
(2)每个窗口维护一个计数器,每有一次请求就将计数器加1,限流就是设置计数器阈值,比如设置为3;
(3)如果计数器超过了限流阈值,则超出阈值的请求都被丢弃。
缺点:
两个窗口的连续时间段内有大量请求,但是又没有超过阈值,导致限流失效,但是服务器可能因为负载过大而宕机。
滑动窗口计数器会将一个窗口划分为n个更小的区间,例如
(1)窗口时间跨度Interval为1秒,区间数量n=2,则每个小区间的时间跨度为500ms;
(2)限流阈值依然为3,时间窗口内请求超过阈值时,超出的请求被限流;
(3)窗口会根据当前请求所在时间(currentTime)移动,窗口范围从(currentTime - Interval)之后的第一个时区开始,到currentTime所在时区结束。
重要概念:窗口,区间
缺点:不能完全解决固定窗口计数器的问题,但可以通过增加区间数量来降低该问题出现的概率。
而且,滑动窗口的实现复杂。
以固定速率生成令牌,存入令牌桶中,如果令牌桶满了以后,多余的令牌会丢弃。
请求进来以后,必须先尝试从桶中获取令牌,获取到令牌后才可以被处理。
如果令牌桶中没有令牌,则请求等待被丢弃。
缺点:令牌只有满了之后才会被丢弃,存放在令牌桶中的令牌可能会在下一秒内被消费,而且下一秒自己产生的令牌也可能被消费,导致下一秒的qps可能达到令牌桶存放令牌的数量 + 一秒内生成的令牌数量。
将每个请求视作水滴放入漏桶;
漏桶以固定速率向外漏出请求来执行,如果漏桶空了,则停止漏水;
如果漏桶满了,则多余的水滴会被丢弃。
Sentinel在实现漏桶时,采用了队列等待模式;
让所有请求进入一个队列中,然后按照阈值允许的时间间隔一次执行,并发的多个请求必须等待,预期的等待时长 = 最近一次请求的预期等待时间 + 允许的间隔,如果请求预期的等待时间超出最大时长,则会被拒绝。
例如:QPS = 5,意味着没200ms处理一个队列中的请求,timeout=2000,意味着预期等待超过2000ms的请求会被拒绝。
Sentinel本质要做的事情就是:
(1)统计数据:统计某个资源的访问数据,如QPS, RT等信息
(2)规则判断:判断限流规则,隔离规则,降级规则,熔断规则是否满足
责任链,将不同功能,如限流,降级封装成一个个的slot,请求进入后逐个执行。
(1)前面三个slot是用来做数据统计的,后面的插槽是用来做规则判断的
(2)NodeSelectorSlot:用来构建调用链路,XXXNode
(3)ClusterBuilderSlot:用来构建全局数据统计的簇点
(4)StatisticsSot:真正做数据统计的节点,如滑动窗口
节点可以理解为与资源相关的一个类。
节点包括两类:
DefaultNode:代表链路树中的每一个资源,一个资源出现在不同链路中时,会创建不同的节点,ExtranceNode是一种特殊的节点,入口节点。链路模式的限流规则中会使用到。
ClusterNode:代表资源,一个资源不管出现在多少链路中,只会有一个ClusterNode,记录的是当前资源被访问的所有统计数据之和。实现默认模式和关联模式的限流规则。
默认情况下,controller中的方法会被作为资源,其他要额外定义为资源的方法需要使用Entry来表征。Entry表示的就是一种需要被保护的资源。
(1)编码声明资源
(2)注解式声明资源
@SentinelResource
默认情况下,controller不是EntranceNode,sentinel_spring_web_context是在Sentinel初始化context时创建的EntranceNode。
(1)什么是context?
context代表调用链路上下文,贯穿一次调用链路中的所有资源(Entry),基于ThreadLocal;
context维持着入口节点(EntranceNode),本次调用链路资源节点(curNode),调用来源(origin)等信息;
后续的Slot都可以通过context拿到DefaultNode胡总和ClusterNode,从而获取统计数据,完成规则判断;
context初始化的过程中,会创建EntranceNode,contextName就是EntranceNode的名称。
1.调用入口:
AbstractSentinelInterceptor
(1)获取resourceName,也就是controller方法的RequestMapping
(2)获取contextName,默认是sentinel_spring_web_context
(3)获取origin,基于自定义的RequestOriginParser来获取
(4)初始化context,ContextUtils.enter(contextName, origin)
1)创建EntranceNode(contextName)
2)创建Context,放入ThreadLocal
(5)标记资源,创建Entry
Entry e = SphU.entry(resourceName)
然后,entry就会执行ProcessorSlotChain
每一个Entry都会有一个执行链,数据统计,创建节点,然后限流判断等操作。
每一个资源有一个自己的执行链,不同的资源有不同的执行链
SphU.entry(resourceName)会根据当前的资源创建一个执行链(SPI加载)
一个请求链路一个context
一个资源,一个clusterNode
先放行,处理没有问题,那么再统计passNum和ThreadNum,还要给clusterNode也计数