SpringCloudAlibaba之Sentinel流控熔断

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

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

1. 引入 Sentinel 依赖

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

2. 入门案例

资源 是 Sentinel 中的核心概念之一。最常用的资源是我们代码中的 Java 方法。 当然,您也可以更灵活的定义你的资源,例如,把需要控制流量的代码用 Sentinel API SphU.entry("HelloWorld")entry.exit() 包围起来即可。在下面的例子中,我们将 System.out.println("hello world"); 作为资源(被保护的逻辑),用 API 包装起来。参考代码如下:

SentinelService.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
package cn.javayuli.service;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
* 限流测试
* @author hanguilin
*/
@Service
public class SentinelService {

/**
* 处理请求
*/
public void handleRquest() {
// 配置规则.
initFlowRules();

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

/**
* 配置流控规则, QPS最大20
*/
private 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);
}
}

SentinelController.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 cn.javayuli.controller;

import cn.javayuli.service.SentinelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* 限流测试
* @author hanguilin
*/
@RestController
public class SentinelController {

@Autowired
private SentinelService sentinelService;

/**
* 请求资源
*/
@GetMapping("getSource")
public void doGetSource () {
sentinelService.handleRquest();
}

}

3.检查结果

启动项目,并使用Jmeter进行压测

配置1秒发送18个请求

配置访问接口

控制台正常打印hello world

现在把18调成30,发现已经被锁住了

Demo 运行之后,我们可以在日志 ${currentUser}/logs/csp/${appName}-metrics.log.xxx 里看到下面的输出:

1
2
3
4
5
6
7
|--timestamp-|------date time----|--resource-|p |block|s |e|rt
1608555871000|2020-12-21 21:04:31|/getSource|10|0|10|0|42|0|0|1
1608555871000|2020-12-21 21:04:31|HelloWorld|10|0|10|0|0|0|0|0
1608555871000|2020-12-21 21:04:31|__total_inbound_traffic__|10|0|10|0|42|0|0|0
1608555872000|2020-12-21 21:04:32|/getSource|20|0|20|0|1|0|0|1
1608555872000|2020-12-21 21:04:32|HelloWorld|15|5|15|0|0|0|0|0
1608555872000|2020-12-21 21:04:32|__total_inbound_traffic__|20|0|20|0|1|0|0|0

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

4.熔断降级

Sentinel 提供了 @SentinelResource 注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException

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

  • value:资源名称,必需项(不能为空)

  • entryType:entry 类型,可选项(默认为 EntryType.OUT

  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • fallback/fallbackClass

    :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.8.0 版本开始,defaultFallback 支持在类级别进行配置。

注: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) 来记录业务异常。

配置

Spring Cloud Alibaba

若您是通过 Spring Cloud Alibaba 接入的 Sentinel,则无需额外进行配置即可使用 @SentinelResource 注解。

Spring AOP

若您的应用使用了 Spring AOP(无论是 Spring Boot 还是传统 Spring 应用),您需要通过配置的方式将 SentinelResourceAspect 注册为一个 Spring Bean:

1
2
3
4
5
6
7
8
@Configuration
public class SentinelAspectConfiguration {

@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}

我们提供了 Spring AOP 的示例,可以参见 sentinel-demo-annotation-spring-aop

AspectJ

若您的应用直接使用了 AspectJ,那么您需要在 aop.xml 文件中引入对应的 Aspect:

1
2
3
<aspects>
<aspect name="com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect"/>
</aspects>

DEMO

系列源代码GitHub地址spring-cloud-demo

代码

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
/**
* hello
*
* @param s
* @return
*/
@SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
public String hello(long s) {
if (s == 0) {
throw new RuntimeException("error");
}
return String.format("Hello at %d", s);
}

/**
* Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
*
* @param s
* @return
*/
public String helloFallback(long s) {
return String.format("Halooooo %d", s);
}

/**
* Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
*
* @param s
* @param ex
* @return
*/
public String exceptionHandler(long s, BlockException ex) {
// Do some log here.
ex.printStackTrace();
return "Oops, error occurred at " + s;
}

接口测试

传入参数1

测试结果为正常调用的结果

image-20201222133146396

参数0,测试结果为回调结果

5.Sentinel控制台

概述

Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。另外,鉴权在生产环境中也必不可少。这里,我们将会详细讲述如何通过简单的步骤就可以使用这些功能。

接下来,我们将会逐一介绍如何整合 Sentinel 核心库和 Dashboard,让它发挥最大的作用。同时我们也在阿里云上提供企业级的控制台:AHAS Sentinel 控制台,您只需要几个简单的步骤,就能最直观地看到控制台如何实现这些功能。

Sentinel 控制台包含如下功能:

注意:Sentinel 控制台目前仅支持单机部署。

下载

  • jar包可以在release页面进行下载
  • 或者下载源码,运行时需要自行打包

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

鉴权

从 Sentinel 1.5.0 开始,控制台提供通用的鉴权接口 AuthService,用户可根据需求自行实现。

从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel

用户可以通过如下参数进行配置:

  • -Dsentinel.dashboard.auth.username=sentinel 用于指定控制台的登录用户名为 sentinel
  • -Dsentinel.dashboard.auth.password=123456 用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel
  • -Dserver.servlet.session.timeout=7200 用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟;

同样也可以直接在 Spring properties 文件中进行配置。

服务端

使用如下命令启动控制台:

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

如若8080端口冲突,可使用 -Dserver.port=新端口 进行设置。

浏览器打开localhost:8080,用户名和密码均为sentinel

客户端

在application.yml中加入如下配置

1
2
3
4
5
6
spring:
cloud:
sentinel:
transport:
port: 8719
dashboard: localhost:8080

如果是用nacos作为配置中心,需要在nacos中配置应用的配置文件

使用

如果我们想使用Sentinel的限流和熔断功能,除了在代码中可以硬编码外,也可直接通过控制台进行粗略的对接口进行限流和熔断。

在Controller中加入测试方法,注意此Controller使用的时@RestController注解,如果是@Controller,请给方法加上@ResponseBody注解

1
2
3
4
5
6
7
8
/**
* 请求资源
*/
@GetMapping("/testReq")
@SentinelResource("testSource")
public String doTest () {
return "test";
}

先调用一次该方法,方便被簇点链路收集,调用后,可在控制台->簇点链路中看到该资源

点击+流控新增规则

使用Jmeter进行测试,每秒执行10个请求

http请求

发现此处通过QPS最大限定值后,其余请求都被拒绝

查看评论