本文介绍: 限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种预防措施。超时处理、线程隔离、降级熔断是在部分服务故障时,将故障控制在一定范围,避免雪崩。是一种补救措施。

一.雪崩问题及解决方案

1.雪崩问题–一个服务阻塞引起其他服务阻塞,造成越来越多的服务阻塞不可用

微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。

如图,如果服务提供者I发生了故障阻塞,当前的应用的部分业务因为依赖于服务I,因此也会被阻塞。此时,其它不依赖于服务I的业务似乎不受影响。

业务请求发生阻塞的原因有很多:

1.网络延迟:当客户端与服务端之间的网络延迟过高时,会导致请求阻塞。例如,当客户端等待从服务端获取响应时,如果网络延迟很高,则客户端将一直等待,直到超时或收到响应为止。

2.数据库操作:当业务请求需要对数据库进行读写操作时,如果数据库连接池中的连接被占满,就会导致请求阻塞,因为没有可用的连接来执行请求。

3.锁:当多个业务请求需要同时访问同一个资源时,例如同一个文件、同一个缓存或同一个数据库表,可能会出现锁竞争,导致请求阻塞。

4.过度消耗资源:当业务请求需要大量的计算或者消耗大量的内存或CPU资源时,系统可能无法及时响应其他请求,导致请求阻塞。

5.队列溢出:当业务请求的处理速度低于请求的到达速度时,请求队列可能会积累过多的请求,导致队列溢出,从而导致请求阻塞。

6.外部依赖:当业务请求依赖于其他服务或第三方库时,如果这些服务或库出现故障或响应缓慢,就会导致请求阻塞,直到这些服务或库返回响应为止。

但是,依赖服务I的业务请求被阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:

服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,那么当前服务也就不可用了。那么,依赖于当前服务的其它服务随着时间的推移,最终也都会变的不可用,形成级联失败,雪崩就发生了:

2.解决方法

解决雪崩问题的常见方式有四种:

【1】超时处理–超出时间返回错误信息

设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待

【2】线程隔离/仓壁模式–限制服务中每个业务能拥有的线程数

仓壁模式来源于船舱的设计:

船舱都会被隔板分离为多个独立空间,当船体破损时,只会导致部分空间进入,将故障控制在一定范围内,避免整个船体都被淹没。

调用者在调用服务提供者时,给每个调用的请求分配独立线程池,出现故障时,最多消耗这个线程池内资源,避免把调用者的所有资源耗尽 ,避免耗尽整个tomcat的资源,因此也叫线程隔离。

【3】熔断降级 /断路器–请求的数量超过异常的比例过高就熔断该服务【即拦击访问服务D的一切请求,直接报错】

断路器模式:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。

断路器会统计访问某个服务的请求数量,当发现访问服务D的请求异常比例过高时,认为服务D有导致雪崩的风险,会拦截访问服务D的一切请求,形成熔断。

统计异常比例:

比例过高:

【4】限流–限制服务访问的每秒访问量

流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。

3.总结

限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种预防措施。

超时处理、线程隔离、降级熔断是在部分服务故障时,将故障控制在一定范围,避免雪崩。是一种补救措施。

二.服务保护技术对比

在SpringCloud当中支持多种服务保护技术。早期比较流行的是Hystrix框架,但目前国内实用最广泛的还是阿里巴巴的Sentinel框架,这里我们做下对比:

Sentinel Hystrix
隔离策略 信号量隔离 线程池隔离/信号量隔离
熔断降级策略 基于慢调用比例或异常比例 基于失败比率
实时指标实现 滑动窗口 滑动窗口(基于 RxJava)
规则配置 支持多种数据源 支持多种数据源
扩展性 多个扩展点 插件的形式
基于注解的支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持
流量整形 支持慢启动、匀速排队模式 不支持
系统自适应保护 支持 不支持
控制台 开箱即用,可配置规则、查看秒级监控、机器发现等 不完善
常见框架的适配 Servlet、Spring Cloud、Dubbo、gRPC 等 Servlet、Spring Cloud Netflix

三.Sentinel的功能

1.簇点链路

当请求进入微服务时,首先会访问DispatcherServlet,然后进入Controller、Service、Mapper,这样的一个调用链就叫做簇点链路。簇点链路中被监控的每一个接口就是一个资源

默认情况下sentinel会监控SpringMVC的每一个端点(Endpoint,也就是controller中的方法),因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源。

Restful风格的API请求路径一般都相同,这会导致簇点资源名称重复。因此我们要修改配置,把请求方式+请求路径作为簇点资源名称:

流控、熔断等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则:

(1)流控:接口流量控制,根据接口控制每秒的访问量;
还可以进行线程隔离,控制每个服务的最大线程数量
(2)降级:降级熔断
(3)热点:热点参数限流,根据接口参数值出现频率控制每秒的访问量
(4)授权:请求的权限控制

2.流控

点击流控,进行设置,介绍一下里面提供的功能。

1.流控模式:在添加限流规则时,点击高级选项,可以选择三种流控模式
【1】直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
【2】关联:统计与当前资源相关的另一个资源,当另一个资源触发阈值时,对当前资源限流

两个资源又竞争关系,对高优先级资源触发阈值,对低优先级资源限流。
比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁,产生竞争。业务需求是优先支付和更新订单的业务,因此当修改订单业务触发阈值时,需要对查询订单业务限流。

【3】链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

2.流控效果是指请求达到流控阈值时应该采取的措施,包括三种:
【1】快速失败:QPS超过阈值时,拒绝新的请求
【2】warm up: QPS超过阈值时,拒绝新的请求;QPS阈值是逐渐提升的,可以避免冷启动时高并发导致服务宕机。

阈值一般是一个微服务能承担的最大QPS,但是一个服务刚刚启动时,一切资源尚未初始化(冷启动),如果直接将QPS跑到接近最大阈值,也可能导致服务瞬间宕机。
warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是 maxThreshold / coldFactor,持续指定时长后,逐渐提高到maxThreshold值。而coldFactor的默认值是3.
例如,设置QPS的maxThreshold为10,预热时间为5秒,那么初始阈值就是 10 / 3 ,也就是3,然后在5秒后逐渐增长到10. 

【3】排队等待:请求会进入队列,按照阈值允许的时间间隔依次执行请求;如果请求预期等待时长大于超时时间,直接拒绝。

例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待时长超过2000ms的请求会被拒绝并抛出异常。
那什么叫做预期等待时长呢?
比如现在一下子来了12 个请求,因为每200ms执行一个请求,那么:
第6个请求的预期等待时长 = 200 * (6 – 1) = 1000ms
第12个请求的预期等待时长 = 200 * (12-1) = 2200ms

现在,第1秒同时接收到10个请求,但第2秒只有1个请求,此时QPS的曲线这样的:

如果使用队列模式做流控,所有进入的请求都要排队,以固定的200ms的间隔执行,QPS会变的很平滑:

平滑的QPS曲线,对于服务器来说是更友好的。 

3.热点参数限流

之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。

1.全局参数限流

例如,一个根据id查询商品的接口:

访问/goods/{id}的请求中,id参数值会有变化,热点参数限流会根据参数值分别统计QPS,统计结果:

当id=1的请求触发阈值被限流时,id值不为1的请求不受影响。

配置示例:

代表的含义是:对hot这个资源的0号参数(第一个参数)做统计,每1秒相同参数值的请求数不能超过5

2.热点参数限流

刚才的配置中,对查询商品这个接口的所有商品一视同仁,QPS都限定为5.

而在实际开发中,可能部分商品是热点商品,例如秒杀商品,我们希望这部分商品的QPS限制与其它商品不一样,高一些。那就需要配置热点参数限流的高级选项了:

结合上一个配置,这里的含义是对0号的long类型参数限流,每1秒相同参数的QPS不能超过5,有两个例外:
如果参数值是100,则每1秒允许的QPS为10
如果参数值是101,则每1秒允许的QPS为15

3.注意事项

热点参数限流对默认的SpringMVC资源无效,需要利用@SentinelResource注解标记资源

再对该资源进行热点参数限流.

4.隔离和降级

限流是一种预防措施,虽然限流可以尽量避免因高并发而引起的服务故障,但服务还会因为其它原因而故障。

而要将这些故障控制在一定范围,避免雪崩,就要靠线程隔离(舱壁模式)和熔断降级手段了。

可以看到,不管是线程隔离还是熔断降级,都是对客户端(调用方)的操作。需要在调用方 发起远程调用时做线程隔离、或者服务熔断。

而我们的微服务远程调用都是基于Feign来完成的,因此我们需要将Feign与Sentinel整合,在Feign里面实现线程隔离和服务熔断。

1.Feign整合Sentinel的步骤

在application.yml中配置:feign.sentienl.enable=true

补充 :Feign异常处理

业务失败后,一般不会直接报错,而是对报错进行处理,返回用户一个友好提示或者默认结果,这个就是失败降级逻辑。

给FeignClient编写失败后的降级逻辑

①方式一:FallbackClass,无法对远程调用的异常做处理

②方式二:FallbackFactory,可以对远程调用的异常做处理
具体讲讲第二种:

给FeignClient编写FallbackFactory并注册为Bean
将FallbackFactory配置到FeignClient

2.线程隔离

线程隔离有两种方式实现:
【1】线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
【2】信号量隔离(Sentinel默认采用):不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。

信号量隔离的特点是?
基于计数器模式,简单,开销小
线程池隔离的特点是?
基于线程池模式,有额外开销,但隔离控制更强

实现方式:在feign接口的资源点击流控,设置线程数。

3.熔断降级

熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。

1.断路器控制熔断和放行是通过状态机来完成的。

状态机包括三个状态:
【1】closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
【2】open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
【3】half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
如果请求成功,则切换到closed状态。如果请求失败:则切换到open状态

2.断路器熔断策略有三种:慢调用、异常比例、异常数

慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。
异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。

5.授权规则

授权规则可以对请求方来源做判断和控制。

授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。
白名单:来源(origin)在白名单内的调用者允许访问
黑名单:来源(origin)在黑名单内的调用者不允许访问

点击左侧菜单的授权,可以看到授权规则:

资源名:就是受保护的资源,例如/order/{orderId}
流控应用:是来源者的名单,如果是勾选白名单,则名单中的来源被许可访问;如果是勾选黑名单,则名单中的来源被禁止访问。

比如:

我们允许请求从gateway到order-service,不允许其他请求不通过网关直接访问order-service,那么白名单中就要填写网关的来源名称(origin)

步骤:在网关中添加过滤器对每个请求添加名为origin,值为gateway的请求头;sentinel获取请求头判断是否值为gateway【重写RequestOriginParser接口】。

四.其他

1.sentinel异常处理

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = "未知异常";
        int status = 429;

        if (e instanceof FlowException) {
            msg = "请求被限流了";
        } else if (e instanceof ParamFlowException) {
            msg = "请求被热点参数限流";
        } else if (e instanceof DegradeException) {
            msg = "请求被降级了";
        } else if (e instanceof AuthorityException) {
            msg = "没有权限访问";
            status = 401;
        }

        response.setContentType("application/json;charset=utf-8");
        response.setStatus(status);
        response.getWriter().println("{"msg": " + msg + ", "status": " + status + "}");
    }
}

2.规则持久化

现在,sentinel的所有规则都是内存存储,重启后所有规则都会丢失。在生产环境下,我们必须确保这些规则的持久化,避免丢失。

规则是否能持久化,取决于规则管理模式,sentinel支持三种规则管理模式:
1.原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失。
2.pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则

3.push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新

​​​​​​​

原文地址:https://blog.csdn.net/bjjx123456/article/details/135330808

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_60104.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注