1
0
mirror of https://gitee.com/mirrors/Spring-Cloud-Alibaba.git synced 2021-06-26 13:25:11 +08:00

sentinel datasource refactor: including code refactor and update examples, docs

This commit is contained in:
fangjian0423 2018-11-22 19:18:40 +08:00
parent 5403fe3165
commit 10bb95c11a
30 changed files with 1758 additions and 1035 deletions

View File

@ -1 +1,257 @@
== Spring Cloud Alibaba Sentinel
### Sentinel介绍
随着微服务的流行服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
* *丰富的应用场景*Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
* *完备的实时监控*Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
* *广泛的开源生态*Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
* *完善的 SPI 扩展点*Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
### 如何使用Sentinel
如果要在您的项目中引入Sentinel使用group ID为 `org.springframework.cloud` 和artifact ID为 `spring-cloud-starter-alibaba-sentinel` 的starter。
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
```
下面这个例子就是一个最简单的使用Sentinel的例子:
```java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
@RestController
public class TestController {
@GetMapping(value = "/hello")
@SentinelResource("hello")
public String hello() {
return "Hello Sentinel";
}
}
```
@SentinelResource注解用来标识资源是否被限流、降级。上述例子上该注解的属性'hello'表示资源名。
@SentinelResource还提供了其它额外的属性如 `blockHandler``blockHandlerClass``fallback` 用于表示限流或降级的操作,更多内容可以参考 https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81[Sentinel注解支持]。
##### Sentinel 控制台
Sentinel 控制台提供一个轻量级的控制台,它提供机器发现、单机资源实时监控、集群资源汇总,以及规则管理的功能。您只需要对应用进行简单的配置,就可以使用这些功能。
*注意*: 集群资源汇总仅支持 500 台以下的应用集群,有大概 1 - 2 秒的延时。
.Sentinel Dashboard
image::https://github.com/alibaba/Sentinel/wiki/image/dashboard.png[]
开启该功能需要3个步骤
###### 获取控制台
您可以从 https://github.com/alibaba/Sentinel/releases[release 页面] 下载最新版本的控制台 jar 包。
您也可以从最新版本的源码自行构建 Sentinel 控制台:
* 下载 https://github.com/alibaba/Sentinel/tree/master/sentinel-dashboard[控制台] 工程
* 使用以下命令将代码打包成一个 fat jar: `mvn clean package`
###### 启动控制台
Sentinel 控制台是一个标准的SpringBoot应用以SpringBoot的方式运行jar包即可。
```shell
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
```
如若8080端口冲突可使用 `-Dserver.port=新端口` 进行设置。
#### 配置控制台信息
.application.yml
----
spring:
cloud:
sentinel:
transport:
port: 8719
dashboard: localhost:8080
----
这里的 `spring.cloud.sentinel.transport.port` 端口配置会在应用对应的机器上启动一个Http Server该Serve会与Sentinel控制台做交互。比如Sentinel控制台添加了1个限流规则会把规则数据push给这个Http Server接受Http Server再将规则注册到Sentinel中。
更多Sentinel控制台的使用及问题参考 https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0[Sentinel控制台]
### Feign支持
DOING
### RestTemplate支持
Spring Cloud Alibaba Sentinel支持对 `RestTemplate` 的服务调用使用Sentinel进行保护在构造 `RestTemplate` bean的时候需要加上 `@SentinelProtect` 注解。
```java
@Bean
@SentinelProtect(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() {
return new RestTemplate();
}
```
`@SentinelProtect` 注解的参数跟 `@SentinelResource` 一致,使用方式也一致。
限流的资源规则提供两种粒度:
* `schema://host:port/path`:协议、主机、端口和路径
* `schema://host:port`:协议、主机和端口
NOTE: 以 `https://www.taobao.com/test` 这个url为例。对应的资源名有两种粒度分别是 `https://www.taobao.com:80` 以及 `https://www.taobao.com:80/test`
### 动态数据源支持
*在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之前*要在Spring Cloud Alibaba Sentinel下使用动态数据源需要3个步骤
* 配置文件中定义数据源信息。比如使用文件:
.application.properties
----
spring.cloud.sentinel.datasource.type=file
spring.cloud.sentinel.datasource.recommendRefreshMs=3000
spring.cloud.sentinel.datasource.bufSize=4056196
spring.cloud.sentinel.datasource.charset=utf-8
spring.cloud.sentinel.datasource.converter=flowConverter
spring.cloud.sentinel.datasource.file=/Users/you/yourrule.json
----
* 创建一个Converter类实现 `com.alibaba.csp.sentinel.datasource.Converter` 接口,并且需要在 `ApplicationContext` 中有该类的一个Bean
```java
@Component("flowConverter")
public class JsonFlowRuleListParser implements Converter<String, List<FlowRule>> {
@Override
public List<FlowRule> convert(String source) {
return JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
});
}
}
```
这个Converter的bean name需要跟 `application.properties` 配置文件中的converter配置一致
* 在任意一个 Spring Bean 中定义一个被 `@SentinelDataSource` 注解修饰的 `ReadableDataSource` 属性
```java
@SentinelDataSource("spring.cloud.sentinel.datasource")
private ReadableDataSource dataSource;
```
`@SentinelDataSource` 注解的value属性表示数据源在 `application.properties` 配置文件中前缀。 该在例子中,前缀为 `spring.cloud.sentinel.datasource` 。
如果 `ApplicationContext` 中存在超过1个 `ReadableDataSource` bean那么不会加载这些 `ReadableDataSource` 中的任意一个。 只有在 `ApplicationContext` 存在一个 `ReadableDataSource` 的情况下才会生效。
如果数据源生效并且规则成功加载,控制台会打印类似如下信息:
```
[Sentinel Starter] load 3 flow rules
```
*在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之后*要在Spring Cloud Alibaba Sentinel下使用动态数据源只需要1个步骤
* 直接在 `application.properties` 配置文件中配置数据源信息即可
比如配置了4个数据源
```
spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json
spring.cloud.sentinel.datasource.ds2.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.ds2.nacos.dataId=sentinel
spring.cloud.sentinel.datasource.ds2.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds2.nacos.data-type=json
spring.cloud.sentinel.datasource.ds3.zk.path = /Sentinel-Demo/SYSTEM-CODE-DEMO-FLOW
spring.cloud.sentinel.datasource.ds3.zk.server-addr = localhost:2181
spring.cloud.sentinel.datasource.ds4.apollo.namespace-name = application
spring.cloud.sentinel.datasource.ds4.apollo.flow-rules-key = sentinel
spring.cloud.sentinel.datasource.ds4.apollo.default-flow-rule-value = test
```
这样配置方式参考了Spring Cloud Stream Binder的配置内部使用了 `TreeMap` 进行存储comparator为 `String.CASE_INSENSITIVE_ORDER` 。
NOTE: d1, ds2, ds3, ds4 是 `ReadableDataSource` 的名字,可随意编写。后面的 `file` `zk` `nacos` , `apollo` 就是对应具体的数据源。 它们后面的配置就是这些数据源各自的配置。
每种数据源都有两个共同的配置项: `data-type` 和 `converter-class` 。
`data-type` 配置项表示 `Converter`Spring Cloud Alibaba Sentinel默认提供两种内置的值分别是 `json` 和 `xml` (不填默认是json)。 如果不想使用内置的 `json` 或 `xml` 这两种 `Converter`,可以填写 `custom` 表示自定义 `Converter`,然后再配置 `converter-class` 配置项,该配置项需要写类的全路径名。
这两种内置的 `Converter` 只支持解析 json数组 或 xml数组。内部解析的时候会自动判断每个json对象或xml对象属于哪4种Sentinel规则(`FlowRule``DegradeRule``SystemRule``AuthorityRule`)。
比如10个规则数组里解析出5个限流规则和5个降级规则。 这种情况下该数据源不会注册,日志里页会进行警告。
如果10个规则里有9个限流规则1个解析报错了。这种情况下日志会警告有个规则格式错误另外9个限流规则会注册上去。
这里json或xml解析用的是 `jackson`。`ObjectMapper` 或 `XmlMapper` 使用默认的配置,遇到不认识的字段会解析报错。 因为不这样做的话限流 json 也会解析成 系统规则(系统规则所有的配置都有默认值),所以需要这样严格解析。
当然还有一种情况是json对象或xml对象可能会匹配上所有4种规则(比如json对象里只配了 `resource` 字段那么会匹配上4种规则),这种情况下日志里会警告,并且过滤掉这个对象。
用户使用这种配置的时候只需要填写正确的json或xml就行有任何不合理的信息都会在日志里打印出来。
如果数据源生效并且规则成功加载,控制台会打印类似如下信息:
```
[Sentinel Starter] DataSource ds1-sentinel-file-datasource load 3 DegradeRule
[Sentinel Starter] DataSource ds2-sentinel-nacos-datasource load 2 FlowRule
```
NOTE: 默认情况下xml格式是不支持的。需要添加 `jackson-dataformat-xml` 依赖后才会自动生效。
关于Sentinel动态数据源的实现原理参考 https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95[动态规则扩展]
### Endpoint支持
在使用Endpoint特性之前需要在 Maven 中添加 `spring-boot-starter-actuator` 依赖,并在配置中允许 Endpoints 的访问。
* Spring Boot 1.x 中添加配置 `management.security.enabled=false`。暴露的endpoint路径为 `/sentinel`
* Spring Boot 2.x 中添加配置 `management.endpoints.web.exposure.include=*`。暴露的endpoint路径为 `/actuator/sentinel`
### More
下表显示 Spring Cloud Alibaba Sentinel 的所有配置信息:
:frame: topbot
[width="60%",options="header"]
|====
^|配置项 ^|含义 ^|默认值
|`spring.cloud.sentinel.enabled`|Sentinel自动化配置是否生效|true
|`spring.cloud.sentinel.eager`|取消Sentinel控制台懒加载|false
|`spring.cloud.sentinel.charset`|metric文件字符集|UTF-8
|`spring.cloud.sentinel.transport.port`|应用与Sentinel控制台交互的端口应用本地会起一个该端口占用的HttpServer|8721
|`spring.cloud.sentinel.transport.dashboard`|Sentinel 控制台地址|
|`spring.cloud.sentinel.transport.heartbeatIntervalMs`|应用与Sentinel控制台的心跳间隔时间|
|`spring.cloud.sentinel.filter.order`|Servlet Filter的加载顺序。Starter内部会构造这个filter|Integer.MIN_VALUE
|`spring.cloud.sentinel.filter.spring.url-patterns`|数据类型是数组。表示Servlet Filter的url pattern集合|/*
|`spring.cloud.sentinel.metric.fileSingleSize`|Sentinel metric 单个文件的大小|
|`spring.cloud.sentinel.metric.fileTotalCount`|Sentinel metric 总文件数量|
|`spring.cloud.sentinel.servlet.blockPage`| 自定义的跳转 URL当请求被限流时会自动跳转至设定好的 URL |
|`spring.cloud.sentinel.flow.coldFactor`| https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8[冷启动因子] |3
|====

View File

@ -31,6 +31,24 @@
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>com.alibaba.csp</groupId>-->
<!--<artifactId>sentinel-datasource-nacos</artifactId>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>com.alibaba.csp</groupId>-->
<!--<artifactId>sentinel-datasource-zookeeper</artifactId>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>com.alibaba.csp</groupId>-->
<!--<artifactId>sentinel-datasource-apollo</artifactId>-->
<!--</dependency>-->
<!-- define in spring-boot-autoconfigure module -->
<!--<dependency>-->
<!--<groupId>com.fasterxml.jackson.dataformat</groupId>-->
<!--<artifactId>jackson-dataformat-xml</artifactId>-->
<!--</dependency>-->
</dependencies>
<build>

View File

@ -196,77 +196,33 @@ Sentinel 控制台支持实时监控查看,您可以通过 Sentinel 控制台
Sentinel 内部提供了[动态规则的扩展实现 ReadableDataSource](https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95#datasource-%E6%89%A9%E5%B1%95)。
Sentinel starter 整合了目前存在的几类 DataSource。只需要在配置文件中进行相关配置即可在 Spring 容器中自动注册 DataSource。
Sentinel starter 整合了目前存在的几类 ReadableDataSource。只需要在配置文件中进行相关配置即可在 Spring 容器中自动注册 DataSource。
比如要定义一个 `FileRefreshableDataSource`,配置如下:
比如要定义两个ReadableDataSource分别是 `FileRefreshableDataSource``NacosDataSource`,配置如下:
spring.cloud.sentinel.datasource.type=file
spring.cloud.sentinel.datasource.recommendRefreshMs=2000
spring.cloud.sentinel.datasource.bufSize=2048
spring.cloud.sentinel.datasource.charset=utf-8
spring.cloud.sentinel.datasource.converter=myParser
spring.cloud.sentinel.datasource.file=/Users/you/rule.json
```properties
spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json
spring.cloud.sentinel.datasource.ds1.file.data-type=json
然后使用`@SentinelDataSource` 注解修饰 DataSource 即可注入:
spring.cloud.sentinel.datasource.ds2.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.ds2.nacos.dataId=sentinel
spring.cloud.sentinel.datasource.ds2.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds2.nacos.data-type=json
```
@SentinelDataSource("spring.cloud.sentinel.datasource")
private ReadableDataSource dataSource;
`ds1``ds2` 表示ReadableDataSource的名称可随意编写。`ds1``ds2` 后面的 `file``nacos` 表示ReadableDataSource的类型。
`@SentinelDataSource` 注解的 value 属性可以不填。默认值就是 `spring.cloud.sentinel.datasource`
目前支持`file`, `nacos`, `zk`, `apollo` 这4种类型
`value` 属性代表配置前缀。示例中会去找 `spring.cloud.sentinel.datasource.xxx` 相关的配置
其中`nacos``zk``apollo`这3种类型的使用需要加上对应的依赖`sentinel-datasource-nacos`, `sentinel-datasource-zookeeper`, `sentinel-datasource-apollo`
`spring.cloud.sentinel.datasource.type` 就是对应的 DataSource 类型。
当ReadableDataSource加载规则数据成功的时候控制台会打印出相应的日志信息
`spring.cloud.sentinel.datasource.recommendRefreshMs` 里的 `recommendRefreshMs` 对应相关 DataSource 的属性。
```
[Sentinel Starter] DataSource ds1-sentinel-file-datasource load 3 DegradeRule
[Sentinel Starter] DataSource ds2-sentinel-nacos-datasource load 2 FlowRule
```
`spring.cloud.sentinel.datasource.converter`代表 `Converter` 在 Spring 容器里的 name。如果没找到会抛出异常。
type目前支持file, nacos, zk, apollo。其中nacoszkapollo的使用需要加上对应的依赖`sentinel-datasource-nacos`, `sentinel-datasource-zookeeper`, `sentinel-datasource-apollo`
### 自定义DataSource
自定义DataSource只需要两步。
1. 定义DataSource
public class CustomDataSource implements ReadableDataSource {
private String fieldA;
private String fieldB;
...
}
2. 装配DataSource。有两种方式处理。
* 直接构造DataSource
@Bean
public CustomDataSource customDataSource() {
CustomDataSource customDataSource =
new CustomDataSource();
customDataSource.setFieldA("valueA");
customDataSource.setFieldB("valueB");
...
return customDataSource;
}
* 在classpath:/META-INF/sentinel-datasource.properties中管理DataSource信息
custom = yourpackage.CustomDataSource
在application.properties中定义DataSource
spring.cloud.sentinel.datasource.type = custom
spring.cloud.sentinel.datasource.fieldA = valueA
spring.cloud.sentinel.datasource.fieldB = valueB
注意由于目前Sentinel的AbstractDataSource需要有个Converter作为构造函数中的参数并且它的子类的构造都是通过多个参数的构造函数构造的。
所以目前所有的Sentinel starter中的DataSource都是基于FactoryBean并且通过设置属性构造的。如果有这方面的需求需要再多加一个registerFactoryBean过程。
SentinelDataSourceRegistry.registerFactoryBean("custeom", CustomDataSourceFactoryBean.class);
如果自定义DataSource可以注入属性那么没有必要使用SentinelDataSourceRegistry注册FactoryBean。
## More
Sentinel 是一款功能强大的中间件,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。此 Demo 仅演示了 使用 Sentinel 作为限流工具的使用,更多 Sentinel 相关的信息,请参考 [Sentinel 项目](https://github.com/alibaba/Sentinel)。

View File

@ -71,7 +71,7 @@ Before we start the demo, let's learn how to connect Sentinel to a Spring Cloud
2. Start the application in IDE or by building a fatjar.
1. Start in IDE: Find main class `ServiceApplication`, and execute the main method.
2. Build a fatjarExecute command `mvn clean package` to build a fatjarand run command `java -jar sentinel-core-example.jar` to start the application.
2. Build a fatjarExecute command `mvn clean package` to build a fatjar, and run command `java -jar sentinel-core-example.jar` to start the application.
### Invoke Service
@ -86,7 +86,7 @@ The screenshot belows shows invoke success:
1. Open http://localhost:8080 in browser, and you can find a Sentinel-Example Application has been registered to the dashboard.
**Note: If you can't find your application in the dashboard, invoke a method that has been defined as a Sentinel Resourcefor Sentinel uses lazy load strategy.**
**Note: If you can't find your application in the dashboard, invoke a method that has been defined as a Sentinel Resource, for Sentinel uses lazy load strategy.**
<p align="center"><img src="https://cdn.nlark.com/lark/0/2018/png/54319/1532315951819-9ffd959e-0547-4f61-8f06-91374cfe7f21.png" width="1000" heigh='400' ></p>
@ -125,8 +125,8 @@ The screenshot belows shows invoke success:
2. When a custom resource is blocked by Sentinel, the default logic is throw BlockException.
If you want to customize your flow control logic, implement interface `SentinelExceptionHandler`, set @SentinelResource's blockHandler() and blockHandlerClass(). See the code below:
If you want to customize your flow control logic, implement interface `SentinelExceptionHandler`, set @SentinelResource's blockHandler() and blockHandlerClass(). See the code below:
@SentinelResource(value = "resource", blockHandler = "", blockHandlerClass = ExceptionUtil.class)
public String hello() {
return "Hello";
@ -171,71 +171,30 @@ Sentinel provide [ReadableDataSource](https://github.com/alibaba/Sentinel/blob/m
Sentinel starter integrated 4 DataSources provided by Sentinel. It will be register into Spring Context if you write some configs in `application.properties`.
If you want to define FileRefreshableDataSource:
If you want to define `FileRefreshableDataSource` and `NacosDataSource`, see the code below:
spring.cloud.sentinel.datasource.type=file
spring.cloud.sentinel.datasource.recommendRefreshMs=2000
spring.cloud.sentinel.datasource.bufSize=2048
spring.cloud.sentinel.datasource.charset=utf-8
spring.cloud.sentinel.datasource.converter=myParser
spring.cloud.sentinel.datasource.file=/Users/you/rule.json
then use `@SentinelDataSource` to annotate DataSource:
@SentinelDataSource("spring.cloud.sentinel.datasource")
private ReadableDataSource dataSource;
The value() of `@SentinelDataSource` is not required, it means the prefix of configuration. Default value is `spring.cloud.sentinel.datasource`.
```properties
spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json
spring.cloud.sentinel.datasource.ds1.file.data-type=json
spring.cloud.sentinel.datasource.type means the type of DataSource.
spring.cloud.sentinel.datasource.ds2.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.ds2.nacos.dataId=sentinel
spring.cloud.sentinel.datasource.ds2.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds2.nacos.data-type=json
```
spring.cloud.sentinel.datasource.recommendRefreshMs means the recommendRefreshMs property of specified DataSource.
`ds1` and `ds2` means the name of ReadableDataSource, you can write whatever you want. The `file` and `nacos` after name `ds1` and `ds2` means the type of ReadableDataSource.
spring.cloud.sentinel.datasource.converter means the name of spring bean that type is Converter. If the bean is not exists, will throw exception.
Now datasource type support 4 categories: file, nacos, zk, apollo. If you want to using nacos, zk or apollo, you should add `sentinel-datasource-nacos`, `sentinel-datasource-zookeeper` or `sentinel-datasource-apollo` dependency.
Now ReadableDataSource type support 4 categories: `file`, `nacos`, `zk` and `apollo`.
### User-defined DataSource
If you want to use `nacos`, `zk` or `apollo` ReadableDataSource, you could add `sentinel-datasource-nacos`, `sentinel-datasource-zookeeper` or `sentinel-datasource-apollo` dependency.
User-defined DataSource need 2 steps.
When ReadableDataSource load rule data successfully, console will print some logs:
1. Define DataSource
public class CustomDataSource implements ReadableDataSource {
private String fieldA;
private String fieldB;
...
}
2. Assemble DataSource. There are 2 ways to do this.
* Construct DataSource directly
@Bean
public CustomDataSource customDataSource() {
CustomDataSource customDataSource = new CustomDataSource();
customDataSource.setFieldA("valueA");
customDataSource.setFieldB("valueB");
...
return customDataSource;
}
* define DataSource metadata in `classpath:/META-INF/sentinel-datasource.properties`
custom = yourpackage.CustomDataSource
define configuration in `application.properties`
spring.cloud.sentinel.datasource.type = custom
spring.cloud.sentinel.datasource.fieldA = valueA
spring.cloud.sentinel.datasource.fieldB = valueB
Note: The AbstractDataSource of Sentinel need a Converter as a constructor param and the subclass of AbstractDataSource was construct by multi-param constructor.
Now All DataSources in starter was construct by FactoryBean. If you want to do it in this way, you should register FactoryBean by SentinelDataSourceRegistry.
SentinelDataSourceRegistry.registerFactoryBean("custeom", CustomDataSourceFactoryBean.class);
It is no need to using SentinelDataSourceRegistry to register FactoryBean if your User-defined DataSource can inject fields.
```
[Sentinel Starter] DataSource ds1-sentinel-file-datasource load 3 DegradeRule
[Sentinel Starter] DataSource ds2-sentinel-nacos-datasource load 2 FlowRule
```
## More
For more information about Sentinel, see [Sentinel Project](https://github.com/alibaba/Sentinel).

View File

@ -10,7 +10,7 @@ import com.alibaba.fastjson.TypeReference;
/**
* @author fangjian
*/
public class JsonFlowRuleListParser implements Converter<String, List<FlowRule>> {
public class JsonFlowRuleListConverter implements Converter<String, List<FlowRule>> {
@Override
public List<FlowRule> convert(String source) {
return JSON.parseObject(source, new TypeReference<List<FlowRule>>() {

View File

@ -27,7 +27,7 @@ public class ServiceApplication {
@Bean
public Converter myParser() {
return new JsonFlowRuleListParser();
return new JsonFlowRuleListConverter();
}
public static void main(String[] args) {

View File

@ -1,14 +1,16 @@
spring.application.name=sentinel-example
server.port=18083
management.security.enabled=false
spring.cloud.sentinel.transport.port=8721
spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.eager=true
spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json
spring.cloud.sentinel.datasource.ds1.file.data-type=json
spring.cloud.sentinel.datasource.type=file
spring.cloud.sentinel.datasource.recommendRefreshMs=3000
spring.cloud.sentinel.datasource.bufSize=4056196
spring.cloud.sentinel.datasource.charset=utf-8
spring.cloud.sentinel.datasource.converter=myParser
spring.cloud.sentinel.datasource.file=/Users/you/rule.json
#spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json
#spring.cloud.sentinel.datasource.ds1.file.data-type=custom
#spring.cloud.sentinel.datasource.ds1.file.converter-class=org.springframework.cloud.alibaba.cloud.examples.JsonFlowRuleListConverter
spring.cloud.sentinel.datasource.ds2.file.file=classpath: degraderule.json
spring.cloud.sentinel.datasource.ds2.file.data-type=json

View File

@ -0,0 +1,16 @@
[
{
"resource": "abc0",
"count": 20.0,
"grade": 0,
"passCount": 0,
"timeWindow": 10
},
{
"resource": "abc1",
"count": 15.0,
"grade": 0,
"passCount": 0,
"timeWindow": 10
}
]

View File

@ -0,0 +1,26 @@
[
{
"resource": "resource",
"controlBehavior": 0,
"count": 1,
"grade": 1,
"limitApp": "default",
"strategy": 0
},
{
"resource": "p",
"controlBehavior": 0,
"count": 1,
"grade": 1,
"limitApp": "default",
"strategy": 0
},
{
"resource": "abc",
"controlBehavior": 0,
"count": 1,
"grade": 1,
"limitApp": "default",
"strategy": 0
}
]

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Rules>
<FlowRule>
<resource>resource</resource>
<controlBehavior>0</controlBehavior>
<count>1</count>
<grade>1</grade>
<limitApp>default</limitApp>
<strategy>0</strategy>
</FlowRule>
<FlowRule>
<resource>test</resource>
<controlBehavior>0</controlBehavior>
<count>1</count>
<grade>1</grade>
<limitApp>default</limitApp>
<strategy>0</strategy>
</FlowRule>
</Rules>

View File

@ -41,8 +41,27 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<scope>provided</scope>
</dependency>
<!--spring boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>

View File

@ -1,152 +0,0 @@
/*
* Copyright (C) 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.sentinel.datasource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import static org.springframework.core.io.support.ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
/**
* {@link ReadableDataSource} Loader
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class DataSourceLoader {
private static final Logger logger = LoggerFactory.getLogger(DataSourceLoader.class);
private final static String PROPERTIES_RESOURCE_LOCATION = "META-INF/sentinel-datasource.properties";
private final static String ALL_PROPERTIES_RESOURCES_LOCATION = CLASSPATH_ALL_URL_PREFIX
+ PROPERTIES_RESOURCE_LOCATION;
private final static ConcurrentMap<String, Class<? extends ReadableDataSource>> dataSourceClassesCache
= new ConcurrentHashMap<String, Class<? extends ReadableDataSource>>(
4);
static void loadAllDataSourceClassesCache() {
Map<String, Class<? extends ReadableDataSource>> dataSourceClassesMap = loadAllDataSourceClassesCache(
ALL_PROPERTIES_RESOURCES_LOCATION);
dataSourceClassesCache.putAll(dataSourceClassesMap);
}
static Map<String, Class<? extends ReadableDataSource>> loadAllDataSourceClassesCache(
String resourcesLocation) {
Map<String, Class<? extends ReadableDataSource>> dataSourcesMap
= new HashMap<String, Class<? extends ReadableDataSource>>(
4);
ClassLoader classLoader = DataSourceLoader.class.getClassLoader();
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
Resource[] resources = resolver.getResources(resourcesLocation);
for (Resource resource : resources) {
if (resource.exists()) {
Properties properties = PropertiesLoaderUtils
.loadProperties(resource);
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String type = (String)entry.getKey();
String className = (String)entry.getValue();
if (!ClassUtils.isPresent(className, classLoader)) {
if (logger.isDebugEnabled()) {
logger.debug(
"Sentinel DataSource implementation [ type : "
+ type + ": , class : " + className
+ " , url : " + resource.getURL()
+ "] was not present in current classpath , "
+ "thus loading will be ignored , please add dependency if required !");
}
continue;
}
Assert.isTrue(!dataSourcesMap.containsKey(type),
"The duplicated type[" + type
+ "] of SentinelDataSource were found in "
+ "resource [" + resource.getURL() + "]");
Class<?> dataSourceClass = ClassUtils.resolveClassName(className,
classLoader);
Assert.isAssignable(ReadableDataSource.class, dataSourceClass);
dataSourcesMap.put(type,
(Class<? extends ReadableDataSource>)dataSourceClass);
if (logger.isDebugEnabled()) {
logger.debug("Sentinel DataSource implementation [ type : "
+ type + ": , class : " + className
+ "] was loaded.");
}
}
}
}
} catch (IOException e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
}
return dataSourcesMap;
}
public static Class<? extends ReadableDataSource> loadClass(String type)
throws IllegalArgumentException {
Class<? extends ReadableDataSource> dataSourceClass = dataSourceClassesCache.get(type);
if (dataSourceClass == null) {
if (dataSourceClassesCache.isEmpty()) {
loadAllDataSourceClassesCache();
dataSourceClass = dataSourceClassesCache.get(type);
}
}
if (dataSourceClass == null) {
throw new IllegalArgumentException(
"Sentinel DataSource implementation [ type : " + type
+ " ] can't be found!");
}
return dataSourceClass;
}
}

View File

@ -1,278 +0,0 @@
/*
* Copyright (C) 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.sentinel.datasource;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.alibaba.sentinel.datasource.annotation.SentinelDataSource;
import org.springframework.cloud.alibaba.sentinel.datasource.util.PropertySourcesUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import static org.springframework.core.annotation.AnnotationUtils.getAnnotation;
/**
* {@link SentinelDataSource @SentinelDataSource} Post Processor
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @see ReadableDataSource
* @see SentinelDataSource
*/
public class SentinelDataSourcePostProcessor
extends InstantiationAwareBeanPostProcessorAdapter
implements MergedBeanDefinitionPostProcessor {
private static final Logger logger = LoggerFactory
.getLogger(SentinelDataSourcePostProcessor.class);
@Autowired
private ApplicationContext applicationContext;
@Autowired
private ConfigurableEnvironment environment;
private final Map<String, List<SentinelDataSourceField>> dataSourceFieldCache = new ConcurrentHashMap<>(
64);
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition,
Class<?> beanType, final String beanName) {
// find all fields using by @SentinelDataSource annotation
ReflectionUtils.doWithFields(beanType, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field)
throws IllegalArgumentException, IllegalAccessException {
SentinelDataSource annotation = getAnnotation(field,
SentinelDataSource.class);
if (annotation != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isWarnEnabled()) {
logger.warn(
"@SentinelDataSource annotation is not supported on static fields: "
+ field);
}
return;
}
if (dataSourceFieldCache.containsKey(beanName)) {
dataSourceFieldCache.get(beanName)
.add(new SentinelDataSourceField(annotation, field));
} else {
List<SentinelDataSourceField> list = new ArrayList<>();
list.add(new SentinelDataSourceField(annotation, field));
dataSourceFieldCache.put(beanName, list);
}
}
}
});
}
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs,
PropertyDescriptor[] pds, Object bean, String beanName)
throws BeanCreationException {
if (dataSourceFieldCache.containsKey(beanName)) {
List<SentinelDataSourceField> sentinelDataSourceFields = dataSourceFieldCache
.get(beanName);
for (SentinelDataSourceField sentinelDataSourceField : sentinelDataSourceFields) {
try {
// construct DataSource field annotated by @SentinelDataSource
Field field = sentinelDataSourceField.getField();
ReflectionUtils.makeAccessible(field);
String dataSourceBeanName = constructDataSource(
sentinelDataSourceField.getSentinelDataSource());
field.set(bean, applicationContext.getBean(dataSourceBeanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return pvs;
}
private String constructDataSource(SentinelDataSource annotation) {
String prefix = annotation.value();
if (StringUtils.isEmpty(prefix)) {
prefix = SentinelDataSourceConstants.PROPERTY_DATASOURCE_PREFIX;
}
Map<String, Object> propertyMap = PropertySourcesUtils
.getSubProperties(environment.getPropertySources(), prefix);
String alias = propertyMap.get("type").toString();
Class dataSourceClass = DataSourceLoader.loadClass(alias);
String beanName = StringUtils.isEmpty(annotation.name())
? StringUtils.uncapitalize(dataSourceClass.getSimpleName()) + "_" + prefix
: annotation.name();
if (applicationContext.containsBean(beanName)) {
return beanName;
}
Class targetClass = null;
// if alias exists in SentinelDataSourceRegistry, wired properties into
// FactoryBean
if (SentinelDataSourceRegistry.checkFactoryBean(alias)) {
targetClass = SentinelDataSourceRegistry.getFactoryBean(alias);
} else {
// if alias not exists in SentinelDataSourceRegistry, wired properties into
// raw class
targetClass = dataSourceClass;
}
registerDataSource(beanName, targetClass, propertyMap);
return beanName;
}
private void registerDataSource(String beanName, Class targetClass,
Map<String, Object> propertyMap) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(targetClass);
for (String propertyName : propertyMap.keySet()) {
Field field = ReflectionUtils.findField(targetClass, propertyName);
if (field != null) {
if (field.getType().isAssignableFrom(Converter.class)) {
// Converter get from ApplicationContext
builder.addPropertyReference(propertyName,
propertyMap.get(propertyName).toString());
} else {
// wired properties
builder.addPropertyValue(propertyName, propertyMap.get(propertyName));
}
}
}
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)applicationContext
.getAutowireCapableBeanFactory();
beanFactory.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
@EventListener(classes = ApplicationReadyEvent.class)
public void appStartedListener(ApplicationReadyEvent event) throws Exception {
logger.info("[Sentinel Starter] Start to find ReadableDataSource");
Map<String, ReadableDataSource> dataSourceMap = event.getApplicationContext().getBeansOfType(
ReadableDataSource.class);
if (dataSourceMap.size() == 1) {
logger.info("[Sentinel Starter] There exists only one ReadableDataSource named {}, start to load rules",
dataSourceMap.keySet().iterator().next());
ReadableDataSource dataSource = dataSourceMap.values().iterator().next();
Object ruleConfig = dataSource.loadConfig();
SentinelProperty sentinelProperty = dataSource.getProperty();
Integer rulesNum;
if ((rulesNum = checkRuleType(ruleConfig, FlowRule.class)) > 0) {
FlowRuleManager.register2Property(sentinelProperty);
logger.info("[Sentinel Starter] load {} flow rules", rulesNum);
}
if ((rulesNum = checkRuleType(ruleConfig, DegradeRule.class)) > 0) {
DegradeRuleManager.register2Property(sentinelProperty);
logger.info("[Sentinel Starter] load {} degrade rules", rulesNum);
}
if ((rulesNum = checkRuleType(ruleConfig, SystemRule.class)) > 0) {
SystemRuleManager.register2Property(sentinelProperty);
logger.info("[Sentinel Starter] load {} system rules", rulesNum);
}
if ((rulesNum = checkRuleType(ruleConfig, AuthorityRule.class)) > 0) {
AuthorityRuleManager.register2Property(sentinelProperty);
logger.info("[Sentinel Starter] load {} authority rules", rulesNum);
}
} else if (dataSourceMap.size() > 1) {
logger.warn(
"[Sentinel Starter] There exists more than one ReadableDataSource, can not choose which one to load");
} else {
logger.warn(
"[Sentinel Starter] No ReadableDataSource exists");
}
}
private Integer checkRuleType(Object ruleConfig, Class type) {
if (ruleConfig.getClass() == type) {
return 1;
} else if (ruleConfig instanceof List) {
List ruleList = (List)ruleConfig;
List checkList = new ArrayList();
for (Object rule : ruleList) {
if (rule.getClass() == type) {
checkList.add(rule);
}
}
if (ruleList.size() == checkList.size()) {
return ruleList.size();
}
}
return -1;
}
class SentinelDataSourceField {
private SentinelDataSource sentinelDataSource;
private Field field;
public SentinelDataSourceField(SentinelDataSource sentinelDataSource,
Field field) {
this.sentinelDataSource = sentinelDataSource;
this.field = field;
}
public SentinelDataSource getSentinelDataSource() {
return sentinelDataSource;
}
public void setSentinelDataSource(SentinelDataSource sentinelDataSource) {
this.sentinelDataSource = sentinelDataSource;
}
public Field getField() {
return field;
}
public void setField(Field field) {
this.field = field;
}
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (C) 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.sentinel.datasource;
import java.util.HashMap;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.ApolloDataSourceFactoryBean;
import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.FileRefreshableDataSourceFactoryBean;
import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.NacosDataSourceFactoryBean;
import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.ZookeeperDataSourceFactoryBean;
/**
* Registry to save DataSource FactoryBean
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @see ReadableDataSource
* @see FileRefreshableDataSourceFactoryBean
* @see ZookeeperDataSourceFactoryBean
* @see NacosDataSourceFactoryBean
* @see ApolloDataSourceFactoryBean
*/
public class SentinelDataSourceRegistry {
private static HashMap<String, Class<? extends FactoryBean>> cache = new HashMap<>(
32);
static {
SentinelDataSourceRegistry.registerFactoryBean("file",
FileRefreshableDataSourceFactoryBean.class);
SentinelDataSourceRegistry.registerFactoryBean("zk",
ZookeeperDataSourceFactoryBean.class);
SentinelDataSourceRegistry.registerFactoryBean("nacos",
NacosDataSourceFactoryBean.class);
SentinelDataSourceRegistry.registerFactoryBean("apollo",
ApolloDataSourceFactoryBean.class);
}
public static synchronized void registerFactoryBean(String alias,
Class<? extends FactoryBean> clazz) {
cache.put(alias, clazz);
}
public static Class<? extends FactoryBean> getFactoryBean(String alias) {
return cache.get(alias);
}
public static boolean checkFactoryBean(String alias) {
return cache.containsKey(alias);
}
}

View File

@ -1,50 +0,0 @@
/*
* Copyright (C) 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.sentinel.datasource.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import org.springframework.core.annotation.AliasFor;
/**
* An annotation to inject {@link ReadableDataSource} instance
* into a Spring Bean. The Properties of DataSource bean get from config files with
* specific prefix.
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @see ReadableDataSource
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SentinelDataSource {
@AliasFor("prefix")
String value() default "";
@AliasFor("value")
String prefix() default "";
String name() default ""; // spring bean name
}

View File

@ -0,0 +1,38 @@
package org.springframework.cloud.alibaba.sentinel.datasource.config;
/**
* Abstract class Using by {@link DataSourcePropertiesConfiguration}
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class AbstractDataSourceProperties {
private String dataType = "json";
private String converterClass;
private final String factoryBeanName;
public AbstractDataSourceProperties(String factoryBeanName) {
this.factoryBeanName = factoryBeanName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public String getConverterClass() {
return converterClass;
}
public void setConverterClass(String converterClass) {
this.converterClass = converterClass;
}
public String getFactoryBeanName() {
return factoryBeanName;
}
}

View File

@ -0,0 +1,44 @@
package org.springframework.cloud.alibaba.sentinel.datasource.config;
import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.ApolloDataSourceFactoryBean;
/**
* Apollo Properties class Using by {@link DataSourcePropertiesConfiguration} and
* {@link ApolloDataSourceFactoryBean}
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class ApolloDataSourceProperties extends AbstractDataSourceProperties {
private String namespaceName;
private String flowRulesKey;
private String defaultFlowRuleValue;
public ApolloDataSourceProperties() {
super(ApolloDataSourceFactoryBean.class.getName());
}
public String getNamespaceName() {
return namespaceName;
}
public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}
public String getFlowRulesKey() {
return flowRulesKey;
}
public void setFlowRulesKey(String flowRulesKey) {
this.flowRulesKey = flowRulesKey;
}
public String getDefaultFlowRuleValue() {
return defaultFlowRuleValue;
}
public void setDefaultFlowRuleValue(String defaultFlowRuleValue) {
this.defaultFlowRuleValue = defaultFlowRuleValue;
}
}

View File

@ -0,0 +1,75 @@
package org.springframework.cloud.alibaba.sentinel.datasource.config;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.springframework.util.ObjectUtils;
/**
* Using By ConfigurationProperties.
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @see NacosDataSourceProperties
* @see ApolloDataSourceProperties
* @see ZookeeperDataSourceProperties
* @see FileDataSourceProperties
*/
public class DataSourcePropertiesConfiguration {
private FileDataSourceProperties file;
private NacosDataSourceProperties nacos;
private ZookeeperDataSourceProperties zk;
private ApolloDataSourceProperties apollo;
public FileDataSourceProperties getFile() {
return file;
}
public void setFile(FileDataSourceProperties file) {
this.file = file;
}
public NacosDataSourceProperties getNacos() {
return nacos;
}
public void setNacos(NacosDataSourceProperties nacos) {
this.nacos = nacos;
}
public ZookeeperDataSourceProperties getZk() {
return zk;
}
public void setZk(ZookeeperDataSourceProperties zk) {
this.zk = zk;
}
public ApolloDataSourceProperties getApollo() {
return apollo;
}
public void setApollo(ApolloDataSourceProperties apollo) {
this.apollo = apollo;
}
public List<String> getInvalidField() {
List<String> fieldList = new ArrayList<>();
for (Field field : this.getClass().getDeclaredFields()) {
try {
if (!ObjectUtils.isEmpty(field.get(this))) {
fieldList.add(field.getName());
}
}
catch (IllegalAccessException e) {
// won't happen
}
}
return fieldList;
}
}

View File

@ -0,0 +1,53 @@
package org.springframework.cloud.alibaba.sentinel.datasource.config;
import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.FileRefreshableDataSourceFactoryBean;
/**
* File Properties class Using by {@link DataSourcePropertiesConfiguration} and
* {@link FileRefreshableDataSourceFactoryBean}
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class FileDataSourceProperties extends AbstractDataSourceProperties {
private String file;
private String charset = "utf-8";
private long recommendRefreshMs = 3000L;
private int bufSize = 1024 * 1024;
public FileDataSourceProperties() {
super(FileRefreshableDataSourceFactoryBean.class.getName());
}
public String getFile() {
return file;
}
public void setFile(String file) {
this.file = file;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public long getRecommendRefreshMs() {
return recommendRefreshMs;
}
public void setRecommendRefreshMs(long recommendRefreshMs) {
this.recommendRefreshMs = recommendRefreshMs;
}
public int getBufSize() {
return bufSize;
}
public void setBufSize(int bufSize) {
this.bufSize = bufSize;
}
}

View File

@ -0,0 +1,44 @@
package org.springframework.cloud.alibaba.sentinel.datasource.config;
import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.NacosDataSourceFactoryBean;
/**
* Nacos Properties class Using by {@link DataSourcePropertiesConfiguration} and
* {@link NacosDataSourceFactoryBean}
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class NacosDataSourceProperties extends AbstractDataSourceProperties {
private String serverAddr;
private String groupId;
private String dataId;
public NacosDataSourceProperties() {
super(NacosDataSourceFactoryBean.class.getName());
}
public String getServerAddr() {
return serverAddr;
}
public void setServerAddr(String serverAddr) {
this.serverAddr = serverAddr;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getDataId() {
return dataId;
}
public void setDataId(String dataId) {
this.dataId = dataId;
}
}

View File

@ -0,0 +1,56 @@
package org.springframework.cloud.alibaba.sentinel.datasource.config;
import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.ZookeeperDataSourceFactoryBean;
/**
* Zookeeper Properties class Using by {@link DataSourcePropertiesConfiguration} and
* {@link ZookeeperDataSourceFactoryBean}
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class ZookeeperDataSourceProperties extends AbstractDataSourceProperties {
public ZookeeperDataSourceProperties() {
super(ZookeeperDataSourceFactoryBean.class.getName());
}
private String serverAddr;
private String path;
private String groupId;
private String dataId;
public String getServerAddr() {
return serverAddr;
}
public void setServerAddr(String serverAddr) {
this.serverAddr = serverAddr;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getDataId() {
return dataId;
}
public void setDataId(String dataId) {
this.dataId = dataId;
}
}

View File

@ -0,0 +1,161 @@
package org.springframework.cloud.alibaba.sentinel.datasource.converter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.slots.block.AbstractRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Convert sentinel rules for json array Using strict mode to parse json
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @see FlowRule
* @see DegradeRule
* @see SystemRule
* @see AuthorityRule
* @see ObjectMapper
*/
public class JsonConverter implements Converter<String, List<AbstractRule>> {
private static final Logger logger = LoggerFactory.getLogger(JsonConverter.class);
private final ObjectMapper objectMapper;
public JsonConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public List<AbstractRule> convert(String source) {
List<AbstractRule> ruleList = new ArrayList<>();
if (StringUtils.isEmpty(source)) {
logger.info(
"Sentinel JsonConverter can not convert rules because source is empty");
return ruleList;
}
try {
List jsonArray = objectMapper.readValue(source,
new TypeReference<List<HashMap>>() {
});
for (Object obj : jsonArray) {
String itemJson = null;
try {
itemJson = objectMapper.writeValueAsString(obj);
}
catch (JsonProcessingException e) {
// won't be happen
}
List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemJson),
convertDegradeRule(itemJson), convertSystemRule(itemJson),
convertAuthorityRule(itemJson));
List<AbstractRule> convertRuleList = new ArrayList<>();
for (AbstractRule rule : rules) {
if (!ObjectUtils.isEmpty(rule)) {
convertRuleList.add(rule);
}
}
if (convertRuleList.size() == 0) {
logger.warn(
"Sentinel JsonConverter can not convert {} to any rules, ignore",
itemJson);
}
else if (convertRuleList.size() > 1) {
logger.warn(
"Sentinel JsonConverter convert {} and match multi rules, ignore",
itemJson);
}
else {
ruleList.add(convertRuleList.get(0));
}
}
if (jsonArray.size() != ruleList.size()) {
logger.warn(
"Sentinel JsonConverter Source list size is not equals to Target List, maybe a "
+ "part of json is invalid. Source List: " + jsonArray
+ ", Target List: " + ruleList);
}
}
catch (Exception e) {
logger.error("Sentinel JsonConverter convert error: " + e.getMessage());
throw new RuntimeException(
"Sentinel JsonConverter convert error: " + e.getMessage(), e);
}
logger.info("Sentinel JsonConverter convert {} rules: {}", ruleList.size(),
ruleList);
return ruleList;
}
private FlowRule convertFlowRule(String json) {
try {
FlowRule rule = objectMapper.readValue(json, FlowRule.class);
if (FlowRuleManager.isValidRule(rule)) {
return rule;
}
}
catch (Exception e) {
// ignore
}
return null;
}
private DegradeRule convertDegradeRule(String json) {
try {
DegradeRule rule = objectMapper.readValue(json, DegradeRule.class);
if (DegradeRuleManager.isValidRule(rule)) {
return rule;
}
}
catch (Exception e) {
// ignore
}
return null;
}
private SystemRule convertSystemRule(String json) {
SystemRule rule = null;
try {
rule = objectMapper.readValue(json, SystemRule.class);
}
catch (Exception e) {
// ignore
}
return rule;
}
private AuthorityRule convertAuthorityRule(String json) {
AuthorityRule rule = null;
try {
rule = objectMapper.readValue(json, AuthorityRule.class);
}
catch (Exception e) {
// ignore
}
return rule;
}
}

View File

@ -0,0 +1,160 @@
package org.springframework.cloud.alibaba.sentinel.datasource.converter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.slots.block.AbstractRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
/**
* Convert sentinel rules for xml array Using strict mode to parse xml
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @see FlowRule
* @see DegradeRule
* @see SystemRule
* @see AuthorityRule
* @see XmlMapper
*/
public class XmlConverter implements Converter<String, List<AbstractRule>> {
private static final Logger logger = LoggerFactory.getLogger(XmlConverter.class);
private final XmlMapper xmlMapper;
public XmlConverter(XmlMapper xmlMapper) {
this.xmlMapper = xmlMapper;
}
@Override
public List<AbstractRule> convert(String source) {
List<AbstractRule> ruleList = new ArrayList<>();
if (StringUtils.isEmpty(source)) {
logger.info(
"Sentinel XmlConverter can not convert rules because source is empty");
return ruleList;
}
try {
List xmlArray = xmlMapper.readValue(source,
new TypeReference<List<HashMap>>() {
});
for (Object obj : xmlArray) {
String itemXml = null;
try {
itemXml = xmlMapper.writeValueAsString(obj);
}
catch (JsonProcessingException e) {
// won't be happen
}
List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemXml),
convertDegradeRule(itemXml), convertSystemRule(itemXml),
convertAuthorityRule(itemXml));
List<AbstractRule> convertRuleList = new ArrayList<>();
for (AbstractRule rule : rules) {
if (!ObjectUtils.isEmpty(rule)) {
convertRuleList.add(rule);
}
}
if (convertRuleList.size() == 0) {
logger.warn(
"Sentinel XmlConverter can not convert {} to any rules, ignore",
itemXml);
}
else if (convertRuleList.size() > 1) {
logger.warn(
"Sentinel XmlConverter convert {} and match multi rules, ignore",
itemXml);
}
else {
ruleList.add(convertRuleList.get(0));
}
}
if (xmlArray.size() != ruleList.size()) {
logger.warn(
"Sentinel XmlConverter Source list size is not equals to Target List, maybe a "
+ "part of xml is invalid. Source List: " + xmlArray
+ ", Target List: " + ruleList);
}
}
catch (Exception e) {
logger.error("Sentinel XmlConverter convert error: " + e.getMessage());
throw new RuntimeException(
"Sentinel XmlConverter convert error: " + e.getMessage(), e);
}
logger.info("Sentinel XmlConverter convert {} rules: {}", ruleList.size(),
ruleList);
return ruleList;
}
private FlowRule convertFlowRule(String xml) {
try {
FlowRule rule = xmlMapper.readValue(xml, FlowRule.class);
if (FlowRuleManager.isValidRule(rule)) {
return rule;
}
}
catch (Exception e) {
// ignore
}
return null;
}
private DegradeRule convertDegradeRule(String xml) {
try {
DegradeRule rule = xmlMapper.readValue(xml, DegradeRule.class);
if (DegradeRuleManager.isValidRule(rule)) {
return rule;
}
}
catch (Exception e) {
// ignore
}
return null;
}
private SystemRule convertSystemRule(String xml) {
SystemRule rule = null;
try {
rule = xmlMapper.readValue(xml, SystemRule.class);
}
catch (Exception e) {
// ignore
}
return rule;
}
private AuthorityRule convertAuthorityRule(String xml) {
AuthorityRule rule = null;
try {
rule = xmlMapper.readValue(xml, AuthorityRule.class);
}
catch (Exception e) {
// ignore
}
return rule;
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright (C) 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.sentinel.datasource.util;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
/**
* {@link PropertySources} Utilities
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class PropertySourcesUtils {
/**
* Get Sub {@link Properties}
*
* @param propertySources {@link PropertySource} Iterable
* @param prefix the prefix of property name
* @return Map
* @see Properties
*/
public static Map<String, Object> getSubProperties(Iterable<PropertySource<?>> propertySources, String prefix) {
Map<String, Object> subProperties = new LinkedHashMap<String, Object>();
String normalizedPrefix = normalizePrefix(prefix);
for (PropertySource<?> source : propertySources) {
if (source instanceof EnumerablePropertySource) {
for (String name : ((EnumerablePropertySource<?>)source).getPropertyNames()) {
if (!subProperties.containsKey(name) && name.startsWith(normalizedPrefix)) {
String subName = name.substring(normalizedPrefix.length());
if (!subProperties.containsKey(subName)) { // take first one
Object value = source.getProperty(name);
subProperties.put(subName, value);
}
}
}
}
}
return subProperties;
}
/**
* Normalize the prefix
*
* @param prefix the prefix
* @return the prefix
*/
public static String normalizePrefix(String prefix) {
return prefix.endsWith(".") ? prefix : prefix + ".";
}
}

View File

@ -40,6 +40,12 @@
<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<scope>provided</scope>
</dependency>
<!--spring boot-->
<dependency>

View File

@ -17,256 +17,285 @@
package org.springframework.cloud.alibaba.sentinel;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePropertiesConfiguration;
import org.springframework.core.Ordered;
import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
/**
* @author xiaojing
* @author hengyunabc
* @author jiashuai.xie
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
@ConfigurationProperties(prefix = SentinelConstants.PROPERTY_PREFIX)
public class SentinelProperties {
/**
* 是否提前初始化心跳连接
*/
private boolean eager = false;
/**
* earlier initialize heart-beat when the spring container starts <note> when the
* transport dependency is on classpath ,the configuration is effective </note>
*/
private boolean eager = false;
/**
* Enable sentinel auto configure, the default value is true
*/
private boolean enabled = true;
/**
* enable sentinel auto configure, the default value is true
*/
private boolean enabled = true;
/**
* 字符编码集
*/
private String charset = "UTF-8";
/**
* charset when sentinel write or search metric file {@link SentinelConfig#CHARSET}
*/
private String charset = "UTF-8";
/**
* 通信相关配置
*/
@NestedConfigurationProperty
private Transport transport = new Transport();
/**
* configurations about datasource, like 'nacos', 'apollo', 'file', 'zookeeper'
*/
private Map<String, DataSourcePropertiesConfiguration> datasource = new TreeMap<>(
String.CASE_INSENSITIVE_ORDER);
/**
* 监控数据相关配置
*/
@NestedConfigurationProperty
private Metric metric = new Metric();
/**
* transport configuration about dashboard and client
*/
@NestedConfigurationProperty
private Transport transport = new Transport();
/**
* web 相关配置
*/
@NestedConfigurationProperty
private Servlet servlet = new Servlet();
/**
* metric configuration about resource
*/
@NestedConfigurationProperty
private Metric metric = new Metric();
/**
* 限流相关
*/
@NestedConfigurationProperty
private Filter filter = new Filter();
/**
* web servlet configuration <note> when the application is web ,the configuration is
* effective </note>
*/
@NestedConfigurationProperty
private Servlet servlet = new Servlet();
@NestedConfigurationProperty
private Flow flow = new Flow();
/**
* sentinel filter <note> when the application is web ,the configuration is effective
* </note>
*/
@NestedConfigurationProperty
private Filter filter = new Filter();
public boolean isEager() {
return eager;
}
/**
* flow configuration
*/
@NestedConfigurationProperty
private Flow flow = new Flow();
public void setEager(boolean eager) {
this.eager = eager;
}
public boolean isEager() {
return eager;
}
public Flow getFlow() {
return flow;
}
public void setEager(boolean eager) {
this.eager = eager;
}
public void setFlow(Flow flow) {
this.flow = flow;
}
public Flow getFlow() {
return flow;
}
public String getCharset() {
return charset;
}
public void setFlow(Flow flow) {
this.flow = flow;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getCharset() {
return charset;
}
public Transport getTransport() {
return transport;
}
public void setCharset(String charset) {
this.charset = charset;
}
public void setTransport(Transport transport) {
this.transport = transport;
}
public Transport getTransport() {
return transport;
}
public Metric getMetric() {
return metric;
}
public void setTransport(Transport transport) {
this.transport = transport;
}
public void setMetric(Metric metric) {
this.metric = metric;
}
public Metric getMetric() {
return metric;
}
public Servlet getServlet() {
return servlet;
}
public void setMetric(Metric metric) {
this.metric = metric;
}
public void setServlet(Servlet servlet) {
this.servlet = servlet;
}
public Servlet getServlet() {
return servlet;
}
public boolean isEnabled() {
return enabled;
}
public void setServlet(Servlet servlet) {
this.servlet = servlet;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return enabled;
}
public Filter getFilter() {
return filter;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setFilter(Filter filter) {
this.filter = filter;
}
public Filter getFilter() {
return filter;
}
public static class Flow {
public void setFilter(Filter filter) {
this.filter = filter;
}
/**
* 限流冷启动因子
*/
private String coldFactor = "3";
public Map<String, DataSourcePropertiesConfiguration> getDatasource() {
return datasource;
}
public String getColdFactor() {
return coldFactor;
}
public void setDatasource(Map<String, DataSourcePropertiesConfiguration> datasource) {
this.datasource = datasource;
}
public void setColdFactor(String coldFactor) {
this.coldFactor = coldFactor;
}
public static class Flow {
}
/**
* the cold factor {@link SentinelConfig#COLD_FACTOR}
*/
private String coldFactor = "3";
public static class Servlet {
public String getColdFactor() {
return coldFactor;
}
/**
* url 限流后的处理页面
*/
private String blockPage;
public void setColdFactor(String coldFactor) {
this.coldFactor = coldFactor;
}
public String getBlockPage() {
return blockPage;
}
}
public void setBlockPage(String blockPage) {
this.blockPage = blockPage;
}
}
public static class Servlet {
public static class Metric {
/**
* The process page when the flow control is triggered
*/
private String blockPage;
/**
* 监控数据写磁盘时单个文件的大小
*/
private String fileSingleSize;
public String getBlockPage() {
return blockPage;
}
/**
* 监控数据在磁盘上的总数量
*/
private String fileTotalCount;
public void setBlockPage(String blockPage) {
this.blockPage = blockPage;
}
}
public String getFileSingleSize() {
return fileSingleSize;
}
public static class Metric {
public void setFileSingleSize(String fileSingleSize) {
this.fileSingleSize = fileSingleSize;
}
/**
* the metric file size {@link SentinelConfig#SINGLE_METRIC_FILE_SIZE}
*/
private String fileSingleSize;
public String getFileTotalCount() {
return fileTotalCount;
}
/**
* the total metric file count {@link SentinelConfig#TOTAL_METRIC_FILE_COUNT}
*/
private String fileTotalCount;
public void setFileTotalCount(String fileTotalCount) {
this.fileTotalCount = fileTotalCount;
}
}
public String getFileSingleSize() {
return fileSingleSize;
}
public static class Transport {
public void setFileSingleSize(String fileSingleSize) {
this.fileSingleSize = fileSingleSize;
}
/**
* sentinel api port,default value is 8721
*/
private String port = "8721";
public String getFileTotalCount() {
return fileTotalCount;
}
/**
* Sentinel dashboard address, won't try to connect dashboard when address is
* empty
*/
private String dashboard = "";
public void setFileTotalCount(String fileTotalCount) {
this.fileTotalCount = fileTotalCount;
}
}
/**
* 客户端和DashBord心跳发送时间
*/
private String heartbeatIntervalMs;
public static class Transport {
public String getHeartbeatIntervalMs() {
return heartbeatIntervalMs;
}
/**
* sentinel api port,default value is 8721 {@link TransportConfig#SERVER_PORT}
*/
private String port = "8721";
public void setHeartbeatIntervalMs(String heartbeatIntervalMs) {
this.heartbeatIntervalMs = heartbeatIntervalMs;
}
/**
* sentinel dashboard address, won't try to connect dashboard when address is
* empty {@link TransportConfig#CONSOLE_SERVER}
*/
private String dashboard = "";
public String getPort() {
return port;
}
/**
* send heartbeat interval millisecond
* {@link TransportConfig#HEARTBEAT_INTERVAL_MS}
*/
private String heartbeatIntervalMs;
public void setPort(String port) {
this.port = port;
}
public String getHeartbeatIntervalMs() {
return heartbeatIntervalMs;
}
public String getDashboard() {
return dashboard;
}
public void setHeartbeatIntervalMs(String heartbeatIntervalMs) {
this.heartbeatIntervalMs = heartbeatIntervalMs;
}
public void setDashboard(String dashboard) {
this.dashboard = dashboard;
}
public String getPort() {
return port;
}
}
public void setPort(String port) {
this.port = port;
}
public static class Filter {
public String getDashboard() {
return dashboard;
}
/**
* Sentinel filter chain order.
*/
private int order = Ordered.HIGHEST_PRECEDENCE;
public void setDashboard(String dashboard) {
this.dashboard = dashboard;
}
/**
* URL pattern for sentinel filter,default is /*
*/
private List<String> urlPatterns;
}
public int getOrder() {
return this.order;
}
public static class Filter {
public void setOrder(int order) {
this.order = order;
}
/**
* sentinel filter chain order.
*/
private int order = Ordered.HIGHEST_PRECEDENCE;
public List<String> getUrlPatterns() {
return urlPatterns;
}
/**
* URL pattern for sentinel filter,default is /*
*/
private List<String> urlPatterns;
public void setUrlPatterns(List<String> urlPatterns) {
this.urlPatterns = urlPatterns;
}
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public List<String> getUrlPatterns() {
return urlPatterns;
}
public void setUrlPatterns(List<String> urlPatterns) {
this.urlPatterns = urlPatterns;
}
}
}

View File

@ -18,6 +18,20 @@ package org.springframework.cloud.alibaba.sentinel.custom;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.alibaba.sentinel.SentinelProperties;
import org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter;
import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
@ -28,115 +42,131 @@ import com.alibaba.csp.sentinel.init.InitExecutor;
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
import com.alibaba.csp.sentinel.util.AppNameUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.alibaba.sentinel.SentinelProperties;
import org.springframework.cloud.alibaba.sentinel.datasource.SentinelDataSourcePostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
/**
* @author xiaojing
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SentinelProperties.class)
public class SentinelAutoConfiguration {
@Value("${project.name:${spring.application.name:}}")
private String projectName;
@Value("${project.name:${spring.application.name:}}")
private String projectName;
@Autowired
private SentinelProperties properties;
@Autowired
private SentinelProperties properties;
@Autowired(required = false)
private UrlCleaner urlCleaner;
@Autowired(required = false)
private UrlCleaner urlCleaner;
@Autowired(required = false)
private UrlBlockHandler urlBlockHandler;
@Autowired(required = false)
private UrlBlockHandler urlBlockHandler;
@PostConstruct
private void init() {
if (StringUtils.isEmpty(System.getProperty(AppNameUtil.APP_NAME))
&& StringUtils.hasText(projectName)) {
System.setProperty(AppNameUtil.APP_NAME, projectName);
}
if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT))
&& StringUtils.hasText(properties.getTransport().getPort())) {
System.setProperty(TransportConfig.SERVER_PORT,
properties.getTransport().getPort());
}
if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER))
&& StringUtils.hasText(properties.getTransport().getDashboard())) {
System.setProperty(TransportConfig.CONSOLE_SERVER,
properties.getTransport().getDashboard());
}
if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_INTERVAL_MS))
&& StringUtils
.hasText(properties.getTransport().getHeartbeatIntervalMs())) {
System.setProperty(TransportConfig.HEARTBEAT_INTERVAL_MS,
properties.getTransport().getHeartbeatIntervalMs());
}
if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET))
&& StringUtils.hasText(properties.getCharset())) {
System.setProperty(SentinelConfig.CHARSET, properties.getCharset());
}
if (StringUtils
.isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE))
&& StringUtils.hasText(properties.getMetric().getFileSingleSize())) {
System.setProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE,
properties.getMetric().getFileSingleSize());
}
if (StringUtils
.isEmpty(System.getProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT))
&& StringUtils.hasText(properties.getMetric().getFileTotalCount())) {
System.setProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT,
properties.getMetric().getFileTotalCount());
}
if (StringUtils.isEmpty(System.getProperty(SentinelConfig.COLD_FACTOR))
&& StringUtils.hasText(properties.getFlow().getColdFactor())) {
System.setProperty(SentinelConfig.COLD_FACTOR,
properties.getFlow().getColdFactor());
}
@PostConstruct
private void init() {
if (StringUtils.isEmpty(System.getProperty(AppNameUtil.APP_NAME))
&& StringUtils.hasText(projectName)) {
System.setProperty(AppNameUtil.APP_NAME, projectName);
}
if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT))
&& StringUtils.hasText(properties.getTransport().getPort())) {
System.setProperty(TransportConfig.SERVER_PORT,
properties.getTransport().getPort());
}
if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER))
&& StringUtils.hasText(properties.getTransport().getDashboard())) {
System.setProperty(TransportConfig.CONSOLE_SERVER,
properties.getTransport().getDashboard());
}
if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_INTERVAL_MS))
&& StringUtils
.hasText(properties.getTransport().getHeartbeatIntervalMs())) {
System.setProperty(TransportConfig.HEARTBEAT_INTERVAL_MS,
properties.getTransport().getHeartbeatIntervalMs());
}
if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET))
&& StringUtils.hasText(properties.getCharset())) {
System.setProperty(SentinelConfig.CHARSET, properties.getCharset());
}
if (StringUtils
.isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE))
&& StringUtils.hasText(properties.getMetric().getFileSingleSize())) {
System.setProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE,
properties.getMetric().getFileSingleSize());
}
if (StringUtils
.isEmpty(System.getProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT))
&& StringUtils.hasText(properties.getMetric().getFileTotalCount())) {
System.setProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT,
properties.getMetric().getFileTotalCount());
}
if (StringUtils.isEmpty(System.getProperty(SentinelConfig.COLD_FACTOR))
&& StringUtils.hasText(properties.getFlow().getColdFactor())) {
System.setProperty(SentinelConfig.COLD_FACTOR,
properties.getFlow().getColdFactor());
}
if (StringUtils.hasText(properties.getServlet().getBlockPage())) {
WebServletConfig.setBlockPage(properties.getServlet().getBlockPage());
}
if (urlBlockHandler != null) {
WebCallbackManager.setUrlBlockHandler(urlBlockHandler);
}
if (urlCleaner != null) {
WebCallbackManager.setUrlCleaner(urlCleaner);
}
if (StringUtils.hasText(properties.getServlet().getBlockPage())) {
WebServletConfig.setBlockPage(properties.getServlet().getBlockPage());
}
if (urlBlockHandler != null) {
WebCallbackManager.setUrlBlockHandler(urlBlockHandler);
}
if (urlCleaner != null) {
WebCallbackManager.setUrlCleaner(urlCleaner);
}
// earlier initialize
if (properties.isEager()) {
InitExecutor.doInit();
}
}
// earlier initialize
if (properties.isEager()) {
InitExecutor.doInit();
}
}
@Bean
@ConditionalOnMissingBean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
@Bean
@ConditionalOnMissingBean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
public SentinelBeanPostProcessor sentinelBeanPostProcessor() {
return new SentinelBeanPostProcessor();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
public SentinelBeanPostProcessor sentinelBeanPostProcessor() {
return new SentinelBeanPostProcessor();
}
@Bean
@ConditionalOnMissingBean
public SentinelDataSourcePostProcessor sentinelDataSourcePostProcessor() {
return new SentinelDataSourcePostProcessor();
}
@Bean
public SentinelDataSourceHandler sentinelDataSourceHandler() {
return new SentinelDataSourceHandler();
}
@Bean("sentinel-json-converter")
public JsonConverter jsonConverter(
@Qualifier("sentinel-object-mapper") ObjectMapper objectMapper) {
return new JsonConverter(objectMapper);
}
@Bean("sentinel-object-mapper")
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
@ConditionalOnClass(XmlMapper.class)
protected static class SentinelXmlConfiguration {
@Bean("sentinel-xml-converter")
public XmlConverter xmlConverter(
@Qualifier("sentinel-xml-mapper") XmlMapper xmlMapper) {
return new XmlConverter(xmlMapper);
}
@Bean("sentinel-xml-mapper")
public XmlMapper xmlMapper() {
return new XmlMapper();
}
}
}

View File

@ -34,7 +34,7 @@ import org.springframework.web.client.RestTemplate;
/**
* PostProcessor handle @SentinelProtect Annotation, add interceptor for RestTemplate
*
* @author fangjian
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @see SentinelProtect
* @see SentinelProtectInterceptor
*/

View File

@ -0,0 +1,353 @@
/*
* Copyright (C) 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.sentinel.custom;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.alibaba.sentinel.SentinelProperties;
import org.springframework.cloud.alibaba.sentinel.datasource.config.AbstractDataSourceProperties;
import org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePropertiesConfiguration;
import org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter;
import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter;
import org.springframework.context.event.EventListener;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.slots.block.AbstractRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
/**
* Sentinel {@link ReadableDataSource} Handler Handle the configurations of
* 'spring.cloud.sentinel.datasource'
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @see SentinelProperties#datasource
* @see JsonConverter
* @see XmlConverter
*/
public class SentinelDataSourceHandler {
private static final Logger logger = LoggerFactory
.getLogger(SentinelDataSourceHandler.class);
private List<String> dataTypeList = Arrays.asList("json", "xml");
private List<Class<? extends AbstractRule>> rulesList = Arrays.asList(FlowRule.class,
DegradeRule.class, SystemRule.class, AuthorityRule.class);
private List<String> dataSourceBeanNameList = Collections
.synchronizedList(new ArrayList<String>());
private final String DATATYPE_FIELD = "dataType";
private final String CUSTOM_DATATYPE = "custom";
private final String CONVERTERCLASS_FIELD = "converterClass";
@Autowired
private SentinelProperties sentinelProperties;
@EventListener(classes = ApplicationReadyEvent.class)
public void buildDataSource(ApplicationReadyEvent event) throws Exception {
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) event
.getApplicationContext().getAutowireCapableBeanFactory();
for (Map.Entry<String, DataSourcePropertiesConfiguration> entry : sentinelProperties
.getDatasource().entrySet()) {
String dataSourceName = entry.getKey();
DataSourcePropertiesConfiguration dataSourceProperties = entry.getValue();
if (dataSourceProperties.getInvalidField().size() != 1) {
logger.error("[Sentinel Starter] DataSource " + dataSourceName
+ " multi datasource active and won't loaded: "
+ dataSourceProperties.getInvalidField());
return;
}
if (dataSourceProperties.getFile() != null) {
try {
dataSourceProperties.getFile()
.setFile(ResourceUtils
.getFile(StringUtils.trimAllWhitespace(
dataSourceProperties.getFile().getFile()))
.getAbsolutePath());
}
catch (IOException e) {
logger.error("[Sentinel Starter] DataSource " + dataSourceName
+ " handle file error: " + e.getMessage());
throw new RuntimeException("[Sentinel Starter] DataSource "
+ dataSourceName + " handle file error: " + e.getMessage(),
e);
}
registerBean(beanFactory, dataSourceProperties.getFile(),
dataSourceName + "-sentinel-file-datasource");
}
if (dataSourceProperties.getNacos() != null) {
registerBean(beanFactory, dataSourceProperties.getNacos(),
dataSourceName + "-sentinel-nacos-datasource");
}
if (dataSourceProperties.getApollo() != null) {
registerBean(beanFactory, dataSourceProperties.getApollo(),
dataSourceName + "-sentinel-apollo-datasource");
}
if (dataSourceProperties.getZk() != null) {
registerBean(beanFactory, dataSourceProperties.getZk(),
dataSourceName + "-sentinel-zk-datasource");
}
}
for (String beanName : dataSourceBeanNameList) {
ReadableDataSource dataSource = beanFactory.getBean(beanName,
ReadableDataSource.class);
Object ruleConfig;
try {
logger.info("[Sentinel Starter] DataSource " + beanName
+ " start to loadConfig");
ruleConfig = dataSource.loadConfig();
}
catch (Exception e) {
logger.error("[Sentinel Starter] DataSource " + beanName
+ " loadConfig error: " + e.getMessage(), e);
return;
}
SentinelProperty sentinelProperty = dataSource.getProperty();
Class ruleType = getAndCheckRuleType(ruleConfig, beanName);
if (ruleType != null) {
if (ruleType == FlowRule.class) {
FlowRuleManager.register2Property(sentinelProperty);
}
else if (ruleType == DegradeRule.class) {
DegradeRuleManager.register2Property(sentinelProperty);
}
else if (ruleType == SystemRule.class) {
SystemRuleManager.register2Property(sentinelProperty);
}
else {
AuthorityRuleManager.register2Property(sentinelProperty);
}
}
}
}
private void registerBean(DefaultListableBeanFactory beanFactory,
final AbstractDataSourceProperties dataSourceProperties,
String dataSourceName) {
Map<String, Object> propertyMap = new HashMap<>();
for (Field field : dataSourceProperties.getClass().getDeclaredFields()) {
try {
field.setAccessible(true);
Object fieldVal = field.get(dataSourceProperties);
if (fieldVal != null) {
propertyMap.put(field.getName(), fieldVal);
}
}
catch (IllegalAccessException e) {
logger.error("[Sentinel Starter] DataSource " + dataSourceName
+ " field: " + field.getName() + " invoke error");
throw new RuntimeException("[Sentinel Starter] DataSource "
+ dataSourceName + " field: " + field.getName() + " invoke error",
e);
}
}
propertyMap.put(CONVERTERCLASS_FIELD, dataSourceProperties.getConverterClass());
propertyMap.put(DATATYPE_FIELD, dataSourceProperties.getDataType());
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(dataSourceProperties.getFactoryBeanName());
for (Map.Entry<String, Object> entry : propertyMap.entrySet()) {
String propertyName = entry.getKey();
Object propertyValue = entry.getValue();
Field field = ReflectionUtils.findField(dataSourceProperties.getClass(),
propertyName);
if (field != null) {
if (DATATYPE_FIELD.equals(propertyName)) {
String dataType = StringUtils
.trimAllWhitespace(propertyValue.toString());
if (CUSTOM_DATATYPE.equals(dataType)) {
try {
if (StringUtils
.isEmpty(dataSourceProperties.getConverterClass())) {
throw new RuntimeException(
"[Sentinel Starter] DataSource " + dataSourceName
+ "dataType is custom, please set converter-class "
+ "property");
}
// construct custom Converter with 'converterClass'
// configuration and register
String customConvertBeanName = "sentinel-"
+ dataSourceProperties.getConverterClass();
if (!beanFactory.containsBean(customConvertBeanName)) {
beanFactory.registerBeanDefinition(customConvertBeanName,
BeanDefinitionBuilder
.genericBeanDefinition(
Class.forName(dataSourceProperties
.getConverterClass()))
.getBeanDefinition());
}
builder.addPropertyReference("converter",
customConvertBeanName);
}
catch (ClassNotFoundException e) {
logger.error("[Sentinel Starter] DataSource " + dataSourceName
+ " handle "
+ dataSourceProperties.getClass().getSimpleName()
+ " error, class name: "
+ dataSourceProperties.getConverterClass());
throw new RuntimeException(
"[Sentinel Starter] DataSource " + dataSourceName
+ " handle "
+ dataSourceProperties.getClass()
.getSimpleName()
+ " error, class name: "
+ dataSourceProperties.getConverterClass(),
e);
}
}
else {
if (!dataTypeList.contains(StringUtils
.trimAllWhitespace(propertyValue.toString()))) {
throw new RuntimeException("[Sentinel Starter] DataSource "
+ dataSourceName + " dataType: " + propertyValue
+ " is not support now. please using these types: "
+ dataTypeList.toString());
}
// converter type now support xml or json.
// The bean name of these converters wrapped by
// 'sentinel-{converterType}-converter'
builder.addPropertyReference("converter",
"sentinel-" + propertyValue.toString() + "-converter");
}
}
else if (CONVERTERCLASS_FIELD.equals(propertyName)) {
continue;
}
else {
// wired properties
builder.addPropertyValue(propertyName, propertyValue);
}
}
}
beanFactory.registerBeanDefinition(dataSourceName, builder.getBeanDefinition());
// init in Spring
beanFactory.getBean(dataSourceName);
dataSourceBeanNameList.add(dataSourceName);
}
private Class getAndCheckRuleType(Object ruleConfig, String dataSourceName) {
if (rulesList.contains(ruleConfig.getClass())) {
logger.info("[Sentinel Starter] DataSource {} load {} {}", dataSourceName, 1,
ruleConfig.getClass().getSimpleName());
return ruleConfig.getClass();
}
else if (ruleConfig instanceof List) {
List convertedRuleList = (List) ruleConfig;
if (CollectionUtils.isEmpty(convertedRuleList)) {
logger.warn("[Sentinel Starter] DataSource {} rule list is empty.",
dataSourceName);
return null;
}
if (checkRuleTypeValid(convertedRuleList)) {
if (checkAllRuleTypeSame(convertedRuleList)) {
logger.info("[Sentinel Starter] DataSource {} load {} {}",
dataSourceName, convertedRuleList.size(),
convertedRuleList.get(0).getClass().getSimpleName());
return convertedRuleList.get(0).getClass();
}
else {
logger.warn(
"[Sentinel Starter] DataSource {} all rules are not same rule type and it will not be used. "
+ "Rule List: {}",
dataSourceName, convertedRuleList.toString());
}
}
else {
List<Class> classList = new ArrayList<>();
for (Object rule : convertedRuleList) {
classList.add(rule.getClass());
}
logger.error("[Sentinel Starter] DataSource " + dataSourceName
+ " rule class is invalid. Class List: " + classList);
throw new RuntimeException(
"[Sentinel Starter] DataSource " + dataSourceName
+ " rule class is invalid. Class List: " + classList);
}
}
else {
logger.error("[Sentinel Starter] DataSource " + dataSourceName
+ " rule class is invalid. Class: " + ruleConfig.getClass());
throw new RuntimeException("[Sentinel Starter] DataSource " + dataSourceName
+ " rule class is invalid. Class: " + ruleConfig.getClass());
}
return null;
}
private boolean checkRuleTypeValid(List convertedRuleList) {
int matchCount = 0;
for (Object rule : convertedRuleList) {
if (rulesList.contains(rule.getClass())) {
matchCount++;
}
}
return matchCount == convertedRuleList.size();
}
private boolean checkAllRuleTypeSame(List convertedRuleList) {
int matchCount = 0;
for(Object rule : convertedRuleList) {
if(rulesList.contains(rule.getClass()) && rule.getClass() == convertedRuleList.get(0).getClass()) {
matchCount ++;
}
}
return matchCount == convertedRuleList.size();
}
public List<String> getDataSourceBeanNameList() {
return dataSourceBeanNameList;
}
}

View File

@ -20,15 +20,19 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.cloud.alibaba.sentinel.SentinelProperties;
import org.springframework.cloud.alibaba.sentinel.custom.SentinelDataSourceHandler;
import org.springframework.context.ApplicationContext;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.cloud.alibaba.sentinel.SentinelProperties;
/**
* Endpoint for Sentinel, contains ans properties and rules
@ -39,6 +43,12 @@ public class SentinelEndpoint extends AbstractEndpoint<Map<String, Object>> {
@Autowired
private SentinelProperties sentinelProperties;
@Autowired
private SentinelDataSourceHandler dataSourceHandler;
@Autowired
private ApplicationContext applicationContext;
public SentinelEndpoint() {
super("sentinel");
}
@ -54,6 +64,20 @@ public class SentinelEndpoint extends AbstractEndpoint<Map<String, Object>> {
result.put("FlowRules", flowRules);
result.put("DegradeRules", degradeRules);
result.put("SystemRules", systemRules);
result.put("datasources", new HashMap<String, Object>());
for (String dataSourceBeanName : dataSourceHandler.getDataSourceBeanNameList()) {
ReadableDataSource dataSource = applicationContext.getBean(dataSourceBeanName,
ReadableDataSource.class);
try {
((HashMap) result.get("datasources")).put(dataSourceBeanName,
dataSource.loadConfig());
}
catch (Exception e) {
((HashMap) result.get("datasources")).put(dataSourceBeanName,
"load error: " + e.getMessage());
}
}
return result;
}