Sentinel提供了@SentinelResource注解用于定义资源,并提供可选的异常回退和Block回退。异常回退指的是@SentinelResource注解标注的方法发生Java异常时的回退处理;Block回退指的是当@SentinelResource资源访问不符合Sentinel控制台定义的规则时的回退(默认返回Blocked by Sentinel (flow limiting))。这节简单记录下该注解的用法。
框架搭建
使用IDEA创建一个maven项目,artifactId为spring-cloud-alibaba-sentinelresource,然后在其下面创建两个Module(Spring Boot项目),artifactId分别为consumer和provider,充当服务消费端和服务提供端,项目结构如下图所示:
QQ20200320-145134
spring-cloud-alibaba-sentinelresource的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
<?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>cc.mrbird</groupId>
<artifactId>spring-cloud-alibaba-sentinelresource</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modules>
<module>provider</module>
<module>consumer</module>
</modules>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
<com-alibaba-cloud.version>2.2.0.RELEASE</com-alibaba-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${com-alibaba-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
引入了spring-boot-starter-web和spring-cloud-alibaba-nacos-discovery Nacos服务注册发现依赖。
provider的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
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.mrbird</groupId>
<artifactId>spring-cloud-alibaba-sentinelresource</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>provider</artifactId>
<name>provider</name>
<description>服务提供者</description>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
consumer的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
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.mrbird</groupId>
<artifactId>spring-cloud-alibaba-sentinelresource</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>consumer</artifactId>
<name>consumer</name>
<description>服务消费者</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
因为要演示在消费端使用@SentinelResource注解,所以我们引入了spring-cloud-starter-alibaba-sentinel依赖。
provider的配置文件application.yml内容如下:
1
2
3
4
5
6
7
8
server:
port: 8081
spring:
application:
name: provider
cloud:
nacos:
server-addr: localhost:8848
配置了端口号,服务名和nacos地址。
consumer的配置文件application.yml内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 9091
spring:
application:
name: consumer
cloud:
nacos:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719
配置了端口号,服务名,nacos地址和sentinel控制台地址等。
基本用法
我们在provider下添加一个REST资源。在provider的cc.mrbird.provider目录下新建controller包,然后在该包下新建GoodsController:
1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("goods")
public class GoodsController {
@GetMapping("buy/{name}/{count}")
public String buy(@PathVariable String name, @PathVariable Integer count) {
return String.format("购买%d份%s", count, name);
}
}
接着在consumer端通过Ribbon消费这个资源。在consumer的启动类ConsumerApplication里注册RestTemplate:
1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
在consumer的cc.mrbird.consumer下新建controller包,然后在该包下新建BuyController:
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
@RestController
public class BuyController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("buy/{name}/{count}")
@SentinelResource(value = "buy", fallback = "buyFallback", blockHandler = "buyBlock")
public String buy(@PathVariable String name, @PathVariable Integer count) {
if (count >= 20) {
throw new IllegalArgumentException("购买数量过多");
}
if ("miband".equalsIgnoreCase(name)) {
throw new NullPointerException("已售罄");
}
Map<String, Object> params = new HashMap<>(2);
params.put("name", name);
params.put("count", count);
return this.restTemplate.getForEntity("http://provider/goods/buy/{name}/{count}", String.class, params).getBody();
}
// 异常回退
public String buyFallback(@PathVariable String name, @PathVariable Integer count, Throwable throwable) {
return String.format("【进入fallback方法】购买%d份%s失败,%s", count, name, throwable.getMessage());
}
// sentinel回退
public String buyBlock(@PathVariable String name, @PathVariable Integer count, BlockException e) {
return String.format("【进入blockHandler方法】购买%d份%s失败,当前购买人数过多,请稍后再试", count, name);
}
}
在buy方法中,我们通过Ribbon的RestTemplate访问provider的/goods/buy接口。当count参数大于20或者name参数的值为miband的时候,方法将抛出异常。buy方法上使用@SentinelResource注解标注,标识为一个sentinel资源,资源名称为buy,并且配置了fallback方法和blockHandler方法。
如前面所说,当buy方法本身抛出异常时,会进入fallback指定的回退方法中;当buy方法调用不符合sentinel控制台规定的规则(如流控规则,降级规则等)时,会进入blockHander指定的block方法中。为了确保成功地进入回退方法(成功反射),它们必须满足以下规则:
- 函数访问范围需要是
public; - Fallback函数,函数签名与原函数一致或末尾加一个
Throwable类型的参数; - Block异常处理函数,参数最后多一个
BlockException,其余与原函数一致。
启动provider、consumer、nacos和sentinel控制台,浏览器访问:http://localhost:9091/buy/ipad/2:
QQ20200320-151518
我们在sentinel控制台中添加如下流控规则:
QQ20200320-151651@2x.png
QPS阈值为2。
然后快速访问http://localhost:9091/buy/ipad/2:
QQ20200320-152106@2x
可以看到,当方法访问不符合sentinel控制台规则时,进入的是blockHandler指定的回退方法。
如果访问:http://localhost:9091/buy/ipad/21
QQ20200320-152155@2x 或者:http://localhost:9091/buy/miband/2
QQ20200320-152224@2x
方法自身抛出异常引发回退,进入的是fallback指定的回退方法。
其他属性
在当前类中编写回退方法会使得代码变得冗余耦合度高,我们可以将回退方法抽取出来到一个指定类中。
在cc.mrbird.consumer包下新建reveal包,然后在该包下新建BuyBlockHandler:
1
2
3
4
5
6
7
public class BuyBlockHandler {
// sentinel回退
public static String buyBlock(@PathVariable String name, @PathVariable Integer count, BlockException e) {
return String.format("【进入blockHandler方法】购买%d份%s失败,当前购买人数过多,请稍后再试", count, name);
}
}
可以看到我们只是将buyBlock方法挪到了BuyBlockHandler中,不过这里的方法必须是static的。
接着新建BuyFallBack:
1
2
3
4
5
6
7
public class BuyFallBack {
// 异常回退
public static String buyFallback(@PathVariable String name, @PathVariable Integer count, Throwable throwable) {
return String.format("【进入fallback方法】购买%d份%s失败,%s", count, name, throwable.getMessage());
}
}
这样BuyController的代码就可以精简为:
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
@RestController
public class BuyController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("buy/{name}/{count}")
@SentinelResource(value = "buy",
fallback = "buyFallback",
fallbackClass = BuyFallBack.class,
blockHandler = "buyBlock",
blockHandlerClass = BuyBlockHandler.class
)
public String buy(@PathVariable String name, @PathVariable Integer count) {
if (count >= 20) {
throw new IllegalArgumentException("购买数量过多");
}
if ("miband".equalsIgnoreCase(name)) {
throw new NullPointerException("已售罄");
}
Map<String, Object> params = new HashMap<>(2);
params.put("name", name);
params.put("count", count);
return this.restTemplate.getForEntity("http://provider/goods/buy/{name}/{count}", String.class, params).getBody();
}
}
fallbackClass和blockHandlerClass指定回退方法所在的类。
此外我们也可以当遇到某个类型的异常时,不进行回退。比如:
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
@RestController
public class BuyController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("buy/{name}/{count}")
@SentinelResource(value = "buy",
fallback = "buyFallback",
fallbackClass = BuyFallBack.class,
blockHandler = "buyBlock",
blockHandlerClass = BuyBlockHandler.class,
exceptionsToIgnore = NullPointerException.class
)
public String buy(@PathVariable String name, @PathVariable Integer count) {
if (count >= 20) {
throw new IllegalArgumentException("购买数量过多");
}
if ("miband".equalsIgnoreCase(name)) {
throw new NullPointerException("已售罄");
}
Map<String, Object> params = new HashMap<>(2);
params.put("name", name);
params.put("count", count);
return this.restTemplate.getForEntity("http://provider/goods/buy/{name}/{count}", String.class, params).getBody();
}
}
exceptionsToIgnore指定,当遇到空指针异常时,不回退。
重启consumer,浏览器访问:http://localhost:9091/buy/miband/2:
QQ20200320-153530@2x
可以看到,此次并没有进行回退,而是直接返回error page。
本节源码链接:https://github.com/wuyouzhuguli/SpringAll/tree/master/78.spring-cloud-alibaba-sentinelresource