Hi,我是空夜!

本节示例代码在 https://github.com/laolunsi/spring-boot-examples


首先下载 sentinel jar包:https://github.com/alibaba/Sentinel/releases

java -jar sentinel-xx.jar 运行,打开浏览器,输入默认地址:http://localhost:8080

参考:如何使用 Sentinel?—— 官方文档

sentinel 进行流量控制有以下流程:

  • 定义资源
  • 定义规则
  • 检验规则是否生效

概念

资源,是 sentinel 的核心概念之一,可以简单的理解为一段代码。

sentinel 对主流的框架都提供了适配,下面以 Spring Cloud 为例,记录在 Spring Cloud 微服务架构中如何使用 sentinel 进行流量控制。

资源

分为以下几种方式:

  • 主流框架的默认配置
  • 抛出异常方式定义资源
  • 返回布尔值方式定义资源
  • 注解方式定义资源
  • 异步调用支持

注解方式定义资源主要用于接口上,对接口进行流量控制,使用的是 @ResourceSentinel 注解。该注解提供了可选的异常处理和 fallback 配置项。参考:Sentinel 注解支持

源码:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
    // 资源名称,不能为空
    String value() default "";

    EntryType entryType() default EntryType.OUT;

    int resourceType() default 0;

    // 可选项,处理 BlockException 的函数名称,默认在本类中;如果想要指定其他类中的函数,需要使用 blockHandlerClass 属性
    String blockHandler() default "";

    Class<?>[] blockHandlerClass() default {};

    // 可选项,fallback 函数名称,用于在抛出异常时提供 fallback 处理逻辑。可以针对所有类型的异常。配合 fallbackClass 属性可以指定其他类中的函数
    String fallback() default "";

    String defaultFallback() default "";

    Class<?>[] fallbackClass() default {};

    Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};

    // 忽略异常,即 fallback 和 blockHandler 函数不起作用的异常
    Class<? extends Throwable>[] exceptionsToIgnore() default {};
}

注意:

  • blockHandler 和 fallback 指定的函数默认在本类中,该函数的返回值类型和参数顺序需与原方法保持一致。blockHandler 对应的函数可以在最后额外加一个 BlockException 类型的参数,fallback 对应的函数可以再最后额外加一个 Throwable 类型的参数
  • 使用对应的 blockHandlerClass 和 fallbackClass 可以指定对应函数所在的类,而不是默认的当前类。注意,此时,指定的函数需要是 static 的,否则无法解析。

规则

原理是监控应用流量的 QPS 或并发线程数等指标,设置阈值,避免服务被瞬时的高峰流量冲垮。目的是保障高峰流量时应用的高可用性。

目的是防止单个服务不可用导致的雪崩现象的发生,同样是保障应用高可用性的方法之一。

针对的是调用链路中不稳定的资源。

系统保护规则从应用级别(而不是资源维度)的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

根据调用来源判断请求是否允许通过。

看作一种特殊的流量控制规则,仅对包含热点参数的资源调用生效。它根据传入参数中的热点参数,与配置的限流阈值、模式,对包含热点参数的资源调用进行限流。

sentinel 还提供了相关 API 用于定制自己的规则策略。

上面默认的规则分类,可以通过代码定义,也可以使用 dashboard 定义。

定义规则可以通过 dashboard,也可以使用代码创建。如果想要持久化存储规则,可以利用 Nacos 等数据源。这一点在后面会讲。


Spring Cloud 整合 Sentinel

参考:spring-cloud-alibaba-sentinel

创建一个 demo 服务,引入 spring-cloud 中的 sentinel 依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>0.9.0.RELEASE</version>
        </dependency>

配置:

server:
  port: 8203
spring:
  application:
    name: demo
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8081

可以配置流控、降级、热点、授权多种规则:

注解支持使用参考:注解支持 —— 官方文档

默认情况下是以 url 作为链路地址以及资源名的,我们可以在代码中用 @SentinelResource 注解来主动注明资源的名称、阻塞处理方法等。比如:

@RestController
@RequestMapping(value = "test")
@Validated
public class TestAction {

    private static Logger logger = LoggerFactory.getLogger(TestAction.class);

    @SentinelResource(value = "helloTest", blockHandler = "handleEx")
    @GetMapping(value = "hello")
    public String hello(@RequestParam("msg") @NotBlank String msg) {
        logger.info("hellow, " + msg);
        return "hello, " + msg;
    }

    @GetMapping(value = "send")
    @SentinelResource(value = "sendTest", blockHandler = "handleException", blockHandlerClass = BlockHandlerConfig.class)
    public String send(@NotBlank(message = "{required}") String email, @Email(message = "{invalid}") String msg) {
        return "发消息给:" + email + ",消息内容:" + msg;
    }

    public String handleEx(String msg, BlockException ex) {
        System.out.println("系统错误:msg=" + msg + ", ex: " + ex);
        return "流量限制,请稍后重试";
    }
}

这里的 helloTest 使用了本类中的 handleEx 方法作为阻塞处理方法。而 sendTest 用 BlockHandlerConfig 类中的 handleException 方法:

public class BlockHandlerConfig {

    public static String handleException(String email, String msg, BlockException exception) {
        return "444, " + exception.getClass().getCanonicalName() + "\t 服务不可用";
    }
    
}

需要注意的是,blockHandle 对应方法的参数必须与 资源 的参数保持一致,否则规则不会生效,且会抛出异常。

这里给 helloTest 这个 resource 配置一个简单的流控:

测试:

快速请求 helloTest 对应的接口,发现成功请求与限流响应交错出现了。这表明我们的限流规则和 blockHandler 生效了。


规则持久化

需要注意的是,如果服务重启了,那么这些规则配置就会被丢失。其中一个解决办法是利用 Nacos 做配置中心,先将规则定义保存在 Nacos 中。

sentinel 提供了 file/nacos/zp/apollo 等规则扩展存储方式。具体参考官方文档:动态规则扩展

一个服务若要使用 Nacos 中的配置作为 sentinel 规则,除了 Nacos 的依赖外,还需要引入如下依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>0.9.0.RELEASE</version>
        </dependency>

                <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.8.0</version>
        </dependency>

        <!-- 为解决 Caused by: java.lang.ClassNotFoundException: com.alibaba.csp.sentinel.log.CommandCenterLog 引入 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
            <version>1.8.0</version>
        </dependency>

配置:

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: sentinel-config
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

nacos 中新建一个 sentinel-config 配置文件,类型是 json,内容比如:

[
    {
         "resource": "helloTest",
         "limitApp": "default",
         "grade":   1,
         "count":   3,
         "strategy": 0,
         "controlBehavior": 0,
         "clusterMode": false    
    }
]

启动后可以看到 sentinel 中出现了这个规则。在 nacos 中修改该规则,发现 sentinel 那边同步修改了(反之则不行)


FAQ —— sentinel 部署路径的问题

之前部署 sentinel 的时候,出现过这样一个问题:我配置了一个域名给微服务平台使用,其中的每一个模板,如 gateway, zipkin,sentinel,都是用 nginx 配置的 xxx.com/gateway/xxx 这样的请求格式来访问的。

打开 xxx.com/sentinel, 是可以看到 dashboard 和每个服务的,但是点开服务,发现监控数据没有了,簇点链路中每个地址的 QPS 都是0,点击新增流控规则,控制台报了 404 异常。

在官方仓库提了这个 issue,有位j叫 jasonjoo2010 的老哥给我解答了一下:

目前dashboard的静态部分,并不支持任意子目录部署的方式,建议在不改动的情况下,使用独立域名部署。

后来我新增了一个二级域名来专门访问 sentinel,就可以了。

关于这个问题的详细描述在 sentinel 官方仓库中:https://github.com/alibaba/Sentinel/issues/1804


参考:


最近在系统地学习 Redis、RabbitMQ、ES 等技术的知识,着重关注原理、底层、并发等问题,关于相关技术分享后续会逐渐发布出来。欢迎关注公众号:猿生物语(ID:JavaApes

Last modification:October 21st, 2020 at 12:17 pm
请作者喝杯肥宅快乐水吧!