1. 简介

1.1 分布式系统常见问题

在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务,如果其中的下单服务不可用, 就会出现线程池里所有线程都因等待响应而被阻塞, 从而造成整个服务链路不可用, 进而导致整个系统的服务雪崩。

服务雪崩效应: 因服务提供者由于某种原因提供的服务不可用,导致服务调用者不可用,并且这种不可用被逐渐放大,这就叫服务雪崩效应。

在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致:进一步加大请求流量。所以归根结底导致雪崩效应的最根本原因是:大量请求线程同步等待造成的资源耗尽。当服务调用者使用同步调用时, 会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了。

1.2 分布式系统解决方案

1.2.1 超时机制

在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题。

1.2.2 服务限流

QPS(Query Per Second),QPS 其实是衡量吞吐量(Throughput)的一个常用指标,就是说服务器在一秒的时间内处理了多少个请求 —— 我们通常是指 HTTP 请求,显然数字越大代表服务器的负荷越高、处理能力越强。作为参考,一个有着简单业务逻辑(包括数据库访问)的程序在单核心运行时可以提供 50 - 100 左右的 QPS,即每秒可以处理 50 - 100 个请求。

Sentinel入门 - 图1

1.2.3 服务隔离

原理:用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。

信号隔离也可以用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请, 如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销。信号量的大小可以动态调整, 线程池大小不可以。

1.2.4 服务熔断

远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断。

软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间后(例如10秒),断路器会进入半开状态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过”跳闸“,应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的“自我修复“。

所以,同样的道理,当依赖的服务有大量超时时,在让新的请求去访问根本没有意义,只会无畏的消耗现有资源。比如我们设置了超时时间为1s,如果短时间内有大量请求在1s内都得不到响应,就意味着这个服务出现了异常,此时就没有必要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费。
Sentinel入门 - 图2

1.4.5 服务降级

有服务熔断,必然要有服务降级。
所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fallback(回退)回调,返回一个缺省值。 例如:(备用接口/缓存/mock数据) 。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。

1.3 了解Sentinel

Sentinel 是阿里巴巴开源的,面向分布式服务架构的高可用防护组件,主要以流量为切入点,从流量控制、流量整形、熔断降级、系统自适应保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀、冷启动、消息削峰填谷、自适应流量控制、实时熔断下游不可用服务等,是保障微服务高可用的利器,原生支持 Java/Go/C++ 等多种语言,并且提供 Istio/Envoy 全局流控支持来为 Service Mesh 提供高可用防护的能力。

Sentinel 的技术亮点:

  • 高度可扩展能力:基础核心 + SPI 接口扩展能力,用户可以方便地扩展流控、通信、监控等功能
  • 多样化的流量控制策略(资源粒度、调用关系、流控指标、流控效果等多个维度),提供分布式集群流控的能力
  • 热点流量探测和防护
  • 对不稳定服务进行熔断降级和隔离
  • 全局维度的系统负载自适应保护,根据系统水位实时调节流量
  • 覆盖 API Gateway 场景,为 Spring Cloud Gateway、Zuul 提供网关流量控制的能力
  • 实时监控和规则动态配置管理能力

Sentinel入门 - 图3
在原来的 Spring Cloud Netflix 系列中,有自带的熔断组件 Hystrix ,是 Netflix 公司提供的一个开源的组件,提供了熔断、隔离的这些特性,不过 Hystrix 在 2018 年 11 月份开始,就不再迭代开发,进入维护的模式。同年开源的 Spring Cloud Alibaba (SCA) 提供了一站式的解决方案,默认为 Sentinel 整合了 Spring Web、RestTemplate、FeignClient 和 Spring WebFlux。Sentinel 在 Spring Cloud 生态中,不仅补全了 Hystrix 在 Servlet、RestTemplate 和 API Gateway 这一块的空白,而且还完全兼容了 Hystrix 在 FeignClient 中限流降级的用法,并且支持运行时灵活地配置和调整限流降级规则。同时 SCA 还集成了 Sentinel 提供的 API gateway 流控模块,可以无缝支持 Spring Cloud Gateway 和 Zuul 网关的流控降级。

1.4 了解Sentinel使用场景

一些普遍的使用场景:

  • 在服务提供方(Service Provider)的场景下,我们需要保护服务提供方自身不被流量洪峰打垮。这时候通常根据服务提供方的服务能力进行流量控制,或针对特定的服务调用方进行限制。我们可以结合前期压测评估核心接口的承受能力,配置 QPS 模式的限流,当每秒的请求量超过设定的阈值时,会自动拒绝多余的请求。
  • 为了避免调用其他服务时被不稳定的服务拖垮自身,我们需要在服务调用端(Service Consumer)对不稳定服务依赖进行隔离和熔断。手段包括信号量隔离、异常比例降级、RT 降级等多种手段。
  • 当系统长期处于低水位的情况下,流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。这时候我们可以借助 Sentinel 的 WarmUp 流控模式控制通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,而不是在一瞬间全部放行。这样可以给冷系统一个预热的时间,避免冷系统被压垮。
  • 利用 Sentinel 的匀速排队模式进行 “削峰填谷”,把请求突刺均摊到一段时间内,让系统负载保持在请求处理水位之内,同时尽可能地处理更多请求。
  • 利用 Sentinel 的网关流控特性,在网关入口处进行流量防护,或限制 API 的调用频率。

    1.5 部署Sentinel

    1、下载 sentinel,网址:https://github.com/alibaba/Sentinel/releases
    2、创建目录 /usr/local/sentinel,将 jar 包放到该目录下。
    1. cd /usr/local/ && mkdir sentinel && cd sentinel
    3、在当期目录新建脚本文件 start.sh 并启动。
    1. nohup java -Dproject.name=sentinel-dashboard \
    2. -Dserver.port=8849 \
    3. -Dcsp.sentinel.dashboard.server=192.168.58.100:8849 \
    4. -Dcsp.sentinel.log.dir=/usr/local/sentinel/logs/csp \
    5. -Dcsp.sentinel.log.use.pid=true \
    6. -jar sentinel-dashboard-1.8.3.jar >/dev/null 2>&1 &
    4、或者可以使用 docker 安装。
    1. docker pull bladex/sentinel-dashboard:latest
    2. docker run --name sentinel --restart=always -d -p 8858:8858 bladex/sentinel-dashboard:latest
    5、启动成功后,浏览器访问:http://192.168.58.100:8858,用户名密码为 sentinel/sentinel。
    image.png

    2. 快速上手

    2.1 引入依赖

    1. <dependency>
    2. <groupId>com.alibaba.cloud</groupId>
    3. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    4. </dependency>

    2.2 修改配置文件

    1. server:
    2. port: 9000
    3. spring:
    4. application:
    5. name: sentinel-service
    6. cloud:
    7. nacos:
    8. discovery:
    9. server-addr: 192.168.58.100:8848
    10. sentinel:
    11. transport:
    12. # 添加sentinel的控制台地址
    13. dashboard: 192.168.58.100:8858
    14. # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
    15. # port: 8719

    2.3 sentinel控制台使用

    1、在 sentinel 控制台中设置流控规则。
    image.png
    2、测试,因为 QPS 是1,表示 1 秒钟可以处理的请求数为 1,当请求数超过 1 后,阻塞。
    image.png

    3. Sentinel控制台

    3.1 实时监控

    监控接口的通过的 QPS 和拒绝的 QPS。
    image.png

    3.2 簇点链路

    用来显示微服务的所监控的API。
    image.png

    3.3 流控规则

    3.3.1 简介

    参考文档: https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

同一个资源可以创建多条限流规则。FlowSlot 会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果。

  • resource:资源名,即限流规则的作用对象
  • count: 限流阈值
  • grade: 限流阈值类型(QPS 或并发线程数)
  • limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
  • strategy: 调用关系限流策略
  • controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)

Sentinel入门 - 图9

3.3.2 限流阈值类型

QPS(Query Per Second):每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。
具体操作:进入簇点链路选择具体的访问的API,然后点击流控按钮 。
Sentinel入门 - 图10 image.png
测试:http://localhost:9001/user/findOrderByUserId/1,查看结果。

并发线程数
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
Sentinel入门 - 图12

3.3.3 BlockException异常统一处理

springwebmvc 接口资源限流入口在 HandlerInterceptor 的实现类 AbstractSentinelInterceptor 的 preHandle 方法中,对异常的处理是BlockExceptionHandler 的实现类。sentinel 1.7.1 引入了sentinel-spring-webmvc-adapter.jar。

1、自定义 BlockExceptionHandler 的实现类统一处理 BlockException。

  1. @Slf4j
  2. @Component
  3. public class MyBlockExceptionHandler implements BlockExceptionHandler {
  4. @Override
  5. public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
  6. log.info("BlockExceptionHandler BlockException================"+e.getRule());
  7. Result r = null;
  8. if (e instanceof FlowException) {
  9. r = Result.error(100,"接口限流了");
  10. } else if (e instanceof DegradeException) {
  11. r = Result.error(101,"服务降级了");
  12. } else if (e instanceof ParamFlowException) {
  13. r = Result.error(102,"热点参数限流了");
  14. } else if (e instanceof SystemBlockException) {
  15. r = Result.error(103,"触发系统保护规则了");
  16. } else if (e instanceof AuthorityException) {
  17. r = Result.error(104,"授权规则不通过");
  18. }
  19. //返回json数据
  20. httpServletResponse.setStatus(500);
  21. httpServletResponse.setCharacterEncoding("utf-8");
  22. httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
  23. new ObjectMapper().writeValue(httpServletResponse.getWriter(), r);
  24. }
  25. }

2、测试,查看页面返回结果。
image.png

3.3.4 流控模式

基于调用关系的流量控制。调用关系包括调用方、被调用方;一个方法可能会调用其它方法,形成一个调用链路的层次关系。

直接
资源调用达到设置的阈值后直接被流控抛出异常,比如上面设置的 QPS 阈值。
Sentinel入门 - 图14

关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为 RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。
Sentinel入门 - 图15

链路
根据调用链路入口限流。 下面中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 getUser 的虚拟节点,调用链的入口都是这个虚节点的子节点。一棵典型的调用树如下图所示:

  1. getUser
  2. / \
  3. / \
  4. /order/test1 /order/test2

上图中来自入口 /order/test1 和 /order/test2的请求都调用到了资源 getUser,Sentinel 允许只根据某个入口的统计信息对资源限流。
Sentinel入门 - 图16
测试会发现链路规则不生效。注意,高版本此功能直接使用不生效,如何解决?
从 1.6.3 版本开始,Sentinel Web filter 默认收敛所有 URL 的入口 context,因此链路限流不生效。1.7.0 版本开始(对应SCA的2.1.1.RELEASE),官方在 CommonFilter 引入了 WEB_CONTEXT_UNIFY 参数,用于控制是否收敛 context。将其配置为 false 即可根据不同的 URL 进行链路限流。SCA 2.1.1.RELEASE之后的版本,可以通过配置 spring.cloud.sentinel.web-context-unify=false 即可关闭收敛

  1. spring.cloud.sentinel.web-context-unify: false

测试,此场景拦截不到BlockException,对应 @SentinelResource 指定的资源必须在 @SentinelResource 注解中指定 blockHandler 处理BlockException
总结: 为了解决链路规则引入 ComonFilter 的方式,除了此处问题,还会导致更多的问题,不建议使用 ComonFilter 的方式。 流控链路模式的问题等待官方后续修复,或者使用 AHAS。

3.3.5 流控效果

快速失败:(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当 QPS 超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。

Warm Up:(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。

排队等待:(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。该方式的作用如下图所示:

注意:匀速排队模式暂时不支持 QPS > 1000 的场景。

Sentinel入门 - 图17
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

3.4 降级规则

3.4.1 简介

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
Sentinel入门 - 图18

3.4.2 熔断策略

慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
Sentinel入门 - 图19
测试用例:

  1. @RequestMapping("/test")
  2. public String test() {
  3. try {
  4. Thread.sleep(100);
  5. } catch (InterruptedException e) {
  6. e.printStackTrace();
  7. }
  8. return "========test()========";
  9. }

异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
Sentinel入门 - 图20
测试用例:

  1. 测试用例
  2. @RequestMapping("/test2")
  3. public String test2() {
  4. atomicInteger.getAndIncrement();
  5. if (atomicInteger.get() % 2 == 0){
  6. //模拟异常和异常比率
  7. int i = 1/0;
  8. }
  9. return "========test2()========";
  10. }

异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
Sentinel入门 - 图21

3.4.3 整合Opne Feign进行降级

Sentinel入门 - 图22
1、引入依赖。

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.cloud</groupId>
  7. <artifactId>spring-cloud-starter-openfeign</artifactId>
  8. </dependency>

2、配置 yaml 文件。

  1. feign:
  2. sentinel:
  3. enabled: true # 添加feign对sentinel的支持

3、配置 openfegin 接口

  1. @FeignClient(value = "nacos-payment-provider",fallback =ConsumerFallBackService.class )
  2. public interface ConsumerService {
  3. @GetMapping(value = "/paymentSQL/{id}")
  4. public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
  5. }

4、配置 openfegin 的 fallback 实现类

  1. @Component
  2. public class ConsumerFallBackService implements ConsumerService {
  3. @Override
  4. public CommonResult<Payment> paymentSQL(Long id) {
  5. return new CommonResult<Payment>("500","进入兜底方法---ConsumerFallBackService",null);
  6. }
  7. }

3.5 热点参数限流

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其访问进行限制。
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel入门 - 图23
注意:热点规则需要使用@SentinelResource(“resourceName”)注解,否则不生效;且参数必须是7种基本数据类型才会生效。

测试用例

  1. @RequestMapping("/info/{id}")
  2. @SentinelResource(value = "userinfo",
  3. blockHandlerClass = CommonBlockHandler.class,
  4. blockHandler = "handleException2",
  5. fallbackClass = CommonFallback.class,
  6. fallback = "fallback"
  7. )
  8. public R info(@PathVariable("id") Integer id){
  9. UserEntity user = userService.getById(id);
  10. return R.ok().put("user", user);
  11. }

单机阈值: 针对所有参数的值进行设置的一个公共的阈值
1. 假设当前 参数 大部分的值都是热点流量, 单机阈值就是针对热点流量进行设置, 额外针对普通流量进行参数值流控
2. 假设当前 参数 大部分的值都是普通流量, 单机阈值就是针对普通流量进行设置, 额外针对热点流量进行参数值流控

配置热点参数规则
注意: 资源名必须是@SentinelResource(value=”资源名”)中 配置的资源名,热点规则依赖于注解
Sentinel入门 - 图24

具体到参数值限流,配置参数值为3,限流阈值为1
Sentinel入门 - 图25

测试:
http://localhost:8800/user/info/1 限流的阈值为3
http://localhost:8800/user/info/3 限流的阈值为1
Sentinel入门 - 图26

3.6 系统规则

Sentinel入门 - 图27
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps minRt 估算得出。设定参考值一般是 CPU cores 2.5。参考:https://www.cnblogs.com/gentlemanhai/p/8484839.html
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

1、编写系统规则
Sentinel入门 - 图28

2、jemeter配置
Sentinel入门 - 图29

3、测试结果
Sentinel入门 - 图30

3.7 授权控制规则

很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

来源访问控制规则(AuthorityRule)非常简单,主要有以下配置项:

  • resource:资源名,即限流规则的作用对象。
  • limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB。
  • strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式。

1、配置授权规则
Sentinel入门 - 图31
第一步:实现 com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser 接口,在 parseOrigin 方法中区分来源,并交给 spring 管理。注意:如果引入 CommonFilter,此处会多出一个
Sentinel入门 - 图32

  1. import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
  2. import org.springframework.stereotype.Component;
  3. import javax.servlet.http.HttpServletRequest;
  4. /**
  5. * @author Fox
  6. */
  7. @Component
  8. public class MyRequestOriginParser implements RequestOriginParser {
  9. /**
  10. * 通过request获取来源标识,交给授权规则进行匹配
  11. * @param request
  12. * @return
  13. */
  14. @Override
  15. public String parseOrigin(HttpServletRequest request) {
  16. // 标识字段名称可以自定义
  17. String origin = request.getParameter("serviceName");
  18. // if (StringUtil.isBlank(origin)){
  19. // throw new IllegalArgumentException("serviceName参数未指定");
  20. // }
  21. return origin;
  22. }
  23. }

2、测试:origin 是 order 的请求不通过。
Sentinel入门 - 图33 Sentinel入门 - 图34

3.8 集群规则

为什么要使用集群流控呢?假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50,但机器数可能很多(比如有 100 台)。这时候我们很自然地就想到,找一个 server 来专门来统计总的调用量,其它的实例都与这台 server 通信来判断是否可以调用。这就是最基础的集群流控的方式。

另外集群流控还可以解决流量不均匀导致总体限流效果不佳的问题。假设集群中有 10 台机器,我们给每台机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。参考:https://github.com/alibaba/Sentinel/wiki/%E9%9B%86%E7%BE%A4%E6%B5%81%E6%8E%A7

集群流控中共有两种身份:

  • Token Client:集群流控客户端,用于向所属 Token Server 通信请求 token。集群限流服务端会返回给客户端结果,决定是否限流。
  • Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放 token(是否允许通过)。

Sentinel 集群流控支持限流规则和热点规则两种规则,并支持两种形式的阈值计算方式:

  • 集群总体模式:即限制整个集群内的某个资源的总体 qps 不超过此阈值。
  • 单机均摊模式:单机均摊模式下配置的阈值等同于单机能够承受的限额,token server 会根据连接数来计算总的阈值(比如独立模式下有 3 个 client 连接到了 token server,然后配的单机均摊阈值为 10,则计算出的集群总量就为 30),按照计算出的总的阈值来进行限制。这种方式根据当前的连接数实时计算总的阈值,对于机器经常进行变更的环境非常适合。

Sentinel 集群限流服务端有两种启动方式:

  • 独立模式(Alone),即作为独立的 token server 进程启动,独立部署,隔离性好,但是需要额外的部署操作。独立模式适合作为 Global Rate Limiter 给集群提供流控服务。

Sentinel入门 - 图35

  • 嵌入模式(Embedded),即作为内置的 token server 与服务在同一进程中启动。在此模式下,集群中各个实例都是对等的,token server 和 client 可以随时进行转变,因此无需单独部署,灵活性比较好。但是隔离性不佳,需要限制 token server 的总 QPS,防止影响应用本身。嵌入模式适合某个应用集群内部的流控。

Sentinel入门 - 图36
云上版本 AHAS Sentinel 提供开箱即用的全自动托管集群流控能力,无需手动指定/分配 token server 以及管理连接状态,同时支持分钟小时级别流控、大流量低延时场景流控场景,同时支持 Istio/Envoy 场景的 Mesh 流控能力。

4. Sentinel持久化模式

4.1 原始模式

如果不做任何修改,Dashboard 的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中:
Sentinel入门 - 图37
这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境。

4.2 拉模式

pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的 WritableDataSourceRegistry 中。

4.3 推模式

生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(ZooKeeper, Nacos, Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。

案例:基于Nacos配置中心控制台实现推送。

1、引入依赖。

  1. <dependency>
  2. <groupId>com.alibaba.csp</groupId>
  3. <artifactId>sentinel-datasource-nacos</artifactId>
  4. </dependency>

2、nacos 配置中心配置流控规则。

  1. [
  2. {
  3. "resource": "TestResource",
  4. "controlBehavior": 0,
  5. "count": 10.0,
  6. "grade": 1,
  7. "limitApp": "default",
  8. "strategy": 0
  9. }
  10. ]

image.png
3、在 yaml 中配置流控规则。

  1. server:
  2. port: 9001
  3. spring:
  4. application:
  5. name: admin-service
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: 192.168.58.100:8848
  10. sentinel:
  11. transport:
  12. dashboard: 192.168.58.100:8858 # 添加sentinel的控制台地址
  13. # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
  14. # port: 8719
  15. datasource:
  16. ds1:
  17. nacos:
  18. server-addr: 192.168.58.100:8848
  19. dataId: ${spring.application.name}
  20. groupId: DEFAULT_GROUP
  21. data-type: json
  22. rule-type: flow

4、nacos配置中心中添加:

  1. [
  2. {
  3. "resource": "userinfo",
  4. "limitApp": "default",
  5. "grade": 1,
  6. "count": 1,
  7. "strategy": 0,
  8. "controlBehavior": 0,
  9. "clusterMode": false
  10. }
  11. ]

image.png
5、增加 yaml 配置。

  1. server:
  2. port: 9001
  3. spring:
  4. application:
  5. name: admin-service
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: 192.168.58.100:8848
  10. sentinel:
  11. transport:
  12. dashboard: 192.168.58.100:8858 # 添加sentinel的控制台地址
  13. # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
  14. # port: 8719
  15. datasource:
  16. # ds1:
  17. # nacos:
  18. # server-addr: 192.168.58.100:8848
  19. # dataId: ${spring.application.name}
  20. # groupId: DEFAULT_GROUP
  21. # data-type: json
  22. # rule-type: flow
  23. flow-rules:
  24. nacos:
  25. server-addr: 192.168.58.100:8848
  26. dataId: ${spring.application.name}-flow-rules
  27. groupId: SENTINEL_GROUP # 注意groupId对应Sentinel Dashboard中的定义
  28. data-type: json
  29. rule-type: flow
  30. degrade-rules:
  31. nacos:
  32. server-addr: 192.168.58.100:8848
  33. dataId: ${spring.application.name}-degrade-rules
  34. groupId: SENTINEL_GROUP
  35. data-type: json
  36. rule-type: degrade
  37. param-flow-rules:
  38. nacos:
  39. server-addr: 192.168.58.100:8848
  40. dataId: ${spring.application.name}-param-flow-rules
  41. groupId: SENTINEL_GROUP
  42. data-type: json
  43. rule-type: param-flow
  44. authority-rules:
  45. nacos:
  46. server-addr: 192.168.58.100:8848
  47. dataId: ${spring.application.name}-authority-rules
  48. groupId: SENTINEL_GROUP
  49. data-type: json
  50. rule-type: authority
  51. system-rules:
  52. nacos:
  53. server-addr: 192.168.58.100:8848
  54. dataId: ${spring.application.name}-system-rules
  55. groupId: SENTINEL_GROUP
  56. data-type: json
  57. rule-type: system

6、以流控规则测试,当在 sentinel dashboard 配置了流控规则,会在 nacos 配置中心生成对应的配置。

Sentinel入门 - 图40