个人 二维码




公众号二维码

目录

Spring Cloud 系列之 Alibaba Sentinel 服务哨兵


https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/43697219-3cb4ef3a-9975-11e8-9a9c-73f4f537442d.png

  前文中我们提到 Netflix 中多项开源产品已进入维护阶段,不再开发新的版本,就目前来看是没有什么问题的。但是从长远角度出发,我们还是需要考虑是否有可替代产品使用。比如本文中要介绍的 Alibaba Sentinel 就是一款高性能且轻量级的流量控制、熔断降级可替换方案。是一款面向云原生微服务的高可用流控防护组件。

  Sentinel 官网:https://sentinelguard.io/zh-cn/

  Github:https://github.com/alibaba/Sentinel

  

Hystrix 目前状态

  

  官网提示:https://github.com/Netflix/Hystrix

Hystrix is no longer in active development, and is currently in maintenance mode.

Hystrix 不再主动开发,当前处于维护模式。

  

Sentinel 是什么

  

  随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来保障微服务的稳定性。

  

Sentinel 具有以下特征:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
  • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

  

Sentinel 主要特征

  

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/sentinel-features-overview-en.png

  

Sentinel 开源生态

  

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/sentinel-opensource-eco-landscape-en.png

  Sentinel 目前已经针对 Servlet、Dubbo、Spring Boot/Spring Cloud、gRPC 等进行了适配,用户只需引入相应依赖并进行简单配置即可非常方便地享受 Sentinel 的高可用流量防护能力。Sentinel 还为 Service Mesh 提供了集群流量防护的能力。未来 Sentinel 还会对更多常用框架进行适配。

  

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

  

Sentinel 的历史

  

  • 2012 年,Sentinel 诞生,主要功能为入口流量控制。
  • 2013-2017 年,Sentinel 在阿里巴巴集团内部迅速发展,成为基础技术模块,覆盖了所有的核心场景。Sentinel 也因此积累了大量的流量归整场景以及生产实践。
  • 2018 年,Sentinel 开源,并持续演进。
  • 2019 年,Sentinel 朝着多语言扩展的方向不断探索,推出 C++ 原生版本,同时针对 Service Mesh 场景也推出了 Envoy 集群流量控制支持,以解决 Service Mesh 架构下多语言限流的问题。
  • 2020 年,推出 Sentinel Go 版本,继续朝着云原生方向演进。

  

Sentinel vs Hystrix

  

Hystrix

  

  官网:https://github.com/Netflix/Hystrix

Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

  Hystrix 的关注点在于隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。

  

Sentinel

  

  官网:https://github.com/alibaba/Sentinel

   Sentinel 的关注点在于:

  • 多样化的流量控制
  • 熔断降级
  • 系统负载保护
  • 实时监控和控制台

  

Hystrix 迁移 Sentinel 方案

  

  Sentinel 提供了从 Hystrix 迁移到 Sentinel 的方案,官网:https://github.com/alibaba/Sentinel/wiki/Guideline:-%E4%BB%8E-Hystrix-%E8%BF%81%E7%A7%BB%E5%88%B0-Sentinel

  

总结

  

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

  

Sentinel 核心

  

Sentinel 的使用可以分为两个部分:

  • 核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 7 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持(见 主流框架适配)。
  • 控制台(Dashboard):控制台主要负责管理推送规则、监控、集群限流分配管理、机器发现等。

  

Sentinel 控制台

  

  Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。

  官网文档:https://github.com/alibaba/Sentinel/wiki/控制台

  

获取控制台

  

  您可以从 release 页面 下载最新版本的控制台 jar 包。

  您也可以从最新版本的源码自行构建 Sentinel 控制台:

  • 下载 控制台 工程
  • 使用以下命令将代码打包成一个 fat jar: mvn clean package

  

启动控制台

  

  启动命令如下,本文使用的是目前最新 1.7.1 版本:

1
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar

注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。

  其中 -Dserver.port=8080 用于指定 Sentinel 控制台端口为 8080

  从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。可以参考 鉴权模块文档 配置用户名和密码。

注:若您的应用为 Spring Boot 或 Spring Cloud 应用,您可以通过 Spring 配置文件来指定配置,详情请参考 Spring Cloud Alibaba Sentinel 文档

  

  为了方便启动,可以编写一个启动脚本 run.bat

1
2
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar
pause

  

访问

  

  访问:http://localhost:8080/

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200131171829583.png

  

  输入默认用户名和密码 sentinel 点击登录。至此控制台就安装完成了。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200131172136071.png

  

环境准备

  

  sentinel-demo 聚合工程。SpringBoot 2.2.4.RELEASESpring Cloud Hoxton.SR1

  • eureka-server:注册中心
  • eureka-server02:注册中心
  • product-service:商品服务,提供了 /product/{id} 接口
  • order-service-rest:订单服务,基于 Ribbon 通过 RestTemplate 调用商品服务
  • order-server-feign:订单服务,基于 Feign 通过声明式服务调用商品服务

  

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200131180149055.png

  

客户端接入控制台

  

  控制台启动后,客户端需要按照以下步骤接入到控制台:

  • 添加依赖
  • 定义资源
  • 定义规则

  

  先把可能需要保护的资源定义好,之后再配置规则。也可以理解为,只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。

  

  由于我们的项目是 Spring Cloud 项目,所以可以借助官方文档来进行学习。

  Spring 官网文档:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html

  Github 文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

  

添加依赖

  

  父工程需要添加如下依赖:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.1.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

  子工程需要添加如下依赖:

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

  

配置文件

  

  客户端需要启动 Transport 模块来与 Sentinel 控制台进行通信。

  order-service-rest 的 application.yml

1
2
3
4
5
6
7
spring:
  cloud:
    # 配置 Sentinel
    sentinel:
      transport:
        port: 8719
        dashboard: localhost:8080

  这里的 spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了一个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中。

  

初始化客户端

  

  确保客户端有访问量,Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。

  简单的理解就是:访问一次客户端,Sentinel 即可完成客户端初始化操作,并持续向控制台发送心跳包。

  

访问

  

  多次访问:http://localhost:9090/order/1 然后查看控制台实时监控结果如下:

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200214154631720.png

  

定义资源

  

  资源 是 Sentinel 中的核心概念之一。我们说的资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。最常用的资源是我们代码中的 Java 方法。Sentinel 提供了 @SentinelResource 注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException 等。

  只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

  官网文档:https://github.com/alibaba/Sentinel/wiki/如何使用#定义资源

  

注解支持

  

  官网文档:https://github.com/alibaba/Sentinel/wiki/注解支持

  OrderServiceImpl.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.example.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.example.pojo.Order;
import com.example.service.OrderService;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private ProductService productService;

    /**
     * 根据主键和订单编号查询订单
     *
     * @param id
     * @param orderNo
     * @return
     */
    @Override
    @SentinelResource(value = "selectOrderByIdAndOrderNo",
            blockHandler = "selectOrderByIdAndOrderNoBlockHandler",
            fallback = "selectOrderByIdAndOrderNoFallback")
    public Order selectOrderByIdAndOrderNo(Integer id, String orderNo) {
        return new Order(id, orderNo, "中国", 2666D,
                Arrays.asList(productService.selectProductById(1)));
    }

    // 服务流量控制处理,参数最后多一个 BlockException,其余与原函数一致。
    public Order selectOrderByIdAndOrderNoBlockHandler(Integer id, String orderNo,
                                                       BlockException ex) {
        // Do some log here.
        ex.printStackTrace();
        return new Order(id, "服务流量控制处理-托底数据", "中国", 2666D,
                Arrays.asList(productService.selectProductById(1)));
    }

    // 服务熔断降级处理,函数签名与原函数一致或加一个 Throwable 类型的参数
    public Order selectOrderByIdAndOrderNoFallback(Integer id, String orderNo,
                                                   Throwable throwable) {
        System.out.println("order-service 服务的 selectOrderById 方法出现异常,异常信息如下:"
                + throwable);
        return new Order(id, "服务熔断降级处理-托底数据", "中国", 2666D,
                Arrays.asList(productService.selectProductById(1)));
    }

}

  

  ProductServiceImpl.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.example.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * 商品管理
 */
@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 根据主键查询商品
     *
     * @param id
     * @return
     */
    @SentinelResource(value = "selectProductById",
            blockHandler = "selectProductByIdBlockHandler", fallback = "selectProductByIdFallback")
    @Override
    public Product selectProductById(Integer id) {
        return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
    }

    // 服务流量控制处理,参数最后多一个 BlockException,其余与原函数一致。
    public Product selectProductByIdBlockHandler(Integer id, BlockException ex) {
        // Do some log here.
        ex.printStackTrace();
        return new Product(id, "服务流量控制处理-托底数据", 1, 2666D);
    }

    // 服务熔断降级处理,函数签名与原函数一致或加一个 Throwable 类型的参数
    public Product selectProductByIdFallback(Integer id, Throwable throwable) {
        System.out.println("product-service 服务的 selectProductById 方法出现异常,异常信息如下:"
                + throwable);
        return new Product(id, "服务熔断降级处理-托底数据", 1, 2666D);
    }

}

注意:注解方式埋点不支持 private 方法。

  

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理

  特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。

  从 1.4.0 版本开始,注解方式定义资源支持自动统计业务异常,无需手动调用 Tracer.trace(ex) 来记录业务异常。Sentinel 1.4.0 以前的版本需要自行调用 Tracer.trace(ex) 来记录业务异常。

  

定义规则

  

  Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API,供您来定制自己的规则策略。

  Sentinel 支持以下几种规则:流量控制规则熔断降级规则热点参数规则系统保护规则来源访问控制规则

  官网文档:https://github.com/alibaba/Sentinel/wiki/如何使用#规则的种类

  

流量控制规则

  

添加流量控制规则

  

  选择 簇点链路 找到定义好的资源 selectProductById 并点击对应的规则按钮进行设置。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200509230328562.png

  

  比如我们设置一个流量控制规则,定义资源访问的 QPS 为 1(每秒能处理查询数目)。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200214155300979.png
https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200131213800426.png

  

测试

  

  快速刷新页面多次访问:http://localhost:9090/order/idAndOrderNo?id=1&orderNo=order-001 结果如下:

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200214155837546.png

  

熔断降级规则

  

模拟服务出错

  

  修改 order-service-rest 项目中的核心代码,模拟服务出错。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.example.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * 商品管理
 */
@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 根据主键查询商品
     *
     * @param id
     * @return
     */
    @SentinelResource(value = "selectProductById",
            blockHandler = "selectProductByIdBlockHandler", fallback = "selectProductByIdFallback")
    @Override
    public Product selectProductById(Integer id, String productName) {
        // 模拟查询主键为 1 的商品信息会导致异常
        if (1 == id)
            throw new RuntimeException("查询主键为 1 的商品信息导致异常");
        return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
    }

    // 服务流量控制处理,参数最后多一个 BlockException,其余与原函数一致。
    public Product selectProductByIdBlockHandler(Integer id, BlockException ex) {
        // Do some log here.
        ex.printStackTrace();
        return new Product(id, "服务流量控制处理-托底数据", 1, 2666D);
    }

    // 服务熔断降级处理,函数签名与原函数一致或加一个 Throwable 类型的参数
    public Product selectProductByIdFallback(Integer id, Throwable throwable) {
        System.out.println("product-service 服务的 selectProductById 方法出现异常,异常信息如下:"
                + throwable);
        return new Product(id, "服务熔断降级处理-托底数据", 1, 2666D);
    }

}

  

添加熔断降级规则

  

  熔断降级规则支持相应时间、异常比例、异常数三种方式。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200131214554221.png

  

测试

  

  访问:http://localhost:9090/order/idAndOrderNo?id=1&orderNo=order-001 结果如下:

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200214160237861.png

  

热点参数规则

  

  热点参数规则是一种更细粒度的流控规则,它允许将规则具体到参数上。比如 selectOrderByIdAndOrderNo 方法有两个参数,我们对第一个参数进行限流,对第二个参数不限流。

  

添加热点参数规则

  

  选择 簇点链路 找到定义好的资源 selectOrderByIdAndOrderNo 并点击对应的规则按钮进行设置。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200509230704067.png

  

  设置热点参数规则,定义对资源的第一个参数的 QPS 为 1(每秒能处理查询数目)。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200509230846281.png
https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200509231407248.png

  

测试

  

  分别用两个参数访问,会发现只对第一个参数限流了。

  快速刷新页面多次访问:http://localhost:9090/order/idAndOrderNo?id=1 被限流。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200510000514404.png

  快速刷新页面多次访问:http://localhost:9090/order/idAndOrderNo?orderNo=order-001 正常访问。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200510000558792.png

  

授权规则

  

  很多时候,我们需要根据调用来源来判断该次请求是否被允许,这时候可以使用 Sentinel 的来源访问控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过。

  Sentinel 提供了 RequestOriginParser 接口来处理来源。一旦 Sentinel 保护的接口资源被访问,Sentinel 就会调用 RequestOriginParser 的实现类去解析访问来源。

  

自定义来源处理规则

  

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.example.sentinel;

import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * 自定义来源处理规则
 */
@Component
public class MyRequestOriginParser implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest request) {
        return request.getParameter("userName");
    }

}

  

新增授权规则

  

  下图配置的意思是资源 selectOrderByIdAndOrderNo 只有 userName=zhangsan 的用户无法访问(黑名单)

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200509234054715.png

  

测试

  

  快速刷新页面多次访问:http://localhost:9090/order/idAndOrderNo?id=1&userName=zhangsan 被限流。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200510000514404.png

  快速刷新页面多次访问:http://localhost:9090/order/idAndOrderNo?id=1&userName=lisi 正常访问。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200510000558792.png

  

系统保护规则

  

  系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 LOAD、RT、线程数、入口 QPS 和 CPU 使用率五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

  系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量(进入应用的流量)生效。

  • Load(仅对 Linux/Unix-like 机器生效):当系统 load 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
  • CPU使用率:当单台机器上所有入口流量的 CPU 使用率达到阈值即触发系统保护。

  

动态规则扩展

  

  官网文档:

  

  SentinelProperties 内部提供了 TreeMap 类型的 datasource 属性用于配置数据源信息。支持:

  • 文件配置规则
  • Nacos 配置规则
  • ZooKeeper 配置规则
  • Apollo 配置规则
  • Redis 配置规则

  

文件配置规则

  

  Sentinel 支持通过本地文件加载规则配置,使用方式如下(限流规则作为演示):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
spring:
  cloud:
    # 配置 Sentinel
    sentinel:
      datasource:
        ds1:
          file:
            file: classpath:flowRule.json
            data-type: json
            rule-type: flow

  

  flowRule.json 对应 com.alibaba.csp.sentinel.slots.block.RuleConstant 各属性。

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

  

  重要属性:

Field 说明 默认值
resource 资源名,资源名是限流规则的作用对象
count 限流阈值
grade 限流阈值类型,QPS 模式(1)或并发线程数模式(0) QPS 模式
limitApp 流控针对的调用来源 default,代表不区分调用来源
strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接)
controlBehavior 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流 直接拒绝
clusterMode 是否集群限流

  

  访问客户端以后,刷新控制台,查看流控规则如下:

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200131221321055.png

  

RestTemplate 支持

  

  Spring Cloud Alibaba Sentinel 支持对 RestTemplate 调用的服务进行服务保护。需要在构造 RestTemplate Bean 时添加 @SentinelRestTemplate 注解。

  

启动类

  

  OrderServiceRestApplication.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example;

import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate;
import com.example.exception.ExceptionUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class OrderServiceRestApplication {

    @Bean
    @LoadBalanced
    @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class,
            fallback = "fallback", fallbackClass = ExceptionUtil.class)
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceRestApplication.class, args);
    }

}

  

服务熔断处理类

  

  ExceptionUtil.java 必须使用静态方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.example.exception;

import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.example.pojo.Product;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;

public class ExceptionUtil {

    // 服务流量控制处理
    public static ClientHttpResponse handleException(HttpRequest request,
                                                     byte[] body,
                                                     ClientHttpRequestExecution execution,
                                                     BlockException exception) {
        exception.printStackTrace();
        return new SentinelClientHttpResponse(
                JSON.toJSONString(new Product(1, "服务流量控制处理-托底数据", 1, 2666D)));
    }

    // 服务熔断降级处理
    public static ClientHttpResponse fallback(HttpRequest request,
                                                    byte[] body,
                                                    ClientHttpRequestExecution execution,
                                                    BlockException exception) {
        exception.printStackTrace();
        return new SentinelClientHttpResponse(
                JSON.toJSONString(new Product(1, "服务熔断降级处理-托底数据", 1, 2666D)));
    }

}

  

访问

  

  控制台设置流量控制规则,定义资源访问的 QPS 为 1(每秒能处理查询数目)。

  快速刷新页面多次访问:http://localhost:9090/order/1 结果如下:

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200214155837546.png

  

OpenFeign 支持

  

  其实不管是 Hystrix 还是 Sentinel 对于 Feign 的支持,核心代码基本上是一致的,只需要修改依赖和配置文件即可。

  

添加依赖

  

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

  

开启 Sentinel

  

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server:
  port: 9091 # 端口

spring:
  application:
    name: order-service-feign # 应用名称
  cloud:
    sentinel:
      transport:
        port: 8719
        dashboard: localhost:8080

# 配置 Eureka Server 注册中心
eureka:
  instance:
    prefer-ip-address: true       # 是否使用 ip 地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  # 设置服务注册中心地址
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

# feign 开启 sentinel 支持
feign:
  sentinel:
    enabled: true

  

熔断降级

  

  ProductServiceFallback.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.example.fallback;

import com.example.pojo.Product;
import com.example.service.ProductService;
import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 服务熔断降级处理可以捕获异常
 */
@Component
public class ProductServiceFallbackFactory implements FallbackFactory<ProductService> {

    // 获取日志,在需要捕获异常的方法中进行处理
    Logger logger = LoggerFactory.getLogger(ProductServiceFallbackFactory.class);

    @Override
    public ProductService create(Throwable throwable) {
        return new ProductService() {
            @Override
            public Product selectProductById(Integer id) {
                logger.error("product-service 服务的 selectProductById 方法出现异常,异常信息如下:"
                        + throwable);
                return new Product(id, "托底数据", 1, 2666D);
            }
        };
    }

}

  

消费服务

  

  ProductService.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.service;

import com.example.fallback.ProductServiceFallbackFactory;
import com.example.pojo.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

// 声明需要调用的服务
@FeignClient(value = "product-service", fallbackFactory = ProductServiceFallbackFactory.class)
public interface ProductService {

    /**
     * 根据主键查询商品
     *
     * @param id
     * @return
     */
    @GetMapping("/product/{id}")
    Product selectProductById(@PathVariable("id") Integer id);

}

  

  OrderServiceImpl.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.service.impl;

import com.example.pojo.Order;
import com.example.service.OrderService;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private ProductService productService;

    /**
     * 根据主键查询订单
     *
     * @param id
     * @return
     */
    @Override
    public Order selectOrderById(Integer id) {
        return new Order(id, "order-001", "中国", 2666D,
                Arrays.asList(productService.selectProductById(1)));
    }

}

  

控制层

  

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.controller;

import com.example.pojo.Order;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 根据主键查询订单
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Order selectOrderById(@PathVariable("id") Integer id) {
        return orderService.selectOrderById(id);
    }

}

  

启动类

  

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

// 开启 FeignClients 注解
@EnableFeignClients
@SpringBootApplication
public class OrderServiceFeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceFeignApplication.class, args);
    }

}

  

测试

  

  控制台信息如下:

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200131233524521.png

  

  添加流量控制规则,定义资源访问的 QPS 为 1(每秒能处理查询数目)。

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200131233925164.png

  

  快速刷新页面多次访问:http://localhost:9091/order/1 结果如下:

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200214161610742.png

  

  或者关闭服务提供者,访问:http://localhost:9091/order/1 结果如下:

https://mrhelloworld.com/resources/articles/spring/spring-cloud/sentinel/image-20200214161610742.png

  至此 Sentinel 服务哨兵知识点就讲解结束了。

https://mrhelloworld.com/resources/articles/articles_bottom/end02.gif

本文采用 知识共享「署名-非商业性使用-禁止演绎 4.0 国际」许可协议

大家可以通过 分类 查看更多关于 Spring Cloud 的文章。

  

🤗 您的点赞转发是对我最大的鼓励和支持。

📢 扫码关注 哈喽沃德先生「文档 + 视频」每篇文章都配有专门视频讲解,学习更轻松噢 ~

https://mrhelloworld.com/resources/mrhelloworld/qrcode/OfficialAccounts500-500.gif
「 感谢支持 」