Sentinel 入门教程 - 中级篇

大纲

前言

1.0、版本说明

本文针对 Sentinel 1.8.0 及以上版本编写,特别说明除外。由于 1.8.0 版本对熔断降级特性进行了全新的改进升级,建议使用最新版本以更好地利用熔断降级的能力。

1.1、Sentinel 的控制规则

Sentinel 的所有规则都可以在内存态中动态地查询与修改,修改之后立即生效,同时 Sentinel 也提供了相关 API 供开发者来定制自己的规则策略。Sentinel 主要支持以下几种规则:

  • 流量控制规则
  • 熔断降级规则
  • 系统保护规则
  • 来源访问控制规则
  • 动态规则扩展

Sentinel 流量控制实现

2.0、流量控制概述

流量控制(Flow Control),其原理是监控应用流量的 QPS 或者并发线程数等指标,当达到指定的阀值时对流量进行控制,以避免被瞬间的流量高峰冲垮,从而保障应用的高可用性。FlowSlot 会根据预设的规则,结合 NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot 统计出来的实时信息进行流量控制。限流的直接表现是在执行 Entry nodeA = SphU.entry(resourceName) 的时候抛出 FlowException 异常。FlowExceptionBlockException 的子类,可以捕捉 BlockException 来自定义被限流之后的处理逻辑。

2.1、流量控制策略

Sentinel 的流量控制策略主要有两种实现方式:

  • 并发线程数:并发线程数限流用于保护业务线程数不被耗尽
  • QPS:当 QPS 超过某个阀值的时候,则采取措施进行流量控制

2.2、流量控制规则的属性

流量控制规则(FlowRule)包含下面几个重要的属性:

  • count:限流阀值

  • strategy:调用关系限流策略

  • resource:资源名,即流控规则的作用对象

  • grade:限流阀值类型(QPS 或者并发线程数)

  • limitApp:流控针对的调用来源,若为 default 则不区分调用来源

  • controlBehavior:流量整形的控制效果(直接拒绝、Warm Up、匀速排队)

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

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

    • 匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法,例如阈值 QPS=2 时,每个 500ms 处理一个请求,假设当前有 10 个请求则需要排队处理 5 秒。

特别注意:同一个资源可以同时拥有多个流控规则,Sentinel 检查规则时会依次检查。

2.3、流量控制规则的设置

流量控制规则设置有以下两种方式:

  • 本地代码设置
  • 在 Sentinel 控制台动态设置

2.3.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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@RestController
public class DegradeController {

/**
* 资源名称
*/
private static final String RESOURCE_NAME = "Flow";

/**
* @return
* @SentinelResource 定义资源
* value:资源名称
* blockHandler:限流处理的方法
*/
@SentinelResource(value = RESOURCE_NAME, blockHandler = "exceptionHandler")
@GetMapping("/hello")
public String hello() {
// 被保护的资源
return "Hello Sentinel!";
}

/**
* 原方法被限流的时候调用此方法
*
* @param e
* @return
*/
public String exceptionHandler(BlockException e) {
e.printStackTrace();
return "系统繁忙,请稍候 ...";
}

/**
* 当前类的构造方法执行之后执行此方法
*/
@PostConstruct
public void initFlowRules() {
// 创建存放流控规则的集合
List<FlowRule> rules = new ArrayList<>();
// 创建流控规则
FlowRule rule = new FlowRule();
// 定义资源,表示Sentinel会对哪个资源生效
rule.setResource(RESOURCE_NAME);
// 定义流控规则的类型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 定义QPS每秒能通过的请求数
rule.setCount(2);
// 将流控规则存放在集合中
rules.add(rule);
// 加载流控规则
FlowRuleManager.loadRules(rules);
}
}

程序运行后,通过浏览器访问 http://127.0.0.1:8080/hello,然后快速多次刷新页面,当每秒的请求数大于 2 时,接口的请求结果为 系统繁忙,请稍候 ...,则说明上面设置的流控规则生效了。

2.3.2、注解属性说明

  • 通过 @SentinelResource 注解的 blockHandler 属性制定具体的限流处理方法
  • 实现处理方法,该方法的传参必须与资源点的传参一样,并且最后必须加上 BlockException 异常参数,同时返回类型也必须一样

2.3.3、Sentinel 控制台动态设置

sentinel-add-flowrule

Sentinel 熔断降级实现

3.0、熔断降级概述

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。


现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或者异常比例升高),对这个资源的调用进行限制,让请求快速多次失败,避免影响到其他的资源而导致级联故障(服务雪崩)。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

3.1、熔断降级策略

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

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

  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

特别注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。

3.2、熔断降级规则的属性

熔断降级规则(DegradeRule)包含下面几个重要的属性:

sentinel-breaker-configuration

特别注意:同一个资源可以同时拥有多个熔断降级规则。

3.3、熔断降级规则的设置

熔断降级规则设置有以下两种方式:

  • 本地代码设置
  • 在 Sentinel 控制台动态设置

3.3.1、代码设置

下面将演示如何使用慢调用比例 (SLOW_REQUEST_RATIO)熔断降级规则,点击下载完整的案例代码。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@RestController
public class DegradeController {

private static final String RESOURCE_NAME = "Degrade";

private Logger LOG = LoggerFactory.getLogger(DegradeController.class);

/**
* @return
* @SentinelResource 定义资源
* value:资源名称
* blockHandler:熔断降级处理的方法
*/
@SentinelResource(value = RESOURCE_NAME, fallback = "exceptionHandler")
@GetMapping("/hello")
public String hello() {
// 被保护的资源
try {
Random random = new Random();
int millis = random.nextInt(10);
LOG.info("sleep time: " + millis);
// 随机休眠10毫秒以内,模拟接口慢调用
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
}

/**
* 原方法被熔断降级的时候调用此方法
*
* @return
*/
public String exceptionHandler() {
LOG.error("fallback handler invoke");
return "系统繁忙,请稍候 ...";
}

/**
* 定义熔断降级规则
*/
@PostConstruct
public void initDegradeRule() {
// 创建存放熔断降级规则的集合
List<DegradeRule> rules = new ArrayList<>();
// 创建熔断降级规则
DegradeRule rule = new DegradeRule();
// 定义资源名称
rule.setResource(RESOURCE_NAME);
// 定义熔断降级规则的类型
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
// 定义降级熔断时间(单位 s)
rule.setTimeWindow(5);
// 定义慢调用临界RT(超出该值计为慢调用,单位 s)
rule.setCount(0.005);
// 定义熔断触发的最小请求数
rule.setMinRequestAmount(1);
// 定义统计时长(单位为 ms)
rule.setStatIntervalMs(1000);
// 定义慢调用比例阈值
rule.setSlowRatioThreshold(0.5);
// 将熔断降级规则添加到集合中
rules.add(rule);
// 加载熔断降级规则
DegradeRuleManager.loadRules(rules);
}
}

上述定义的慢调用比例熔断降级规则为:调用临界 RT(超出该值计为慢调用)值为 0.005 秒,当 1000 毫秒内请求数量大于 1,且慢调用的比例大于阈值大于 0.5,则熔断降级 5 秒。


程序运行后,通过浏览器访问 http://127.0.0.1:8080/hello,然后快速多次刷新页面,若输出的日志信息类似下面的内容,则说明上面设置的熔断降级规则生效了。

1
2
3
4
5
6
7
8
9
10
2020-01-18 22:15:45.705  INFO 61206 --- [nio-8080-exec-1] c.s.study.controller.DegradeController   : sleep time: 8
2020-01-18 22:15:46.585 ERROR 61206 --- [nio-8080-exec-3] c.s.study.controller.DegradeController : fallback handler invoke
2020-01-18 22:15:47.112 ERROR 61206 --- [nio-8080-exec-5] c.s.study.controller.DegradeController : fallback handler invoke
2020-01-18 22:15:48.911 ERROR 61206 --- [nio-8080-exec-7] c.s.study.controller.DegradeController : fallback handler invoke
2020-01-18 22:15:49.505 ERROR 61206 --- [nio-8080-exec-9] c.s.study.controller.DegradeController : fallback handler invoke
2020-01-18 22:15:49.809 ERROR 61206 --- [nio-8080-exec-1] c.s.study.controller.DegradeController : fallback handler invoke
2020-01-18 22:15:51.511 INFO 61206 --- [nio-8080-exec-3] c.s.study.controller.DegradeController : sleep time: 1
2020-01-18 22:15:51.834 INFO 61206 --- [nio-8080-exec-5] c.s.study.controller.DegradeController : sleep time: 3
2020-01-18 22:15:52.428 ERROR 61206 --- [nio-8080-exec-7] c.s.study.controller.DegradeController : fallback handler invoke
2020-01-18 22:15:52.846 ERROR 61206 --- [nio-8080-exec-9] c.s.study.controller.DegradeController : fallback handler invoke

3.3.2、注解属性说明

sentinel-annotation-configuration

3.3.3、Sentinel 控制台动态设置

sentinel-dashboard-add-degrade-rule

Sentinel 系统自适应保护实现

4.0、系统自适应保护概述

在开始之前,先了解一下系统保护的目的:

  • 保证系统不被拖垮
  • 在系统稳定的前提下,保持系统的吞吐量

长期以来,系统保护的思路是根据硬指标,即系统的负载 (load1) 来做系统过载保护。当系统负载高于某个阈值,就禁止或者减少流量的进入;当 load 开始好转,则恢复流量的进入。这个思路给我们带来了不可避免的两个问题:

  • load 是一个 “结果”,如果根据 load 的情况来调节流量的通过率,那么就始终有延迟性。也就意味着通过率的任何调整,都会过一段时间才能看到效果。当前通过率是使 load 恶化的一个动作,那么也至少要过 1 秒之后才能观测到;同理,如果当前通过率调整是让 load 好转的一个动作,也需要 1 秒之后才能继续调整,这样就浪费了系统的处理能力。所以我们看到的曲线,总是会有抖动。

  • 恢复慢。想象一下这样的一个场景(真实),出现了这样一个问题,下游应用不可靠,导致应用 RT 很高,从而 load 到了一个很高的点。过了一段时间之后下游应用恢复了,应用 RT 也相应减少。这个时候,其实应该大幅度增大流量的通过率;但是由于这个时候 load 仍然很高,通过率的恢复仍然不高。


TCP BBR 的思想给了我们一个很大的启发。我们应该根据系统能够处理的请求,和允许进来的请求,来做平衡,而不是根据一个间接的指标(系统 load)来做限流。最终我们追求的目标是在系统不被拖垮的情况下,提高系统的吞吐率,而不是 load 一定要到低于某个阈值。如果我们还是按照固有的思维,超过特定的 load 就禁止流量进入,系统 load 恢复就放开流量,这样做的结果是无论我们怎么调参数,调比例,都是按照果来调节因,都无法取得良好的效果。Sentinel 在系统自适应保护的做法是,用 load1 作为启动自适应保护的因子,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。

4.1、系统自适应保护策略

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 Load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。


系统规则支持以策略:

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

4.2、系统自适应保护规则的属性

sentinel-system-auto-protect

特别注意:系统自适应保护规则只针对入口资源(EntryType.IN)有效

4.3、系统自适应保护规则的设置

系统自适应保护规则设置有以下两种方式:

  • 本地代码设置
  • 在 Sentinel 控制台动态设置

4.3.1、代码设置

以下演示的是如何使用 入口 QPS 系统自适应保护规则,点击下载完整的案例代码。

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
@RestController
public class SystemProtectController {

/**
* 定义资源
* EntryType.IN 表示入口资源
*
* @return
*/
@SentinelResource(entryType = EntryType.IN)
@GetMapping("/hello")
public String hello() {
return "Hello Sentinel!";
}

/**
* 定义系统自适应保护规则
*/
@PostConstruct
public void initSystemRule() {
// 创建存放系统自适应保护规则的集合
List<SystemRule> rules = new ArrayList<>();
// 创建系统自适应保护规则
SystemRule rule = new SystemRule();
// 定义入口资源的QPS(每秒允许的最大请求数)
rule.setQps(2);
// 添加系统自适应保护规则到集合中
rules.add(rule);
// 加载系统自适应保护规则
SystemRuleManager.loadRules(rules);
}
}

程序运行后,当 /hello 接口每秒请求的次数大于 2,则会触发 Sentinel 的系统自适应保护规则,同时会返回 Blocked by Sentinel (flow limiting) 字符串给客户端。

4.3.2、Sentinel 控制台动态设置

sentinel-add-system-rule

Sentinel 来源访问控制实现

5.0、来源访问控制概述

很多时候需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(授权控制、黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。调用方的信息通过 ContextUtil.enter(resourceName, origin) 方法中的 origin 参数传入。特别注意,白名单和黑名单不能同时使用。

5.1、来源访问控制规则的属性

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

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

5.2、来源访问控制规则的设置

来源访问控制规则设置有以下两种方式:

  • 本地代码设置
  • 在 Sentinel 控制台动态设置

5.2.1、代码设置

下面将演示如何使用白名单来源访问控制规则,点击下载完整的案例代码。

1
2
3
4
5
6
7
8
9
10
11
/**
* 自定义来源解析器
*/
@Component
public class RequestOriginParserDefinition implements RequestOriginParser {

@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
return httpServletRequest.getRemoteAddr();
}
}
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
@RestController
public class OriginControlController {

private static final String RESOURCE_NAME = "Origin";

/**
* @return
* @SentinelResource 定义资源
* value:资源名称
* blockHandler:被限制访问时处理的方法
*/
@SentinelResource(value = RESOURCE_NAME, blockHandler = "exceptionHandler")
@GetMapping("/hello")
public String hello() {
return "Hello Sentinel!";
}

/**
* 原方法被限制访问的时候调用此方法
*
* @param e
* @return
*/
public String exceptionHandler(BlockException e) {
return "系统繁忙,请稍候 ...";
}

/**
* 定义来源访问控制规则(黑名单)
*/
@PostConstruct
public void initBlackRule() {
// 创建存放规则的集合
List<AuthorityRule> rules = new ArrayList<>();
// 创建来源访问控制规则
AuthorityRule rule = new AuthorityRule();
// 定义资源名称
rule.setResource(RESOURCE_NAME);
// 定义限制模式
rule.setStrategy(RuleConstant.AUTHORITY_BLACK);
// 定义请求来源
rule.setLimitApp("127.0.0.1");
// 将规则保存到集合中
rules.add(rule);
// 加载规则
AuthorityRuleManager.loadRules(rules);
}
}

程序运行后,通过浏览器访问 http://127.0.0.1:8080/hello,若响应结果为 系统繁忙,请稍候 ...,则说明上面设置的黑名单来源控制规则生效了。

5.2.2、Sentinel 控制台动态设置

sentinel-add-origin-control-rule

Sentinel 动态规则扩展(持久化规则)

Sentinel 的理念是开发者只需要关注资源的定义,当资源定义成功后可以动态增加各种流控降级规则。Sentinel 提供以下几种方式设置规则:

  • 通过 API 直接设置 (loadRules)
  • 通过 DataSource 适配不同数据源修改

手动通过 API 设置比较直观,可以通过以下几个 API 设置不同的规则:

1
2
3
4
FlowRuleManager.loadRules(List<FlowRule> rules); // 设置流控规则
DegradeRuleManager.loadRules(List<DegradeRule> rules); // 设置熔断降级规则
SystemRuleManager.loadRules(List<SystemRule> rules); // 设置系统自适应保护规则
AuthorityRuleManager.loadRules(List<AuthorityRule> rules); // 设置来源访问控制规则

6.0、DataSource 扩展

不管是通过 Java 代码还是通过 Sentinel 控制台的方式设置流控降级规则,都属于手动方式,不够灵活。这种方式一般仅用于测试和演示,生产环境一般通过动态规则源的方式来动态管理流控降级规则。上述 loadRules() 方法只接受内存态的规则对象,但更多时候规则存储在文件、数据库或者配置中心当中。Sentinel 的 DataSource 接口提供了对接任意数据源的能力。Sentinel 官方推荐通过控制台设置规则后,将规则推送到统一的规则中心,客户端则实现 ReadableDataSource 接口监听规则中心来实时获取规则配置的变更,流程图如下:

sentinel-data-source

DataSource 扩展常见的实现方式有:

  • 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更
  • 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心,这种方式有更好的实时性和一致性保证

Sentinel 目前支持以下数据源扩展:

  • Pull-based(拉模式): 动态文件数据源、Consul、Eureka
  • Push-based(推模式): ZooKeeper、Apollo、Nacos, etcd、Redis

6.1、使用 ZooKeeper 规则配置(推模式)

下面将演示如何使用 ZooKeeper 存放 Sentinel 的流控规则配置数据,使用的是 推模式,各组件的版本如下,点击下载完整的案例代码。

  • Sentinel 1.8.0
  • ZooKeeper Server 3.5.5
  • Spring Boot 2.1.18.RELEASE
  • Sentinel Datasource Zookeeper 1.8.0
  • Spring Cloud Starter Sentinel 2.1.3.RELEASE

6.1.1、代码示例

引入 Maven 依赖,添加 sentinel-datasource-zookeeper 依赖

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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.18.RELEASE</version>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-cloud-starter-sentinel>2.1.3.RELEASE</spring-cloud-starter-sentinel>
<sentinel-datasource-zookeeper.version>1.8.0</sentinel-datasource-zookeeper.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${spring-cloud-starter-sentinel}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-zookeeper</artifactId>
<version>${sentinel-datasource-zookeeper.version}</version>
</dependency>
</dependencies>

创建 Sentinel 的 配置类,让 Sentinel 使用 ZooKeeper 作为规则配置数据源

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
@Configuration
public class SentinelZookeeperConfig {

public static final String ZOOKEEPER_ADDRESS = "127.0.0.1:2181";

public static final String ZOOKEEPER_PATH = "/Sentinel/FlowRules";

/**
* Sentinel从Zookeeper加载规则配置数据
*/
@PostConstruct
public void init() {
// 参数一:Zookeeper的地址
// 参数二:Zookeeper中数据的路径
// 参数三:Zookeeper中数据的解析器
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new ZookeeperDataSource<>(
ZOOKEEPER_ADDRESS,
ZOOKEEPER_PATH,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
}));

// 加载流控规则
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}
}

创建 Controller 测试类

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
@RestController
public class HelloController {

private final static Logger LOG = LoggerFactory.getLogger(HelloController.class);

/**
* 资源名称
*/
public static final String RESOURCE_NAME = "Hello";

/**
* @return
* @SentinelResource 定义资源
* value:资源名称
* blockHandler:限流处理的方法
*/
@SentinelResource(value = RESOURCE_NAME, blockHandler = "exceptionHandler")
@GetMapping("/hello")
public String hello() {
return "Hello Sentinel!";
}

/**
* 原方法被限流的时候调用此方法
*
* @param e
* @return
*/
public String exceptionHandler(BlockException e) {
LOG.info("系统繁忙,请稍候 ...");
return "系统繁忙,请稍候 ...";
}
}

创建主启动类

1
2
3
4
5
6
7
@SpringBootApplication
public class SentinelApplication {

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

创建 application.yml 配置文件,其中 sentinel.transport.dashboard 为非必要配置项;若不需要通过 Sentinel 控制台监控应用,这里可以不配置 sentinel.transport.dashboard,无论是否配置都不会影响应用加载和动态感知 ZooKeeper Server 中的规则配置数据

1
2
3
4
5
6
7
8
9
10
server:
port: 8080

spring:
application:
name: sentinel-zookeeper-demo
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:9000 # 非必要配置项

创建 ZooKeeper 的数据测试类,作用是插入规则配置数据到 ZooKeeper

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
54
55
56
@RunWith(SpringRunner.class)
@SpringBootTest
public class ZookeeperConfigSender {

private static final int RETRY_TIMES = 3;
private static final int SLEEP_TIME = 1000;

@Test
public void sendData() throws Exception {
final String remoteAddress = "127.0.0.1:2181";
final String groupId = "Sentinel";
final String dataId = "FlowRules";
final String rule = "[\n"
+ " {\n"
+ " \"resource\": \"Hello\",\n"
+ " \"controlBehavior\": 0,\n"
+ " \"count\": 2.0,\n"
+ " \"grade\": 1,\n"
+ " \"limitApp\": \"default\",\n"
+ " \"strategy\": 0\n"
+ " }\n"
+ "]";

CuratorFramework zkClient = CuratorFrameworkFactory.newClient(remoteAddress, new ExponentialBackoffRetry(SLEEP_TIME, RETRY_TIMES));
zkClient.start();
String path = getPath(groupId, dataId);
Stat stat = zkClient.checkExists().forPath(path);
if (stat == null) {
zkClient.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, null);
}
zkClient.setData().forPath(path, rule.getBytes());

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}

zkClient.close();
}

private static String getPath(String groupId, String dataId) {
String path = "";
if (groupId.startsWith("/")) {
path += groupId;
} else {
path += "/" + groupId;
}
if (dataId.startsWith("/")) {
path += dataId;
} else {
path += "/" + dataId;
}
return path;
}
}

6.1.2、测试代码

  • 1)启动 ZooKeeper Server
  • 2)启动 sentinel-zookeeper-demo 应用,若控制台输出如下日志信息,则说明应用已经成功连接上 ZooKeeper 服务器
1
2
3
4
[localhost:2181)] org.apache.zookeeper.ClientCnxn          : Opening socket connection to server localhost/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
[localhost:2181)] org.apache.zookeeper.ClientCnxn : Socket connection established to localhost/127.0.0.1:2181, initiating session
[localhost:2181)] org.apache.zookeeper.ClientCnxn : Session establishment complete on server localhost/127.0.0.1:2181, sessionid = 0x10000447be50007, negotiated timeout = 40000
[ain-EventThread] o.a.c.f.state.ConnectionStateManager : State change: CONNECTED
  • 3)浏览器访问 http://127.0.0.1:8080/hello,快速多次刷新页面,可以发现响应结果会一直返回 Hello Sentinel! 字符串
  • 4)通过 Junit 执行 ZookeeperConfigSender.sendData() 方法,将规则配置数据插入到 ZooKeeper Server
  • 5)通过命令行登录进 ZooKeeper Server 后,执行以下操作,可以观察到 ZooKeeper Server 中有对应的规则配置数据成功插入了
1
2
3
4
5
6
7
8
9
10
11
[zk: localhost:2181(CONNECTED) 15] get /Sentinel/FlowRules
[
{
"resource": "Hello",
"controlBehavior": 0,
"count": 2.0,
"grade": 1,
"limitApp": "default",
"strategy": 0
}
]
  • 6)浏览器再次快速多次访问 http://127.0.0.1:8080/hello,若响应结果为 系统繁忙,请稍候 ...,则说明 Sentinel 成功加载到 ZooKeeper Server 里的规则配置数据,而且是基于 推模式,默认支持监听规则配置的变更

6.2、更多数据源扩展支持

Sentinel 默认还支持文件、Nacos、Apollo、Redis 等作为数据源扩展,这里不再累述,具体可以阅读官方文档中的动态规则扩展