写在前面

前面我们通过一个入门的demo来演示了如何使用Sentinel来实现接口的限流,其实它只是流控规则的一种实现,流控规则除了基于QPS和并发数以外,还支持调用关系的流量控制,但是这都不是本文的重点。本文主要介绍基于流控规则的QPS方式,如何将之前使用的流控规则进行持久化存储。

规则持久化

现在系统出了一个bug,需要重启sentinel-hello服务,然后你会发现一个神奇的现象,就是之前开发者在sentinel-dashboard页面配置的限流规则居然不见了,而且该微服务显示处于“失联”状态:

因此现在需要做的就是对限流规则进行持久化操作,那么如何进行持久化呢?往下看,并阅读 官方文档 进行了解。

从官方文档中可以看出,我们上面讲到的持久化,其实是动态规则扩展里面的外部配置源扩展,接下来就详细学习这个外部配置源扩展。不过在此之前有必要先对动态规则扩展进行一个学习,并通过流控规则基于QPS实现这一例子,来介绍规则持久化存储。

内存态扩展

内存态扩展就是直接通过使用API来直接修改规则,Sentinel提供了以下5个API来修改不同的规则:

1
2
3
4
5
FlowRuleManager.loadRules(List<FlowRule> rules); // 流控规则
DegradeRuleManager.loadRules(List<DegradeRule> rules); // 降级规则
ParamFlowRuleManager.loadRules(List<DegradeRule> rules);// 热点规则
SystemRuleManager.loadRules(List<SystemRule> rules); // 系统规则
AuthorityRuleManager.loadRules(List<AuthorityRule> rules); // 授权规则

其实上述5个API就分别对应于首页的5个规则,同时它们都使用了loadRules方法来加载规则:

由于这5个API分别用于不同的场景,因此后续会结合实际场景来进行学习,这里就只介绍最简单的流控规则。接下来就简单学习如何使用上述提供的API来动态扩展规则。

流控规则

流控规则,全称为流量控制规则,其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

接下来我们将通过sentinel-dashboard控制台设置和API直接修改这两种方式来演示流控规则如何设置与使用。

控制台设置

这里我们以/hello接口为例,来学习如何对其进行限流设置。点击sentinel-dashboard首页左侧的流控规则按钮,然后点击右侧的新增流控规则按钮,按照下图进行设置:

这里的流控规则非常简单,资源名填入/hello,就是你需要限流的接口;阈值类型选择QPS,也就是每秒查询条数;单机阈值设置2,表示每秒最多允许2个请求进入。之后点击新增按钮,之后页面就显示流控规则已经添加:

接下来就开始验证上述流控规则是否有效,可以同时多次访问http://127.0.0.1:8036/hello链接,看看页面的返回情况,当每秒请求次数超出设置的2次,页面就会提示以下信息:

再来看几个通过控制台设置的例子,如下所示:

其实可以发现上述设置其实都是使用了高级选项中的默认设置,它就表示当每秒访问/movie接口的次数大于QPS设置的2时,/movie接口就会立即失败,并进行流控。


上图表示,当访问关联资源/book接口的QPS大于设置的阈值2,那么接口/movie就会立即失败,并进行流控。


上图表示,当访问/movie接口的QPS大于设置的阈值2,那么后续请求就会处于排队等待状态,超时时间为20秒。


上图表示,在预热完成前,QPS只有10/(3,冷启动因子,默认为3)=3,5秒后预热完成,QPS达到10。

API直接修改

第一步,在pom.xml依赖文件中添加spring-cloud-starter-alibaba-sentinel依赖,之前的项目已经添加,因此这一步可以跳过。

第二步,在项目目录下新建hellorules包,并在该包内新建一个HelloRules类,其中的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class HelloRules {
/**
* 流控规则
* */
@Bean
public void flowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule flowRule = new FlowRule();
flowRule.setResource("book");
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
flowRule.setCount(2);
rules.add(flowRule);
FlowRuleManager.loadRules(rules);
}
}

请注意这里设置的流控规则与下面这张图相似,不同点是资源名称由hello变成了book而已,因此这个book就是一个接口,后续需要提供,其余的阈值类型选择QPS,单机阈值设置2:

第三步,在之前创建的HelloController类中提供一个/book接口,如下所示:

1
2
3
4
5
6
@GetMapping(value = "/book")
@SentinelResource(value = "book")
public String book(){
log.info("flowRules is doing...");
return "flowRules!";
}

这里使用的@SentinelResource注解用来标识资源是否被限流、降级,该注解的value属性book表示资源名,也就是接口(注意没有/符号)。@SentinelResource还提供了其它额外的属性如blockHandlerblockHandlerClassfallback用于表示限流或降级的操作,这些信息都会在后续学习Sentinel注解支持的时候进行介绍。

第四步,启动项目。启动这里名为sentinel-hello的服务,接着在浏览器访问http://127.0.0.1:8036/book链接,即可看到如下的返回信息:

如果一秒内的请求次数大于2,那么该页面就会出如下的错误,这个页面很熟悉,在学习Hystrix的时候经常出现,这就说明服务熔断了:

而且可以看到在控制台的流控规则那一栏中已经显示出我们对于book接口的限流规则:

但是细心的朋友可能已经发现,API这种直接修改方式式硬编码的,很多配置内容都是写死的,就算你通过读取配置文件来设置,但是配置文件的管理也是问题,因此这种方式最好只在学习和测试的时候时候,在实际工作中不要使用这种方式。

当然流控规则还有另一个根据线程数来设置限流,但总的来说与根据QPS限流设置的规则一样,因此这里就不过多解释,关于流控规则的详细学习会在后面有专门的文章进行学习。同时本文重点在于学习如何将控制规则进行持久化存储,而不是学习流控规则相关的知识,这部分会在后续有专门的文章进行学习。

外部配置源扩展

前面我们介绍的API是只能接受内存态的规则对象,但是在更多时候规则都是存储在文件、数据库或者配置中心里面,因此Sentinel提供的DataSource接口允许我们能对接任意的数据配置源,相比于之前直接通过API在内存中修改规则,使用外部配置源无疑是最佳选择。

官方文档推荐我们通过控制台设置规则后,将规则推送到统一的规则中心这一方式,然后客户端实现ReadableDataSource接口端监听规则中心实时获取变更,流程如下:

推拉模式介绍

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

根据上面介绍的推拉模式,Sentinel目前支持以下数据源扩展:(1)基于拉模式的有:动态文件数据源、Consul和Eureka;(2)基于推模式的有:ZooKeeper、Redis、Nacos、Apollo和etcd。

推拉模式扩展

如果开发者想基于上述模式进行自定义扩展,而不使用上述产品的话,Sentinel也是支持的。

如果开发者想对拉模式扩展,最简单的方式是将数据源继承AutoRefreshDataSource抽象类,然后实现readSource() 方法,在该方法里指定数据源读取字符串格式的配置数据,比如基于文件的数据源。

如果开发者想对拉模式扩展,最简单的方式是将数据源继承AbstractDataSource抽象类,然后在其构造方法中添加监听器,并实现readSource()方法,在该方法内指定数据源读取字符串格式的配置数据。比如基 Nacos的数据源。

当然控制台通常需要做一些改造来直接推送应用维度的规则到配置中心,这个改造指南可以参考官方文档。

前面也提到过,由于拉模式采用的是客户端主动向某个规则管理中心定期轮询拉取规则,该方式的缺点是无法及时获取变更,因此在实际工作中这样方式用的不是很多,更多的则是使用推模式,这里以Nacos为例,介绍推模式如何持久化存储规则。

推模式Nacos持久化存储

接下来就介绍如何使用Nacos来持久化存储流控规则,在这个例子中我们将同时使用到Nacos和Sentinel Dashboard,因此就可以将这两个应用服务以默认配置进行启动,其中两者的默认访问路径为:Nacos地址是http://127.0.0.1:8848;Sentinel DashBoard地址是http://127.0.0.1:8080

考虑到微服务的特性,这里就新建一个测试用的实例,这也与微服务的解耦思想相匹配。

第一步,创建Spring Boot项目。使用Spring Initializr工具创建一个Spring Boot应用,名称为sentinel-storage-nacos

第二步,编辑依赖文件。请注意考虑到版本兼容问题,因此最好还是使用之前学习Nacos时的Spring Boot、Spring Cloud、Spring Cloud Alibaba的版本。编辑项目的pom.xml依赖文件,添加如下配置信息:

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
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<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>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<optional>true</optional>
</dependency>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

第三步,配置服务名称、sentinel-dashboard的访问地址以及Nacos中的配置信息。application.properties配置文件中指定服务名称,端口号,sentinel-dashboard的访问地址,以及Nacos中的配置信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 应用名称
spring.application.name=sentinel-storage-nacos
# 应用端口号
server.port=8038

# sentinel-dashboard访问地址
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8080

# sentinel-storage-nacos配置
# nacos的访问地址
spring.cloud.sentinel.datasource.ds.nacos.server-addr=127.0.0.1:8848
# nacos中存储规则的dataId
spring.cloud.sentinel.datasource.ds.nacos..data-id=${spring.application.name}
# nacos中存储规则的groupId
spring.cloud.sentinel.datasource.ds.nacos..group-id=DEFAULT_GROUP
# nacos中存储的规则类型
spring.cloud.sentinel.datasource.ds.nacos.rule-type=flow

这里主要介绍后者的设置内容,主要是配置nacos中存储规则的dataId、groupId和ruleType,前面两个设置信息在前面都已经进行了介绍,接下来主要学习ruleType,它用来定义存储的规则类型,开发者可以查看枚举类com.alibaba.cloud.sentinel.datasource.RuleType,如下所示:

1
2
3
4
5
6
7
8
9
public enum RuleType {
FLOW("flow", FlowRule.class),
DEGRADE("degrade", DegradeRule.class),
PARAM_FLOW("param-flow", ParamFlowRule.class),
SYSTEM("system", SystemRule.class),
AUTHORITY("authority", AuthorityRule.class),
GW_FLOW("gw-flow", "com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule"),
GW_API_GROUP("gw-api-group", "com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition");
}

这里因为是存储流控规则,因此使用的就是flow这一类型。还有上面我们的dataId使用了${spring.application.name}这一变量,这样就可以根据应用名称来区分不同的规则配置。

第四步,提供一个访问接口。由于这是一个web工程,因此可以添加一个Controller,并提供一个访问入口。在项目目录下新建controller包,并在里面新建HelloController文件,其中的代码为:

1
2
3
4
5
6
7
@RestController
public class HelloController {
@GetMapping(value = "/hello")
public String hello(){
return "envythink";
}
}

第五步,在Nacos中创建流控规则配置。确保之前的Nacos已经以默认配置启动成功,然后按照下图所示配置信息来新建一个配置:

请注意在这里dataId设置为应用名称;groupId设置为默认分组;配置格式设置为JSON,然后里面的配置内容如下:

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

可以看到上面配置的流控规则是一个JSON格式的数组类型,数组中的每个对象是针对每个保护资源的配置对象,这里对这些配置对象简单介绍一下:

  • resource,资源名,即限流规则的作用对象;
  • limitApp,流控针对的调用来源,如设置为default则不区分调用来源;
  • grade,限流阈值类型,可以是QPS或并发线程数,其中0代表根据并发数量来限流,1代表根据QPS来进行流量控制;
  • count,限流阈值;
  • strategy,调用关系限流策略;
  • controlBehavior,流量控制效果,有三种分别为:直接拒绝、Warm Up和匀速排队;
  • clusterNode,是否为集群模式,这里肯定是false。

这里只是简单的介绍了上述配置,但是除此之外还有很多可选配置和规则,这些在后续文章中将会介绍。

第五步,启动应用。请确保sentinel-dashboard服务已经正常运行,然后启动这里名为sentinel-storage-nacos的服务。接着在浏览器访问http://127.0.0.1:8038/hello链接,即可看到如下的返回信息:

此时,打开浏览器访问Sentinel DashBoard,可以看到当前我们启动的名为sentinel-storage-nacos的服务已经在其中显示了:

然后点击左侧菜单栏中的流控规则,可以看到其中已经存在一条记录,如下所示:

查看一下这条流控规则的详情信息,如下所示:

可以发现这里填写的其实就是之前我们在Nacos中配置的JSON格式的数组对象的信息,也就是说这样我们就在Sentinel Dashboard中获取到了存储在Nacos中的配置规则,自然也可以将Sentinel Dashboard中设置的流控规则存储到Nacos中。在完成上述操作后,对于接口流控规则的修改,开发者既可以通过Sentinel Dashboard来修改,还可以通过Nacos控制台来修改。

需要说明的是,通过Nacos修改的配置系信息,那么Sentinel中的信息也会同步修改,但是通过Sentinel修改的信息,Nacos中不一定会同步修改。开发者需要确认当前版本的Sentinel控制台是否具备同步修改Nacos配置的能力,Nacos可以通过在客户端中使用Listener来实现自动更新。

也就是说,开发者在使用Nacos来持久化存储规则后,需要明确两者修改规则是否同步,其中在Sentinel控制台中修改规则,仅存在于服务的内存中,它是不会修改Nacos中的配置值,重启服务后将恢复原来的值;而在Nacos控制台中修改规则,不仅服务内存中的规则会更新,同时Nacos中持久化的规则也会更新,且重启服务后修改值不会恢复为原来的值。