1. 序言

sentinel数据默认保存在内存中,dashboard服务端和使用客户端均是如此。
客户端需要在启动时通过编码加载,改动时需要修改代码,无法做到配置化。

这里改造分为服务端和客户端的改造

  • 服务端改造:从nacos中获取规则数据,保存时存入nacos中
  • 客户端改造:启动时从nacos中获取数据

2. 改造

2.1. 服务端sentinel-dashboard改造

这里只对push模式改造,其他两种因有不完全缺陷,也不适合生产,就不考虑了。

sentinel官方修改文档

主要修改建议主要修改建议

民间修改文档

push模式改造需要改造sentinel-dashboard,dashboard应该与nacos交互存储,持久化至nacos中

2.1.1. 下载sentinel-dashboard工程

https://github.com/alibaba/Sentinel

下载工程,拷贝出sentinel-dashboard

切换项目只能得到1.8.6的版本,这里使用1.8.6版本,正好也比1.8.1版本好改造

2.1.2. 改造项目

2.1.2.1. 修改依赖

pom文件中取消sentinel-datasource-nacos的text属性,否则依赖只在测试下生效,之后拷贝文件会没有依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!--<scope>test</scope>-->
</dependency>

由于dashboard工程依赖父工程,这里没有拷贝父工程下来,因此将父工程的pom信息整合进来,得到以下pom

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-dashboard</artifactId>
<version>1.8.6</version>
<packaging>jar</packaging>

<properties>
<project.version>1.8.6</project.version>
<resource.delimiter>@</resource.delimiter>
<spring.boot.version>2.5.12</spring.boot.version>
<curator.version>4.0.1</curator.version>
<!-- Compile libs -->
<fastjson.version>1.2.83_noneautotype</fastjson.version>
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>

<!-- Test libs -->
<junit.version>4.12</junit.version>
<mockito.version>2.28.2</mockito.version>
<assertj.version>3.12.1</assertj.version>
<awaitility.version>3.1.5</awaitility.version>
<powermock.version>2.0.0</powermock.version>

<!-- Build -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.source.version>1.8</java.source.version>
<java.target.version>1.8</java.target.version>
<java.encoding>UTF-8</java.encoding>
<maven.compiler.version>3.8.0</maven.compiler.version>
<maven.surefire.version>2.22.1</maven.surefire.version>
<maven.source.version>3.0.1</maven.source.version>
<maven.javadoc.version>3.0.1</maven.javadoc.version>
<maven.deploy.version>2.8.2</maven.deploy.version>
<maven.gpg.version>1.6</maven.gpg.version>
<maven.jacoco.version>0.8.3</maven.jacoco.version>
<maven.jar.version>3.1.0</maven.jar.version>
<maven.pmd.version>3.8</maven.pmd.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-extension</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-cdi-interceptor</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-zookeeper</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-apollo</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-etcd</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-netty-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-spring-mvc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-common-default</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-adapter</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-metric-exporter</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>${awaitility.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>java-hamcrest</artifactId>
<version>2.0.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
<version>4.4.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>

<!-- for Nacos rule publisher sample -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!--<scope>test</scope>-->
</dependency>
<!-- for Apollo rule publisher sample -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-openapi</artifactId>
<version>1.2.0</version>
<scope>test</scope>
</dependency>

<!--for Zookeeper rule publisher sample-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<version>1.16.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<finalName>sentinel-dashboard</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimiter>${resource.delimiter}</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<fork>true</fork>
<mainClass>com.alibaba.csp.sentinel.dashboard.DashboardApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven.deploy.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>

<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>

<resource>
<directory>src/main/webapp/</directory>
<excludes>
<exclude>resources/node_modules/**</exclude>
</excludes>
</resource>
</resources>
</build>
</project>

有的会报红,不用管它,不影响项目运行

最后dashboard工程目录如下:
dashboard

2.1.2.2. 拷贝文件

  1. 工程下sentinel-dashboard的test目录下有nacos实现,拷贝至src下rule目录下,如图:

    nacos

2.1.2.3. 修改流控规则

2.1.2.3.1. NacosConfigV2

将两个动态注入类DynamicRuleProvider<List<FlowRuleEntity>> ruleProviderDynamicRulePublisher<List<FlowRuleEntity>> rulePublisher修改为nacos加载的那两个类

修改类

2.1.2.3.2. 修改 sidebar.html

修改 resources/app/scripts/directives/sidebar/sidebar.html
改造流控规则这里,将55行附近dashboard.flowV1改为dashboard.flow

1
2
3
4
5
6
7
8
<!--<li ui-sref-active="active" ng-if="!entry.isGateway">
<a ui-sref="dashboard.flowV1({app: entry.app})">
<i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则</a>
</li>-->
<li ui-sref-active="active" ng-if="!entry.isGateway">
<a ui-sref="dashboard.flow({app: entry.app})">
<i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则</a>
</li>
2.1.2.3.3. 配置nacos

nacos中默认的流控规则配置为

  • groupId: SENTINEL_GROUP
  • dataId: {appName}-flow-rules ,比如应用名为 appA,则 dataId 为 appA-flow-rules

若要修改需要修改NacosConfigUtil中的GROUP_IDFLOW_DATA_ID_POSTFIX建议不要修改,要考虑客户端也要公用规则

NacosConfigUtil

这里其实相当于官方只给了一个非常简单的实现,甚至连自动配置都没有做,所以我们就得自己动手做成配置化了,我们总不能写死配置在应用里吧,这比硬编码还硬编码了。

而且sentinel-dashboard的配置信息我们也想配置在nacos中。

nacos的配置如下,注意namespace、group、dataId

rules

dataId为拼接上来的,{appName}-flow-rules

rInfo

rule的格式同FlowRuleEntity保持一致,因为程序中是直接JSON.parseArray处理的

具体可以见代码FlowRuleNacosProvider#getRules()

1
2
3
4
5
6
7
8
9
@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}

而converter为NacosConfig注册的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class NacosConfig {


@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}

//就是这个
@Bean
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}

@Bean
public ConfigService nacosConfigService() throws Exception {
return ConfigFactory.createConfigService("http://113.31.109.7:8848");
}
}

适当的改造 这里比较多的硬编码,没做配置,我们稍微修改

改造后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class NacosConfig {

@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}

@Bean
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}
//做了一个配置信息配置 弄成properties或yml
@Value("${sentinel.dashboard.datasource.nacos.server-addr}")
private String serverAddr="localhost:8848";
@Bean
public ConfigService nacosConfigService() throws Exception {
return ConfigFactory.createConfigService(serverAddr);
}
}
2.1.2.3.4. 启动sentinel-dashboard并配置

启动工程,查看流控规则,这里的流控规则加载是在点击流控规则菜单的时候加载,不用起客户端了

centinel-dashboard

  1. 新增一个看看
    sentinel
  2. 再看nacos中
    nacos
  3. 格式化后为:
    format
  4. 再看看一个集群的
    sentinel配置
    dashboard
    nacos格式化后
    format

3. 客户端改造

客户端原本是直接与dashboard服务端交互的,由于服务端已经直接和nacos交互了,因此客户端也改造成和Nacos交互。
这里也只处理push模式的情况,客户端通过注册监听器到nacos中的方式,nacos数据变化了通知客户端更新,这样就实现了dashboard修改数据最后客户端更新数据,且数据持久化在nacos中

3.1. pom

客户端不再与服务端直接交互,但是客户端首先要注册到dashboard中,因此还是需要sentinel-transport-simple-http依赖
,否则进入sentinel的时候啥也没有,看不到应用

加入nacos 的pom

1
2
3
4
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

3.2. 配置改造

由于nacos中保存的dataId和groupId在dashboard中已经定义好了规则,客户端也需要按照其规则执行,因此拷贝规则相关的逻辑
NacosConfigUtil

1
2
3
4
5
6
7
8
9
10
11
12
public final class NacosConfigUtil {
public static final String GROUP_ID = "SENTINEL_GROUP";
public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules";
public static final String PARAM_FLOW_DATA_ID_POSTFIX = "-param-rules";
public static final String CLUSTER_MAP_DATA_ID_POSTFIX = "-cluster-map";
public static final String CLIENT_CONFIG_DATA_ID_POSTFIX = "-cc-config";
public static final String SERVER_TRANSPORT_CONFIG_DATA_ID_POSTFIX = "-cs-transport-config";
public static final String SERVER_FLOW_CONFIG_DATA_ID_POSTFIX = "-cs-flow-config";
public static final String SERVER_NAMESPACE_SET_DATA_ID_POSTFIX = "-cs-namespace-set";

private NacosConfigUtil() {}
}

配置类,主要是替换原来硬编码加载的FlowRule,替换为从nacos中获取并注册监听

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
@Configuration
public class SentinelConfig implements InitializingBean {

/**
* 这里需要注入切面管理的类,
* @return
*/
@Bean
SentinelResourceAspect sentinelResourceAspect(){
return new SentinelResourceAspect();
}

@Value("${sentinel.datasource.nacos.server-addr}")
private String serverAddr="localhost:8848";

/**
* 流控规则,主要是在FlowRuleManager中写入规则
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {

/* 原本这里这些加载流控规则的就不要了
FlowRule flowRule = new FlowRule();
flowRule.setResource("hello");
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
flowRule.setCount(1);
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
//加载流控规则
FlowRuleManager.loadRules(Arrays.asList(flowRule));
*/
//从nacos中加载规则
loadRules();

}

private void loadRules() {
//从sentinel.properties中读取到工程名,
String projectName = ResourceBundle.getBundle("sentinel").getString("project.name");

ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(
serverAddr,
//默认的GROUP_ID,在dashboard中就定义好了,若要修改,需要两处都修改,因此要抽象的话最好把两边的nacos相关的公共部分再抽象出一个包来处理最为合理。此处先不提供修改规则的逻辑配置
NacosConfigUtil.GROUP_ID,
//默认的dataId规则,与dashboard服务端保持一致
projectName+NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
//json格式存的,需要json来取
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
}));
SentinelProperty<List<FlowRule>> sentinelProperty = flowRuleDataSource.getProperty();
//加载规则
FlowRuleManager.register2Property(sentinelProperty);
}
}

此处变化也好理解,从nacos配置中心去拿到配置,再解析就完了,同时注册到nacos的时候注册监听器,当nacos变化的时候会通知应用,从而应用修改自己的数据。

这里还做了一个配置,默认本地nacos

1
2
@Value("${sentinel.datasource.nacos.server-addr}")
private String serverAddr="localhost:8848";

可以启动sentinel-dashboard测试一下
1
Jmeter测试一下
2
修改资源
3
Jmeter测试一下
4

至此,就完成了sentinel持久化到nacos的整个部分

4. 总结

  1. sentinel-dashboard总给人感觉是一个半成品,控制面板都做的差不多了,但是没有对后续数据持久化做扩展,官方在test中其实也有做了实现,但是就是没有把对应的地方改完。
  2. 除此之外,还有硬编码在给出的测试类中,没做自动话配置,也就是说即使用了官方给出的类还是需要做扩展。
  3. 我理解对于大佬来说后续其实很简单了,毕竟主要的东西都在,但是不知为啥就是没有实现完,不让开箱即用。
  4. sentinel-datasource-nacos此类也是,已经接上了nacos了,但是呢,group、dataId又没有和dashboard这里保持一致,要用户手动拼接规则,传入数据,对于新使用的开发者来说,又需要探索好久吧。这里本可以直接按照相同的规则处理好的……
  5. 到了push模式了,其实sentinel-transport-simple-http这个模块已经可以不要了,因为两者其实已经不用通讯了,但是刚开始客户端的懒加载逻辑(即第一次流控的时候才注册到dashboard中)又没有按照解耦的来修改,这里其实修改成启动dashboard的时候直接从nacos中获取就可以解决了,但是后续也没有做。
    对此,最大的猜测还是,官方想让用商业化的云服务?