sentinel详解

241 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

构成

  • 核心库
  • 控制台

核心库

在sentinel中资源是很重要的元素,通常是函数(需要被sentinel保护的方法)。

通过对资源进行配置策略实现保护功能,如:

public static void main(String[] args) {
    // 配置规则.
    initFlowRules();

    while (true) {
        // 1.5.0 版本开始可以直接利用 try-with-resources 特性
        try (Entry entry = SphU.entry("HelloWorld")) {
            // 被保护的逻辑
            System.out.println("hello world");
	} catch (BlockException ex) {
            // 处理被流控的逻辑
	    System.out.println("blocked!");
	}
    }
}

//或者使用注解
@SentinelResource("HelloWorld")
public void helloWorld() {
    // 资源中的逻辑
    System.out.println("hello world");
}

这样,helloWorld() 方法就成了我们的一个资源。注意注解支持模块需要配合 Spring AOP 或者 AspectJ 一起使用。

再对上述的资源配置规则:

private static void initFlowRules(){
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule();
    rule.setResource("HelloWorld");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    // Set limit QPS to 20.
    rule.setCount(20);
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

运行结果:

|--timestamp-|------date time----|--resource-|p |block|s |e|rt
1529998904000|2018-06-26 15:41:44|hello world|20|0    |20|0|0
1529998905000|2018-06-26 15:41:45|hello world|20|5579 |20|0|728
1529998906000|2018-06-26 15:41:46|hello world|20|15698|20|0|0
1529998907000|2018-06-26 15:41:47|hello world|20|19262|20|0|0
1529998908000|2018-06-26 15:41:48|hello world|20|19502|20|0|0
1529998909000|2018-06-26 15:41:49|hello world|20|18386|20|0|0

其中 p 代表通过的请求, block 代表被阻止的请求, s 代表成功执行完成的请求个数, e 代表用户自定义的异常, rt 代表平均响应时长

控制台

  1. 下载jar并在本地运行
  2. 客户端添加依赖
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.8.6</version>
</dependency>

客户端需要引入 Transport 模块来与 Sentinel 控制台进行通信
启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口

定义资源

主流框架的默认适配

为了减少开发的复杂程度,我们对大部分的主流框架,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 等都做了适配。您只需要引入对应的依赖即可方便地整合 Sentinel。

抛出异常的方式定义资源

SphU 包含了 try-catch 风格的 API。用这种方式,当资源发生了限流之后会抛出 BlockException。这个时候可以捕捉异常,进行限流之后的逻辑处理。示例代码如下:

// 1.5.0 版本开始可以利用 try-with-resources 特性
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {
  // 被保护的业务逻辑
  // do something here...
} catch (BlockException ex) {
  // 资源访问阻止,被限流或被降级
  // 在此处进行相应的处理操作
}

若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(exit(count, args)),否则可能会有统计错误。

这个时候不能使用 try-with-resources 的方式。

另外通过 Tracer.trace(ex) 来统计异常信息时,由于 try-with-resources 语法中 catch 调用顺序的问题,会导致无法正确统计异常数,因此统计异常信息时也不能在 try-with-resources 的 catch 块中调用 Tracer.trace(ex)。

返回布尔值方式定义资源

SphO 提供 if-else 风格的 API。用这种方式,当资源发生了限流之后会返回 false,这个时候可以根据返回值,进行限流之后的逻辑处理。示例代码如下:

// 资源名可使用任意有业务语义的字符串
  if (SphO.entry("自定义资源名")) {
    // 务必保证finally会被执行
    try {
      /**
      * 被保护的业务逻辑
      */
    } finally {
      SphO.exit();
    }
  } else {
    // 资源访问阻止,被限流或被降级
    // 进行相应的处理操作
  }

注解方式定义资源

Sentinel 支持通过 @SentinelResource 注解定义资源并配置 blockHandler 和 fallback 函数来进行限流之后的处理。示例:

// 原本的业务方法.
@SentinelResource(blockHandler = "blockHandlerForGetUser")
public User getUserById(String id) {
    throw new RuntimeException("getUserById command failed");
}

// blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
public User blockHandlerForGetUser(String id, BlockException ex) {
    return new User("admin");
}
  • blockHandler函数会在原方法被限流/降级/系统保护的时候调用
  • fallback函数会针对所有类型的异常

异步调用支持

支持异步调用链路的统计。

try {
    AsyncEntry entry = SphU.asyncEntry(resourceName);
    // 异步调用.
    doAsync(userId, result -> {
        try {
            // 在此处处理异步调用的结果.
        } finally {
            // 在回调结束后 exit.
            entry.exit();
        }
    });
} catch (BlockException ex) {
    // Request blocked.
    // Handle the exception (e.g. retry or fallback).
}

SphU.asyncEntry(xxx) 不会影响当前(调用线程)的 Context,所以以下两个在链路上是平级的:

asyncEntry = SphU.asyncEntry(asyncResource);

entry = SphU.entry(normalResource);

如果要在异步中嵌套其他的资源调用,形成上下调用链路,可以使用ContextUtil.runOnContext(context, f)进行变换,如:

public void handleResult(String result) {
    Entry entry = null;
    try {
        entry = SphU.entry("handleResultForAsync");
        // Handle your result here.
    } catch (BlockException ex) {
        // Blocked for the result handler.
    } finally {
        if (entry != null) {
            entry.exit();
        }
    }
}

public void someAsync() {
    try {
        AsyncEntry entry = SphU.asyncEntry(resourceName);

        // Asynchronous invocation.
        doAsync(userId, result -> {
            // 在异步回调中进行上下文变换,通过 AsyncEntry 的 getAsyncContext 方法获取异步 Context
            ContextUtil.runOnContext(entry.getAsyncContext(), () -> {
                try {
                    // 此处嵌套正常的资源调用.
                    handleResult(result);
                } finally {
                    entry.exit();
                }
            });
        });
    } catch (BlockException ex) {
        // Request blocked.
        // Handle the exception (e.g. retry or fallback).
    }
}

这样,调用链路如下:

-parent
---asyncInvocation
-----handleResultForAsync

规则的种类

Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效

流量控制规则 (FlowRule)

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

同一个资源可以同时有多个限流规则。
通过调用 FlowRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则,比如:

private static void initFlowQpsRule() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule1 = new FlowRule();
    rule1.setResource(resource);
    // Set max qps to 20
    rule1.setCount(20);
    rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule1.setLimitApp("default");
    rules.add(rule1);
    FlowRuleManager.loadRules(rules);
}

熔断降级规则 (DegradeRule)

Field说明默认值
resource资源名,即规则的作用对象
grade熔断策略,支持慢调用比例/异常比例/异常数策略慢调用比例
count慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow熔断时长,单位为 s
minRequestAmount熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)5
statIntervalMs统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)1000 ms
slowRatioThreshold慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)

同一个资源可以同时有多个降级规则
通过调用 DegradeRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则:

private static void initDegradeRule() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule(resource);
        .setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());
        .setCount(0.7); // Threshold is 70% error ratio
        .setMinRequestAmount(100)
        .setStatIntervalMs(30000) // 30s
        .setTimeWindow(10);
    rules.add(rule);
    DegradeRuleManager.loadRules(rules);
}

系统保护规则 (SystemRule)

通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性

Field说明默认值
highestSystemLoadload1 触发值,用于触发自适应控制阶段-1 (不生效)
avgRt所有入口流量的平均响应时间-1 (不生效)
maxThread入口流量的最大并发数-1 (不生效)
qps所有入口资源的 QPS-1 (不生效)
highestCpuUsage当前系统的 CPU 使用率(0.0-1.0)-1 (不生效)

通过调用 SystemRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则:

private void initSystemProtectionRule() {
  List<SystemRule> rules = new ArrayList<>();
  SystemRule rule = new SystemRule();
  rule.setHighestSystemLoad(10);
  rules.add(rule);
  SystemRuleManager.loadRules(rules);
}

访问控制规则 (AuthorityRule)

黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

授权规则,即黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:

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

热点规则 (ParamFlowRule)

查询更改规则

引入了 transport 模块后,可以通过以下的 HTTP API 来获取所有已加载的规则:

http://localhost:8719/getRules?type=

  • type=flow 以 JSON 格式返回现有的限流规则
  • degrade 返回现有生效的降级规则列表
  • system 则返回系统保护规则

http://localhost:8719/getParamRules

  • type 可以输入 flow、degrade 等方式来制定更改的规则种类
  • data 则是对应的 JSON 格式的规则

定制自己的持久化规则

上面的规则配置,都是存在内存中的。即如果应用重启,这个规则就会失效

可以通过实现 DataSource 接口的方式,来自定义规则的存储数据源:

  • 整合动态配置系统,如 ZooKeeper、Nacos 等,动态地实时刷新配置规则
  • 结合 RDBMS、NoSQL、VCS 等来实现该规则
  • 配合 Sentinel Dashboard 使用

规则生效的效果

判断限流降级异常

通过以下方法判断是否为 Sentinel 的流控降级异常:BlockException.isBlockException(Throwable t);

block 事件

Sentinel 提供以下扩展接口,可以通过 StatisticSlotCallbackRegistry 向 StatisticSlot 注册回调函数:

  • ProcessorSlotEntryCallback:当资源条目通过(onPass)或阻塞(onBlocked)时的回调
  • ProcessorSlotExitCallback:资源条目成功完成时的回调(onExit)

其他API

业务异常统计 Tracer

业务异常记录类 Tracer 用于记录业务异常。相关方法:

  • trace(Throwable e):记录业务异常(非 BlockException 异常),对应的资源为当前线程 context 下 entry 对应的资源。
  • trace(Throwable e, int count):记录业务异常(非 BlockException 异常),异常数目为传入的 count。
  • traceEntry(Throwable, int, Entry):向传入 entry 对应的资源记录业务异常(非 BlockException 异常),异常数目为传入的 count。

如果用户通过 SphU 或 SphO 手动定义资源,则 Sentinel 不能感知上层业务的异常,需要手动调用 Tracer.trace(ex) 来记录业务异常,否则对应的异常不会统计到 Sentinel 异常计数中。注意不要在 try-with-resources 形式的 SphU.entry(xxx) 中使用,否则会统计不上。

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

上下文工具类 ContextUtil

略。

接入案例

添加依赖

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

配置

# 8179 用于和Dashboard通信
spring.cloud.sentinel.transport.port=8719
# Dashboard地址
spring.cloud.sentinel.transport.dashboard=localhost:8080 

开发资源

@RestController
public class SentinelApi {

    @GetMapping("si")
    @SentinelResource(value = "sentinel_info",blockHandler = "infoEx")
    public String info(@RequestParam  String name){
     return name+"#"+this.getClass().getName();
    }

    public String infoEx(String name, BlockException ex){
        ex.printStackTrace();
        return "过于频繁";
    }    

@SentinelResource(value = "sentinel_info",blockHandler = "infoEx")注解的意思:把该函数包装成Sentinel资源,该接口的调用被Sentinel监管,其中blockHandler指定当限流后执行的逻辑。其中infoEx和info两个函数的参数要保持一直,infoEx最后多一个BlockException参数

正常运行该项目,SentinelApi#info已经被sentinel监控,可以通过Dashboard配置流控、熔断、热点和授权。

Sentinel控制台

Sentinel 控制台目前仅支持单机部署。Sentinel 控制台项目提供 Sentinel 功能全集示例,不作为开箱即用的生产环境控制台,若希望在生产环境使用请根据文档自行进行定制和改造。

下载jar包

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

启用

java -Dserver.port=7001 -Dcsp.sentinel.dashboard.server=localhost:7001 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

默认用户名和密码都是 sentinel

客户端接入

需要添加配置项:spring.cloud.sentinel.transport.dashboard=localhost:7001