Review
- 解决了服务拆分之后的服务治理问题:Nacos解决了服务治理问题
- OpenFeign解决了服务之间的远程调用问题
- 网关与前端进行交互,基于网关的过滤器解决了登录校验的问题
流量控制:避免因为突发流量而导致的服务宕机。
隔离和降级:避免微服务出现雪崩
避免非法的请求进入微服务当中
避免因为服务的重启而导致这些规则的丢失
1. 初始Sentinel
1.1 雪崩问题及解决方案
1.1.1 雪崩问题
微服务中,服务间调用关系错综复杂,一个微服务往往依赖于其它多个微服务。
如图,服务消费者调用服务提供者,如果服务提供者发生了故障,由于当前服务消费者应用的部分业务依赖于服务提供者,导致服务消费者的业务请求会被阻塞,因为服务消费者要等待服务提供者结果的返回,请求被阻塞,用户自然不会得到响应,Tomcat的这个线程也不会释放,因为阻塞它就不会释放Tomcat的连接,于是越来越多的用户请求到来,越来越多的线程会被阻塞,由于Tomcat服务器支持的线程和并发数有限,业务请求一直被阻塞,会导致服务器资源耗尽,从而导致其它业务请求请求不进来,因为由于当前服务挂了,其它服务调用当前服务无法得到当前服务的响应,这时候其它服务就会出现多条线程阻塞等待,此时其它服务若有大量的 业务请求涌入,就会导致大量线程积压,最终导致其它服务也挂掉,导致其它服务也故障不可用了,由于服务与服务之间的依赖性,服务之间有关联,故障会传播,形成级联失败,进而导致整个微服务系统造成灾难性的严重后果,导致整个调用链上的所有服务都挂掉,此时服务故障的"雪崩效应"就发生了 => 一个服务故障导致依赖于它的服务最终也出现故障了,导致依赖于它的服务最终被拖垮
在微服务架构中,服务与服务之间会通过远程调用的方式进行通信,一旦微服务调用链路中的某个服务发生故障或某个资源出现不稳定,例如,表现为timeout - 业务接口超时响应,业务接口响应时间过长,出现故障或阻塞,会导致其依赖服务(即调用该服务的其它服务)由于没有做好异常处理,导致自身也会发生故障,此时就会发生故障的蔓延,引起整个链路中的所有微服务都不可用,也就是引起整个链路中的所有微服务都无法访问的情况,最终导致系统瘫痪,导致调用链中的所有服务级联失败,这就是服务雪崩问题或者叫级联失败问题。
- 在微服务里面,雪崩问题是一个必须要解决的问题。
微服务保护
- 保证服务运行的健壮性,避免级联失败导致的雪崩问题,就属于微服务保护。
- 要防止雪崩的扩散,我们就要做好服务的容错,说白了就是保护自己不被猪队友拖垮的一些措施。
服务保护方案或容错保护措施
- 服务降级、服务熔断、流量控制(请求限流)、线程隔离、超时机制等
这些方案或多或少都会导致服务的体验上略有下降,比如
- 请求限流:降低了并发上限;
- 线程池隔离:降低了可用资源的数量;
- 服务熔断:降低了服务的完整度,部分服务变得不可用或弱可用。
但通过这些方案,服务的健壮性得到了提升。
容错保护就是当某个服务发生故障时,通过断路器的监控,给调用返回一个错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程,从而防止故障的蔓延。
解决雪崩问题的常见方式有四种:应对服务雪崩的一种微服务链路保护机制
- 前面三种是已经有服务故障了,我该怎么样去避免这个故障传递到其它服务而引起雪崩(3解决 + 1预防):
如何避免因服务故障而引起的雪崩问题?
超时处理:
- 在上游服务调用下游服务的时候,设定一个最大响应时间,即调用业务时设定超时时间(给业务设定超时时间),如果请求超过这个时间,下游未作出反应或没有响应就断开或释放该请求,释放掉该线程,直接返回错误信息,而不会让请求无休止等待,这样就不会导致一直占用Tomcat资源,可以在一定程序上缓解雪崩问题{缓解雪崩问题而不是100%解决雪崩问题,因为如果说释放或断开请求的速度没有进入请求的速度快,那么终有一天服务器端的资源还是有可能会被耗尽}
舱壁模式(线程(池)隔离)
- 限定每个业务接口能使用的线程数量(说白了就是给每个业务接口都分配一个独立的线程池),实现线程隔离,这个时候每个业务能够使用的线程资源是有限的(同时也限制了每个业务请求处理的并发量),这样就可以避免耗尽整个Tomcat的资源(因为一个业务一旦出现故障,它最多是把整个线程池内的资源耗尽,而不会导致整个Tomcat的资源被耗尽),因此也叫线程隔离。
- 这种模式它确实解决了雪崩问题,但是从资源上来讲会造成一定的浪费,比如服务提供者真的宕机了,但服务消费者每次还来去请求访问它,明明知道它挂了,还要去尝试访问,而且还要占用我一定的线程数,这显然是一种浪费。
服务熔断 - 断路器模式或熔断降级(模式):比较推荐的一种解决方案
- 由断路器统计业务执行的异常比例或慢调用比例,如果超出阈值则会熔断该业务,会认为该服务由导致雪崩的风险,会拦截访问该业务接口的一切请求,形成熔断,隔离对该服务的调用,熔断期间,所有请求快速失败,全部走fallback逻辑,资源快速释放,避免影响到其它的资源,从而避免最终雪崩问题的发生,该模式不会存在资源浪费的情况,因为如果知道服务出现故障了,就不让请求再去访问该服务了 => 类似于电路里面的保险丝儿。
- 当检测到服务恢复正常响应以后,我们再去放行访问该业务的请求。
- 熔断降级模式是解决雪崩问题里面比较好的一种方案。
如何避免因瞬间高并发流量而导致的服务故障?
请求限流 - 流量控制:
- 当系统负载较高时,如果持续让请求进入,可能会导致系统崩溃,无法响应。
- 限制业务访问的QPS(每秒查询率,每秒的响应请求数,每秒钟处理请求的数量),限制访问接口的请求的并发流量,避免服务因流量的激增或突增而出现故障, 避免因为突发流量而导致的服务宕机。
- 使用Sentinel(限流器)实现限流:Sentinel它可以按照服务所能够承受的一个频率去释放请求,当流量激增的时候,控制流量通过的速率,把雪崩问题扼杀在了摇篮当中。总结:流量控制是预防雪崩,避免出现雪崩问题!
思考:那我就只用流量控制不就可以从根源上避免出现雪崩问题了吗?其它三种我不用不就行了。。。
- 因为高并发引起的服务故障只是故障的原因之一,往往服务还会因为其它问题而出现故障,比如因为网络问题或者是Full GC引起的假死问题,这些问题都会引起服务故障,这个时候就要用到其它三种解决方案了。
总结:
- 限流是对服务的保护,避免因突增的高并发流量而导致服务故障,进而避免雪崩,是一种预防措施。
- 而超时处理、线程隔离(舱壁模式)、熔断降级(断路器模式)是在部分服务故障时,将故障控制在一定范围,避免雪崩,是一种补救措施。
1.2 服务保护框架或技术对比 - 容错保护技术选型对比
在SpringCloud当中支持多种服务保护技术:
- Netfix Hystrix:https://github.com/Netflix/Hystrix
- Sentinel:https://github.com/alibaba/Sentinel
- Resilience4J:https://github.com/resilience4j/resilience4j
断路器:Spring Cloud Circuit Breaker
Hystrix和Sentinel都是非常成熟可靠的服务保护工具,早期比较流行的是Hystrix框架,但目前国内使用最广泛的还是阿里巴巴的Sentinel服务保护框架(是Spring Cloud Alibaba的组件之一),这里我们做下对比:
关于Hystrix和Sentinel的对比,在Sentinel的官网上有一篇文章写的很详细:
sentinel-vs-hystrix | Sentinelsentinel-vs-hystrixhttps://sentinelguard.io/zh-cn/blog/sentinel-vs-hystrix.html
技术选型:Sentinel vs Hystrix本文将从多个角度对 Sentinel 和 Hystrix 进行对比,希望在面临技术选型的时候,对各位开发者能有所帮助。https://mp.weixin.qq.com/s/D8RKfnzofM-br_y4fTLIaA
1. 开源和维护
- 首先两者都是开源,Hystrix是Netflix开源的项目,而Sentinel是阿里巴巴开源的项目;
- 但Hystrix已经停止维护,Spring Cloud在已经发布的项目中已经移除了对Hystrix的支持,而Sentinel在阿里巴巴内部广泛使用,阿里团队一直在迭代更新,并且有着活跃的社区支持。
- Hystrix适用于Java语言的微服务架构,而Sentinel则支持多种语言和多种类型的应用架构,包括微服务、API网关、消息队列等
2. 隔离设计上的对比
Sentinel底层是基于信号量来实现资源隔离,而Hystrix提供两种隔离策略,Hystrix支持线程池隔离或信号量隔离,但默认情况下都是使用线程池隔离来实现资源隔离。
线程池隔离模式下需要配置线程池对应的参数,信号量隔离模式下需要配置最大并发数。
- 线程池隔离:在一个业务请求进入Tomcat以后,它会给每一个被隔离的业务创建一个独立的线程池,Hystrix也一样,Hystrix的线程池隔离针对不同的资源分别创建不同的线程池,这样,不同的服务调用都发生在不同的线程池中,在线程池阻塞情况时可以快速失败,线程池隔离的好处是隔离度比较高,资源和资源之间做到了最彻底的隔离,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的overhead比较大,线程上下文切换会有非常大的损耗,增加了线程切换的成本,特别是对低延时的调用有比较大的影响。另外,还需要预先给各个资源做线程池大小的分配,实际情况下,线程池隔离并没有带来非常多的好处,最直接的影响,就是会让机器资源碎片化。
- Hystrix的信号量隔离限制对某个资源调用的并发数,这样的隔离非常轻量级,仅限制对某个资源调用的并发数,而不是显式的去创建线程池(也就意味着不会去创建新的线程,这样就减少了线程的创建),所以overhead比较小,但是效果不错,但缺点是无法对慢调用自动进行降级,只能等待客户端自己超时,因此仍然可能会出现级联阻塞的情况。
- 而Sentinel可以通过并发线程数模式的流量控制来提供信号量隔离的功能,并且结合基于响应时间的熔断降级模式,可以在不稳定资源的平均响应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统。
Sentinel的线程隔离就是基于信号量隔离来实现的,而Hystrix两种都支持,但默认是基于线程池隔离。
3. 熔断降级的对比:熔断降级的策略不同
- Sentinel和Hystrix的熔断降级功能本质上都是基于熔断器或断路器模式;
Sentinel和Hystrix都支持基于失败比率(异常比率)的熔断降级,在调用达到一定量级并且失败比率达到设定的阈值时自动进行熔断,此时所有对该资源的调用都会被block,直到过了指定的时间窗口后才启发性的恢复;
- 而且Sentinel还支持基于平均响应时间的熔断降级,可以在服务响应时间RT持续飙高的时候自动熔断,拒绝掉更多的请求,直到一段时间后才恢复,这样可以防止调用非常慢,也就是防止慢调用造成级联阻塞的情况。
4. 配置方式不同
- Sentinel基于控制台来做配置,重启后失效;
- 而Hystrix是基于注解或配置文件来做配置,是永久生效。
5. Sentinel特性
- Hystrix主要提供了服务降级、服务熔断、线程隔离等功能,而Sentinel除了提供上述功能之外,还提供了实时监控、流量控制、热点参数限流、系统自适应负载保护等功能。
- 另外Sentinel的很多配置都能够动态推送到Sentinel客户端进行更新无需重启(热更新),而Hystrix部分配置需要重启才能更新
1. 轻量级、高性能
- Sentinel非常轻量级,其核心sentinel-core没有任何多余依赖,打包后只有不到200KB,非常轻量级,同时Sentinel非常高性能,引入Sentinel带来的性能损耗非常小,只有在业务单机量级超过25wQPS的时候才会有一些显著的影响,单机QPS不太大的时候损耗几乎可以忽略不计。
2. 流量控制
- Sentinel可以针对不同的调用关系,以不同的运行指标(如QPS、并发调用数、系统负载)为基准,对系统资源的调用进行流量控制,将随机的请求调整成合适的形状。
Sentinel支持多样化的流量整形策略,在QPS过高时可以自动将流量调整成合适的形状,常用的有:
- 直接拒绝模式:即超出的请求直接拒绝。
- 慢启动预热模式:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
- 匀速器模式:利用Leaky Bucket漏桶算法实现的匀速模式,严格控制了请求通过的时间间隔,同时推积的请求将会排队,超过超时时长的请求直接被拒绝。
Sentinel还支持调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于Sentinel强大的调用链路统计信息,可以提供精准的不同维度的限流。
3. 系统负载保护或系统自适应保护
- 当系统负载较高时,如果仍持续让请求进入,可能会导致系统崩溃,无法响应。
- 在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去,如果这个时候其它的汲取也处在一个边缘状态的时候,这个时候增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用,针对这个情况,Sentinel提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
4. 实时监控和控制面板
- Sentinel控制台(Dashboard)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便的去查看监控和进行配置。
5. 生态
- Sentinel目前已经针对Servlet、Dubbo、Spring Boot/Spring Cloud、gRPC等进行了适配,用户只需引入相应引来并进行简单配置即可非常的方便的享受Sentinel的高可用流量防护能力。
1.3.Sentinel介绍和安装
1.3.1.初识Sentinel
- Sentinel阿里巴巴开源的一款微服务流量控制组件,是阿里巴巴开源的一款服务保护框架,目前已经加入SpringCloudAlibaba中,阿里开源的流量防卫兵Sentinel。
- 官网地址:home | Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要,Sentinel是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、预热、消息队列削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备可视化的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 多样化的流量控制:资源粒度、调用关系、指标类型等多维度的流量控制
- 完善的SPI扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel的客户端依赖底层会和控制台建立连接,建立连接以后就可以做实时的监控,它可以监控微服务内部的所有接口的一个运行情况等。
Sentinel的熔断降级
什么是熔断降级?
- 除了流量控制以外,降低调用链路中的不稳定资源也是Sentinel的使命之一。
- 由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生积压。
Sentinel和Hystrix的原则是一致的:当调用链路中的某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
熔断降级设计理念
在限制的手段上,Sentinel和Hystrix采取了完全不一样的方法。
Sentinel对这个问题采取了两种手段:
- 通过并发线程数进行限制
和资源池隔离的方法不同,Sentinel通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响,这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如RT响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积,当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝,堆积的线程完成任务后才开始继续接收请求。
使用线程池隔离:当服务提供者不可用时,就会出现服务调用者的线程池里所有的线程都因等待响应而被阻塞,这个时候就会有上下文切换的开销。。。。
当线程池阻塞时,你其它所有请求打进来也会被阻塞,除非当线程池能够处理新请求了,那你此时被阻塞的线程就要被唤醒,这个时候就会有线程上下文的切换开销;而控制线程并发数就是你一旦达到我这个线程上限的阈值,你还来请求,我就直接给你拒绝掉,这样也就不存在什么阻塞了,自然不会有线程上下文切换的开销。
- 通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel还可以通过响应时间来快速降级不稳定的资源,当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
1.3.2 安装Sentinel控制台
官网详情:
dashboard | Sentinel
Sentinel官方提供了UI控制台,方便我们对系统做限流设置,其实就是下载一个jar包。
1. 将其拷贝到一个你能记住的非中文、不包含特殊字符的目录下,重命名为sentinel-dashboard,然后运行命令:
java -jar sentinel-dashboard.jar
2. jar包运行完成以后它底层其实就是一个基于Spring Boot的Web应用,然后访问:localhost:8080 即可看到控制台页面,默认的账户和密码都是sentinel
注意:Sentinel控制台目前仅支持单机部署,启动Sentinel控制台需要JDK版本为1.8及以上版本。
登录后,即可看到控制台,但是登录后,我们发现一片空白,什么都没有:
默认会监控sentinel-dashboard服务本身。。。。。
- 这是因为我们还没有让微服务去和Sentinel去做整合,因此我们在控制台看不到任何信息。
Sentinel规则种类
- Sentinel主要提供了五种流量控制:
- 系统规则是对当前应用所在的服务器的一种保护,不过这个保护的规则只对Linux系统有效,并且系统规则仅对入口流量生效,入口流量指的是进入应用的流量。
- 集群流控是指把这种限流的规则放在集群的场景下去做判断,而不再是针对单个机器。
1.4 微服务整合Sentinel
- 要使用Sentinel肯定要结合微服务。
我们在微服务模块中整合Sentinel,并且连接sentinel-dashboard控制台,步骤如下:
1. 引入Sentinel依赖
<!-- 引入sentinel依赖-->
<dependency><groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2. 配置Sentinel的控制台地址,修改application.yaml文件,添加下面内容:
spring:cloud: sentinel:transport:dashboard: localhost:8080
3. 重启微服务,访问微服务的任意端点,触发sentinel监控
什么是端点呢?
-
Spring MVC的任意一个Controller接口都是一个端点。
2. Sentinel实现请求限流或流量控制
雪崩问题虽然有四种方案,但是限流是避免服务因突发的流量而导致服务发生故障,是对微服务雪崩问题的预防。我们先学习这种模式。
2.1 快速入门
簇点链路
- 簇点链路(即单机调用链路)就是项目内的调用链路(当请求进入微服务时,首先会访问DispatcherServlet,然后进入Controller,Controller调Service,Service调Mapper,这样的一个调用链就叫做簇点链路),簇点链路中被监控的每个接口就是一个资源。
- 默认情况下Sentinel会监控Spring MVC的每一个端点(Endpoint接口 - Controller接口,也就是Controller中的方法),因此Spring MVC的每一个端点(Endpoint)就是调用链路中的一个资源。
- 如果说你的Service、你的Mapper也想被监控,那将来就要利用Sentinel当中的一些特殊的注解去实现了。
树状视图:
默认情况下Sentinel会把接口路径作为簇点资源的名称,因此,我们看到/carts这个接口路径就是一个簇点,我们可以对其进行流控(流量控制 - 限流)、熔断(熔断降级)、热点(热点参数限流,是限流的一种)、授权(请求的权限控制、隔离)等保护措施 => 这些都是针对簇点链路中的资源来设置的,我们可以点击对应资源后面的按钮来设置规则。
不过,需要注意的是,我们的Spring MVC接口是按照RESTful风格设计,RESTful风格的API请求路径一般都相同,这会导致簇点资源重复,因此购物车的查询、删除、修改等接口全部都是/carts路径:
默认情况下Sentinel会把路径作为簇点资源的名称,无法区分路径相同但请求方式不同的接口,查询、删除、修改等都被识别为一个簇点资源,这显然是不合适的。
所以我们可以选择打开Sentinel的请求方式前缀,把 请求方式 + 请求路径 作为簇点资源名称:
- 首先,在服务的application.yml中添加下面的配置:
spring:cloud:sentinel:transport:dashboard: localhost:8090http-method-specify: true # 开启请求方式前缀
然后,重启服务,通过页面访问购物车的相关接口,可以看到sentinel控制台的簇点链路发生了变化:
每一个资源名都是一个EndPoint端点~!
请求限流 - 流量控制
流量控制,其原理是监控应用流量的QPS(每秒查询率)或并发线程数等指标,当达到执行的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
- 在簇点链路后面点击流控按钮,即可对其做限流配置:
表单中可以填写流控规则或限流规则,如下:
资源名:唯一名称,默认是请求路径,可自定义
针对来源:指定对哪个微服务进行限流,默认指default,意思是不区分来源,全部限制
在添加限流规则时,可以选择两种阈值类型:
- QPS即每秒钟的请求数量
- 线程数:是该资源能使用的Tomcat线程数的最大值,也就是通过限制线程数量,实现舱壁模式 => 说白了就是指定信号量的最大值
其含义是限制 /order/{orderId}这个簇点资源的单机QPS为1,即每秒只允许1次请求,超出的请求会被拦截并报错。
单机QPS阈值应该设置为多少呢?
- 设置为该接口最大的并发量值就行 => 通过JMeter去做压测,去做限流测试得知该接口的最大并发量值。
如何利用JMeter去做限流测试?
- 可以利用JMeter这样的工具去做这种并发的模拟,每秒发出X个请求。
JMeter快速入门
1. 安装JMeter
- JMeter依赖于JDK,所以必须确保当前计算机上已经安装了JDK,并且配置了环境变量。
1.1.下载
可以Apache Jmeter官网下载,地址:Apache JMeter - Download Apache JMeter
1.2.解压
因为下载的是zip包,解压缩即可使用,目录结构如下:
其中的bin目录就是执行的脚本,其中包含启动脚本:
1.3.运行
双击即可运行,但是有两点注意:
-
启动速度比较慢,要耐心等待
-
启动后黑窗口不能关闭,否则JMeter也跟着关闭了
JMeter是用Java写的~!
2. 快速入门
2.1.设置中文语言
默认JMeter的语言是英文,需要设置:
效果:
注意:上面的配置只能保证本次运行是中文,如果要永久中文,需要修改JMeter的配置文件。
打开JMeter文件夹,在bin目录中找到 jmeter.properties,添加下面配置:
language=zh_CN
注意:前面不要出现#,#代表注释,另外这里是下划线,不是中划线
2.2.基本用法
在Test Plan测试计划上点鼠标右键,选择添加 > 线程(用户) > 线程组:
在新增的线程组中,填写线程信息:
给线程组点鼠标右键,添加取样器 => HTTP请求:
编写取样器内容:
添加监听器 => 添加汇总报告:
添加监听器 => 查看结果树:
运行:
注意,不要点击菜单中的执行按钮来运行。
汇总报告结果:
结果树:
可以看到,成功的请求每次只有5个。
2.2 流控模式
在添加限流规则时,点击高级选项,可以选择三种流控模式:
-
直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
-
关联:统计与当前资源相关的另一个资源(即关联资源),当关联资源触发阈值时,就会对当前资源限流,避免影响关联资源 => 给谁限流,就给谁加流控规则 高优先级资源触发阈值,对低优先级资源限流。
-
链路:统计从指定链路(即入口资源)访问到本资源的请求(是对请求来源的一种统计),只统计从指定资源进入当前资源的请求 ,触发阈值时,对指定链路限流,是对请求来源的限流
关联模式的使用场景:
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联,比如对数据库同一个字段的读操作和写操作存在争抢,读操作过于频繁,自然就会影响写操作 ,反之亦然,如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量,此时就可以使用关联模式限流来避免具有关联关系的资源之间过度的争抢。
- 比如用户支付时需要修改订单状态,同时用户要查询订单,查询和修改操作会争抢数据库锁,产生竞争。
- 但是支付业务的优先级要高于查询业务,因此当修改订单状态的业务触发阈值时,需要对查询订单业务做限流。
Blocked by Sentinel(flow limiting)
总结
满足下面两个条件可以使用关联模式:
- 两个有竞争关系的资源
- 一个优先级较高,一个优先级较低,我们希望的是当优先级高的资源触发阈值时,对优先级较低的资源做限流
流控模式-链路
链路流控模式指的是当从某个接口过来的资源达到限流条件时,开启限流。
- Sentinel默认只标记Controller中的方法为资源,如果要标记其它方法,需要利用@SentinelResource注解,示例
@SentinelResource("goods")public void queryGoods() { System.err.println("查询商品");}
链路模式中,是对不同来源的两个链路做监控。
但是Sentinel默认会给进入SpringMVC的所有请求设置同一个root资源,会导致链路模式失效。
Sentinel默认会将Controller方法做context上下文整合,导致链路模式的流控失效,我们需要关闭这种对SpringMVC的资源聚合,需要修改application.yml,添加配置:
spring:cloud:sentinel:web-context-unify: false # 关闭context整合
2.3 流控效果
在流控的高级选项中,还有一个流控效果选项,流控效果是指请求达到流控阈值时应该采取的措施,包括三种:
- 快速失败(默认):达到阈值后,新的请求会被立即拒绝并抛出FlowException异常,是默认的处理方式。
- Warm Up:也叫预热模式,是应对服务冷启动的一种方案,对超出阈值的新请求同样是拒绝并抛出异常,但这种模式阈值会动态变化,是从一个较小值逐渐增加到最大阈值,它会有一个预热时间(有一个缓冲阶段),持续指定预热时间后,逐渐提高到maxThreshold最大阈值,初始阈值是maxThreshold最大阈值 / coldFactor冷启动因子,而coldFactor的默认值是3。
- 排队等待:让所有的请求按照先后次序排队执行 ,让请求以均匀的速度通过,以固定的时间间隔执行,后来的请求必须等待前面执行完成,单机阈值为每秒通过数量,其余请求进入到一个队列当中排队等待,它还会设置一个超时时间,如果请求预期的等待时间大于或超过超时时间还未被处理,则请求会被直接丢弃。
流控效果 - Warm Up - 预热模式
- 阈值一般是一个微服务能承担或承受的最大的QPS。
- 当一个服务刚刚启动时,一切资源尚未初始化(冷启动),如果直接将QPS跑到最大值,可能导致服务瞬间宕机。
- 预热模式的QPS阈值是逐渐提升的,预热模式就是为了避免冷启动那一刻过高并发导致的服务故障或服务宕机。
- 预热模式适用于将突然增大的流量转换为缓步增长的场景。
配置流控规则:
流控效果 - 排队等待
- 当请求超过QPS阈值时,快速失败和Warm Up会拒绝新的请求并抛出异常,而排队等待则不会。
- 排队等待则是让所有请求进入一个队列中,然后按照单机阈值允许的时间间隔依次执行请求,后来的请求必须等待前面执行完成,如果请求预期的等待时间大于超时时间或超出最大时长,则会被直接拒绝并抛出异常。
- 例如:QPS = 5,意味着每200ms处理一个队列中的请求,前面的请求执行完,第二个请求一定要等够200ms
- 使用队列模式做流控,所有进入的请求都要进行排队,以固定的时间间隔执行,QPS会变得很平滑,平滑的QPS曲线,对于服务器来说是更友好的(本质:流量整型)。
添加流控规则
2.4 热点参数限流
- 热点即经常访问的数据。
之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值;
而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值 => 热点参数限流是一种更细粒度的限流。
注意事项:
- 热点参数限流对默认的Spring MVC资源无效,需要利用@SentinelResource("资源名称")注解标记资源。
- 热点参数限流的参数类型只能是基本数据类型以及String字符串,如果你的参数不是这些类型,是没有办法做热点参数限流的~!
3. 隔离和降级
- 限流是一种预防措施,虽然限流可以尽量避免服务因高并发而引起的服务故障,但服务还会因为其它原因而故障,而要将这些故障控制在一定范围,避免雪崩,就要靠线程隔离(舱壁模式)和熔断降级的手段了。
Sentinel支持的雪崩解决方案:线程隔离 & 熔断降级
线程隔离(舱壁模式)
- 服务调用者在调用服务提供者时,给每个调用的请求分配独立线程池,当出现故障时,最多消耗这个线程池内的资源,避免把服务调用的所有资源耗尽。
熔断降级 - 服务熔断
- 在服务调用者即调用方这边加入断路器,由断路器统计对服务提供者的调用,如果调用的失败比例或慢请求比例过高,则超出阈值后会熔断该业务,不允许访问该接口的业务请求;而当服务恢复时,断路器会放行访问该服务的请求。
总结:
- 不管是线程隔离还是熔断降级,都是对客户端或服务调用方的一种保护,需要在调用方发起远程调用时做线程隔离,或者服务熔断,而我们的微服务远程调用都是基于OpenFeing来完成的,因此我们需要将OpenFeign与Sentinel整合,在Feign里面实现线程隔离和服务熔断。
3.1 Sentinel实现线程隔离
线程隔离的两种手段或两种实现方式:
- 线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果(线程池隔离会给每一个远程调用创建独立线程,有额外开销,但隔离控制更强)
- 信号量隔离(Sentinel采用的就是信号量隔离):不创建线程池,而是基于计数器模式,记录业务使用的线程数量(每进入一个请求,计数器就会减一),当业务使用的线程数量达到信号量上限时,禁止新的请求(信号量隔离不会开启独立线程,而是使用的是原来处理Tomcat请求的那个线程,无额外开销,消耗比较低,都没有独立线程,肯定也没有异步调用)
两者的优缺点:
扇出:现在请求到我这个服务了,我这个服务又依赖于N个其它的服务,那么这个就叫扇出。
扇出越高,调用的越多,我需要开启的线程也越多,那我的消耗也就越大。
1. 首先要让OpenFeign整合Sentinel
- 修改服务调用者的application.yml文件,开启Feign的Sentinel功能
feign:sentinel:enabled: true # 开启feign对sentinel的支持或整合
接着重启服务,在控制台就可以看到该服务的业务接口自动变成了一个簇点资源。
2. 配置线程隔离
在弹出的表单中填写下面内容:
注意,这里勾选的是并发线程数限制,这样该接口能使用的线程资源被限制了,也就是说这个查询功能最多使用5个线程,而不是5QPS。如果查询商品的接口每秒处理2个请求,则5个线程的实际QPS在10左右,而超出的请求自然会被拒绝。
我们利用JMeter测试,每秒发送100个请求:
3.2 Sentinel实现服务熔断
- 对于一些不太健康的接口,RT响应耗时较长的接口我们应该停止调用,直接走降级逻辑,说白了就是将该接口熔断,直接走fallback逻辑,让请求快速失败,资源快速释放,避免影响到当前服务,这样的话就不用等待,从而避免最终雪崩问题的发生;而当服务接口恢复正常后,再允许调用,这其实就是断路器的工作模式了。
编写降级逻辑
- 业务失败后,即触发限流或熔断后的请求不一定要直接报错,也可以返回一些默认数据或者友好提示,用户体验会更好,这就是失败后的降级逻辑。
- 注意:走fallback就不要自己抛异常了,否则会失效!
给FeignClient编写失败后的降级逻辑有两种方式:
- 方式一:基于FallbackClass,无法对远程调用的异常做处理
- 方式二:基于FallbackFactory,可以对远程调用的异常做处理,我们一般都会选择这种方式
演示基于FallbackFactory的失败降级处理:
步骤一:需要自定义降级处理类,实现FallbackFactory<T>接口
步骤二:将自定义的降级处理类注册为Spring当中的一个Bean
步骤三:将FallbackFactory配置到FeignClient - 在FeignClient接口中使用自定义降级处理类
服务熔断
熔断机制概念普及
- 在互联网系统中,当下游服务因访问压力过大而导致响应变慢或响应失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用,这种牺牲局部,保全整体的措施就叫做熔断。
- Sentinel中的断路器不仅可以统计某个接口的慢请求比例,还可以统计异常请求比例,当这些比例超出阈值时,就会熔断该接口, 即拦截访问该接口的一切请求,降级处理;当该接口恢复正常时,再放行对于该接口的请求。
断路器的控制熔断和放行是通过状态基本来完成的,断路器的工作状态切换有一个状态机来控制。
状态机包括三个状态:服务熔断的三种状态
-
Closed:熔断关闭状态(默认情况下肯定都是处于Cloesd关闭状态),断路器放行所有请求,并开始统计异常比例、慢请求比例,超出比例阈值则切换到Open状态
-
Open:熔断打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,走fallback逻辑,快速失败 / 立刻失败,直接走降级逻辑。Open状态持续一段时间后,即配置的熔断时长结束后会进入Half-Open状态。
-
Half-Open:半熔断状态或半开状态,放行一次请求,根据执行结果来判断接下来的操作。
- 请求成功 - Success:说明服务已恢复,则切换到Closed熔断关闭状态,关闭断路器
- 请求失败 - Fail:则重新进入Open熔断开启状态,打开断路器
点击Sentinel控制台中簇点资源后的熔断按钮来配置熔断策略:
断路器熔断策略有三种:慢调用、异常比例、异常数
慢调用:
- 业务的响应时间RT大于指定的最大响应时间或时长 - 最大RT的请求会被认定为是慢调用请求,在指定时间内,如果请求数量超过设定的最小请求数,并且慢调用比例大于设定的比例阈值(0~1之间),则会触发熔断。
异常比例
- 统计指定时间内的调用,如果调用次数超过指定的最小请求数,并且出现异常的比例达到设定的比例阈值,则触发熔断。
异常数
- 统计指定时间内的调用,如果调用次数超过指定的最小请求数,并且异常调用的次数超过指定的异常数,则触发熔断。
4. Sentinel授权规则
- 授权规则可以对请求方来源做判断和控制,授权规则是对请求者的一种身份的判断。
- 根据调用来源判断该次请求是覅允许放行,此时就可以利用Sentinel的来源访问控制的功能。
- 来源访问控制根据资源的请求来源(origin)限制资源是否通过:若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
万一微服务地址泄漏了,那可能访问微服务时就不经过网关服务了。。。
授权规则的授权类型有白名单和黑名单两种方式:
资源名:就是受保护的资源,例如/order/{orderId}
流控应用:是来源者的名单,填写的是请求来源的名称:许可来源的调用者或允许调用者的名字和禁止访问的来源的调用者
- 白名单(允许的名单):来源在白名单内的调用者允许访问
- 黑名单(禁止访问的人的名单):来源在黑名单内的调用者不允许访问
如何获取origin请求来源呢?
- Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的。
- 这个方法的作用就是从request对象中,获取请求者的origin值并返回。
获取请求来源的接口:
/*** 请求来源解析器*/
public interface RequestOriginParser {/*** 从请求request对象中获取origin,获取方式自定义*/String parseOrigin(HttpServletRequest request);
}
默认情况下,Sentinel不管请求者从哪里来,返回值永远是default,也就是说一切请求的来源都被认为是一样的值default。
因此,我们需要自定义这个接口的实现,让不同来源的请求,返回不同的origin。
在Getway网关服务中,利用网关的过滤器添加名为getway的origin头,此时经过网关的所有请求,一定会带上这样的头。
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;@Component
public class HeaderOriginParser implements RequestOriginParser {@Overridepublic String parseOrigin(HttpServletRequest request) {// 1.获取请求头String origin = request.getHeader("origin");// 2.利用StringUtils工具类做非空判断if (StringUtils.isEmpty(origin)) {origin = "blank";}return origin;}
}
5. Sentinel自定义异常返回
默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方,前端页面的异常结果都是flow limmiting(限流),这样不够友好,无法得知是限流还是降级还是授权拦截。
如果要自定义异常时的返回结果,需要实现BlocExceptionHandler接口 - 阻塞异常处理器:
这个方法有三个参数:
- HttpServletRequest request:request对象
- HttpServletResponse response:response对象
- BlockException e:被sentinel拦截时抛出的异常
BlockException包含很多个子类,分别对应不同的场景:
-
FlowException 限流异常 => 限流返回的状态码一般都是429
-
DegradeException 降级异常
-
ParamFlowExcption 参数限流异常
-
AuthorityException 授权异常
-
SystemBlockException 系统负载异常
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {@Overridepublic 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 + "}");}
}
6. 规则持久化
- Sentinel的所有规则默认都是内存存储,重启后所有规则都会丢失,在生产环境下,我们必须确保这些规则的持久化,避免丢失。
6.1 Sentinel规则管理的常见模式
规则是否能持久化,取决于规则管理模式,Sentinel控制台支持三种规则管理模式:
-
原始模式:Sentinel的默认模式,将规则保存在内存,服务重启后规则会丢失。
-
pull模式
-
push模式
推荐:实现其中的push模式,完成规则的持久化。
pull和push这两种模式都可以实现规则的持久化,只不过在实现的方式上有差异。
pull模式
- Sentinel控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中,以后会定时轮询的去本地文件或数据库中查询,更新本地规则。
- 由于是定时的去更新本地规则,所以pull模式的缺点是不保证一致性与实时性,拉取过于频繁也可能会有性能问题。
push模式
- Sentinel控制台将配置规则推送到远程配置中心,比如Nacos统一配置中心、Zookeeper等配置中心,接着Sentinel客户端会去监听Nacos,获取配置变更的推送消息,完成本地配置的实时更新。
- push模式有更好的实时性和一致性保证,因此生产环境下一般采用 push 推送模式的数据源。
- 缺点:引入第三方依赖