如需转载,请根据 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 许可,附上本文作者及链接。
本文作者: 执笔成念
作者昵称: zbcn
本文链接: https://1363653611.github.io/zbcn.github.io/2021/01/18/springcloud18-Ali-Sentinel/
Spring Cloud Alibaba:Sentinel实现熔断与限流
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案,Sentinel 作为其核心组件之一,具有熔断与限流等一系列服务保护功能,本文将对其用法进行详细介绍。
Sentinel简介
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel具有如下特性:
- 丰富的应用场景:承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀,可以实时熔断下游不可用应用;
- 完备的实时监控:同时提供实时的监控功能。可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况;
- 广泛的开源生态:提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合;
- 完善的 SPI 扩展点:提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。
安装Sentinel控制台
Sentinel控制台是一个轻量级的控制台应用,它可用于实时查看单机资源监控及集群资源汇总,并提供了一系列的规则管理功能,如流控规则、降级规则、热点规则等。
我们先从官网下载Sentinel,这里下载的是sentinel-dashboard-1.8.0.jar
文件,下载地址:https://github.com/alibaba/Sentinel/releases
- 下载完成后在命令行输入如下命令运行Sentinel控制台:
1 | java -jar sentinel-dashboard-1.8.0.jar |
- Sentinel控制台默认运行在8080端口上,登录账号密码均为
sentinel
,通过如下地址可以进行访问:http://localhost:8080
- Sentinel控制台可以查看单台机器的实时监控数据。
创建sentinel-service模块
这里我们创建一个sentinel-service模块,用于演示Sentinel的熔断与限流功能。
- 在pom.xml中添加相关依赖,这里我们使用Nacos作为注册中心,所以需要同时添加Nacos的依赖:
1 | <dependency> |
2 | <groupId>org.springframework.boot</groupId> |
3 | <artifactId>spring-boot-starter-web</artifactId> |
4 | </dependency> |
5 | <dependency> |
6 | <groupId>com.alibaba.cloud</groupId> |
7 | <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> |
8 | </dependency> |
9 | <dependency> |
10 | <groupId>com.alibaba.cloud</groupId> |
11 | <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> |
12 | </dependency> |
说明:
踩坑:必须要添加 spring-boot-starter-web
依赖,否则 无法注册nacos, 而且不报错,问题比较奇怪。
- 在application.yml中添加相关配置,主要是配置了Nacos和Sentinel控制台的地址:
1 | server: |
2 | port: 8401 |
3 | spring: |
4 | application: |
5 | name: sentinel-service |
6 | cloud: |
7 | nacos: |
8 | discovery: |
9 | server-addr: localhost:8848 #配置Nacos地址 |
10 | sentinel: |
11 | transport: |
12 | dashboard: localhost:8080 #配置sentinel dashboard地址 |
13 | port: 8719 |
14 | service-url: |
15 | user-service: http://nacos-user-service |
16 | management: |
17 | endpoints: |
18 | web: |
19 | exposure: |
20 | include: '*' |
限流功能
Sentinel Starter 默认为所有的 HTTP 服务提供了限流埋点,我们也可以通过使用@SentinelResource来自定义一些限流行为。
创建RateLimitController类
用于测试熔断和限流功能。
1 |
|
2 | "/rateLimit") ( |
3 | public class RateLimitController { |
4 | |
5 | /** |
6 | * 按资源名称限流,需要指定限流处理逻辑 |
7 | */ |
8 | "/byResource") ( |
9 | "byResource",blockHandler = "handleException") (value = |
10 | public ResponseResult byResource() { |
11 | return ResponseResult.success("按资源名称限流"); |
12 | } |
13 | |
14 | /** |
15 | * 按URL限流,有默认的限流处理逻辑 |
16 | */ |
17 | "/byUrl") ( |
18 | "byUrl",blockHandler = "handleException") (value = |
19 | public ResponseResult byUrl() { |
20 | return ResponseResult.success("按url限流"); |
21 | } |
22 | |
23 | public ResponseResult handleException(BlockException exception){ |
24 | return ResponseResult.success(exception.getClass().getCanonicalName()); |
25 | } |
26 | |
27 | } |
根据资源名称限流
我们可以根据@SentinelResource注解中定义的value(资源名称)来进行限流操作,但是需要指定限流处理逻辑
- 流控规则可以在Sentinel控制台进行配置,由于我们使用了Nacos注册中心,我们先启动Nacos和sentinel-service;
- 由于Sentinel采用的懒加载规则,需要我们先访问下接口,Sentinel控制台中才会有对应服务信息,我们先访问下该接口:http://localhost:8401/rateLimit/byResource
- 在Sentinel控制台配置流控规则,根据@SentinelResource注解的value值:
- 快速访问上面的接口,可以发现返回了自己定义的限流处理信息:
1 | { |
2 | "code": 200, |
3 | "status": "OK", |
4 | "msg": "com.alibaba.csp.sentinel.slots.block.flow.FlowException", |
5 | "data": null |
6 | } |
根据URL限流
我们还可以通过访问的URL来限流,会返回默认的限流处理信息。
- 在Sentinel控制台配置流控规则,使用访问的URL:
- 多次访问该接口,会返回默认的限流处理结果:http://localhost:8401/rateLimit/byUrl
1 | Blocked by Sentinel (flow limiting) |
自定义限流处理逻辑
我们可以自定义通用的限流处理逻辑,然后在@SentinelResource中指定。
- 创建CustomBlockHandler类用于自定义限流处理逻辑:
1 | public class CustomBlockHandler { |
2 | public static ResponseResult handleException(BlockException exception){ |
3 | return ResponseResult.success("自定义限流信息"); |
4 | } |
5 | } |
- 在RateLimitController中使用自定义限流处理逻辑:
1 | /** |
2 | * 自定义通用的限流处理逻辑 |
3 | */ |
4 | "/customBlockHandler") ( |
5 | "customBlockHandler", blockHandler = "handleException",blockHandlerClass = CustomBlockHandler.class) (value = |
6 | public ResponseResult blockHandler() { |
7 | return ResponseResult.success("限流成功"); |
8 | } |
- 在Sentinel控制台配置流控规则,使用访问的URL:
踩坑:
说明 @SentinelResource 注解:
- value属性必须唯一,这个是sentinel web系统进行配置限流规则的资源名称。
- blockHandlerClass ,这个是要配置 自定义的 限流触发后执行的方法的类,因为要与业务代码解耦,增强代码的复用性和可读性。
- 新建的 一个普通类 CustomerblockHandler,并且 不需要注入,这里使用的是反射机制。
- blockHandler ,这个就是自定义触发限流后的方法名称,
- 自定义的限流的方法 必须是 静态的 :static 并且 要有参数:BlockException (踩坑点,方法为非静态,自定义限流死活不生效)
熔断功能
Sentinel 支持对服务间调用进行保护,对故障应用进行熔断操作,这里我们使用RestTemplate来调用nacos-user-service服务所提供的接口来演示下该功能。
- 首先我们需要使用@SentinelRestTemplate来包装下RestTemplate实例:
1 |
|
2 | public class RibbonConfig { |
3 | |
4 | |
5 | |
6 | public RestTemplate restTemplate(){ |
7 | return new RestTemplate(); |
8 | } |
9 | } |
- 添加CircleBreakerController类,定义对nacos-user-service提供接口的调用:
1 | /** |
2 | * 熔断功能 |
3 | */ |
4 |
|
5 | "/breaker") ( |
6 | public class CircleBreakerController { |
7 | |
8 | private static Logger log = LoggerFactory.getLogger(CircleBreakerController.class); |
9 | |
10 | |
11 | private RestTemplate restTemplate; |
12 | |
13 | "${service-url.user-service}") ( |
14 | private String userServiceUrl; |
15 | |
16 | "/fallback/{id}") ( |
17 | "fallback",fallback = "handleFallback") (value = |
18 | public ResponseResult fallback(@PathVariable Long id) { |
19 | return restTemplate.getForObject(userServiceUrl + "/user/{1}", ResponseResult.class, id); |
20 | } |
21 | |
22 | "/fallbackException/{id}") ( |
23 | "fallbackException",fallback = "handleFallback2", exceptionsToIgnore = {NullPointerException.class}) (value = |
24 | public ResponseResult fallbackException(@PathVariable Long id) { |
25 | if (id == 1) { |
26 | throw new IndexOutOfBoundsException(); |
27 | } else if (id == 2) { |
28 | throw new NullPointerException(); |
29 | } |
30 | return restTemplate.getForObject(userServiceUrl + "/user/{1}", ResponseResult.class, id); |
31 | } |
32 | |
33 | public ResponseResult handleFallback(Long id) { |
34 | User defaultUser = new User(-1L, "defaultUser", "123456"); |
35 | ResponseResult<User> success = ResponseResult.success(defaultUser); |
36 | success.setMsg("服务降级返回"); |
37 | return ResponseResult.success(success); |
38 | } |
39 | |
40 | public ResponseResult handleFallback2(@PathVariable Long id, Throwable e) { |
41 | log.error("handleFallback2 id:{},throwable class:{}", id, e.getClass()); |
42 | User defaultUser = new User(-2L, "defaultUser2", "123456"); |
43 | ResponseResult<User> success = ResponseResult.success(defaultUser); |
44 | success.setMsg( "服务降级返回"); |
45 | return success; |
46 | |
47 | } |
48 | } |
- 启动nacos-user-service和sentinel-service服务:
- 由于我们并没有在nacos-user-service中定义id为4的用户,所有访问如下接口会返回服务降级结果:http://localhost:8401/breaker/fallback/4
1 | { |
2 | "code": 200, |
3 | "status": "OK", |
4 | "msg": null, |
5 | "data": { |
6 | "code": 200, |
7 | "status": "OK", |
8 | "msg": "服务降级返回", |
9 | "data": { |
10 | "id": -1, |
11 | "userName": "defaultUser", |
12 | "password": "123456" |
13 | } |
14 | } |
15 | } |
- 由于我们使用了exceptionsToIgnore参数忽略了NullPointerException,所以我们访问接口报空指针时不会发生服务降级:http://localhost:8401/breaker/fallbackException/2
与Feign结合使用
Sentinel也适配了Feign组件,我们使用Feign来进行服务间调用时,也可以使用它来进行熔断。
- 首先我们需要在pom.xml中添加Feign相关依赖:
1 | <dependency> |
2 | <groupId>org.springframework.cloud</groupId> |
3 | <artifactId>spring-cloud-starter-openfeign</artifactId> |
4 | </dependency> |
- 在application.yml中打开Sentinel对Feign的支持:
1 | feign: |
2 | sentinel: |
3 | enabled: true #打开sentinel对feign的支持 |
- 在应用启动类上添加@EnableFeignClients启动Feign的功能;
- 创建一个UserService接口,用于定义对nacos-user-service服务的调用:
1 | "nacos-user-service",fallback = UserFallbackService.class) (value = |
2 | public interface UserService { |
3 | "/user/create") ( |
4 | ResponseResult create(@RequestBody User user); |
5 | |
6 | "/user/{id}") ( |
7 | ResponseResult getUser(@PathVariable Long id); |
8 | |
9 | "/user/getByUsername") ( |
10 | ResponseResult getByUsername(@RequestParam String username); |
11 | |
12 | "/user/update") ( |
13 | ResponseResult update(@RequestBody User user); |
14 | |
15 | "/user/delete/{id}") ( |
16 | ResponseResult delete(@PathVariable Long id); |
17 | } |
- 创建UserFallbackService类实现UserService接口,用于处理服务降级逻辑:
1 |
|
2 | public class UserFallbackService implements UserService { |
3 | |
4 | public ResponseResult create(User user) { |
5 | User defaultUser = new User(-1L, "defaultUser", "123456"); |
6 | ResponseResult<User> success = ResponseResult.success(defaultUser); |
7 | success.setMsg("服务降级返回"); |
8 | return success; |
9 | } |
10 | |
11 | |
12 | public ResponseResult getUser(Long id) { |
13 | User defaultUser = new User(-1L, "defaultUser", "123456"); |
14 | ResponseResult<User> success = ResponseResult.success(defaultUser); |
15 | success.setMsg("服务降级返回"); |
16 | return success; |
17 | } |
18 | |
19 | |
20 | public ResponseResult getByUsername(String username) { |
21 | User defaultUser = new User(-1L, "defaultUser", "123456"); |
22 | ResponseResult<User> success = ResponseResult.success(defaultUser); |
23 | success.setMsg("服务降级返回"); |
24 | return success; |
25 | } |
26 | |
27 | |
28 | public ResponseResult update(User user) { |
29 | return ResponseResult.fail(ApiStatus.SERVICE_UNAVAILABLE,"调用失败,服务被降级"); |
30 | } |
31 | |
32 | |
33 | public ResponseResult delete(Long id) { |
34 | return ResponseResult.fail(ApiStatus.SERVICE_UNAVAILABLE,"调用失败,服务被降级"); |
35 | } |
36 | } |
- 在UserFeignController中使用UserService通过Feign调用nacos-user-service服务中的接口
1 |
|
2 | "/user") ( |
3 | public class UserFeignController { |
4 | |
5 | |
6 | private UserService userFallbackService; |
7 | |
8 | "/{id}") ( |
9 | public ResponseResult getUser(@PathVariable Long id) { |
10 | return userFallbackService.getUser(id); |
11 | } |
12 | |
13 | "/getByUsername") ( |
14 | public ResponseResult getByUsername(@RequestParam String username) { |
15 | return userFallbackService.getByUsername(username); |
16 | } |
17 | |
18 | "/create") ( |
19 | public ResponseResult create(@RequestBody User user) { |
20 | return userFallbackService.create(user); |
21 | } |
22 | |
23 | "/update") ( |
24 | public ResponseResult update(@RequestBody User user) { |
25 | return userFallbackService.update(user); |
26 | } |
27 | |
28 | "/delete/{id}") ( |
29 | public ResponseResult delete(@PathVariable Long id) { |
30 | return userFallbackService.delete(id); |
31 | } |
32 | } |
- 关闭 nacos-user-server 服务后,调用如下接口会发生服务降级,返回服务降级处理信息:http://localhost:8401/user/1
使用Nacos存储规则
默认情况下,当我们在Sentinel控制台中配置规则时,控制台推送规则方式是通过API将规则推送至客户端并直接更新到内存中。一旦我们重启应用,规则将消失。下面我们介绍下如何将配置规则进行持久化,以存储到Nacos为例。
原理示意图
- 首先我们直接在配置中心创建规则,配置中心将规则推送到客户端;
- Sentinel控制台也从配置中心去获取配置信息。
功能演示
以下所有修改都是针对 sentinel-server 服务
- 先在pom.xml中添加相关依赖:
1 | <dependency> |
2 | <groupId>com.alibaba.csp</groupId> |
3 | <artifactId>sentinel-datasource-nacos</artifactId> |
4 | </dependency> |
- 修改application.yml配置文件,添加Nacos数据源配置:
1 | spring: |
2 | cloud: |
3 | sentinel: |
4 | datasource: |
5 | ds1: |
6 | nacos: |
7 | server-addr: localhost:8848 |
8 | dataId: ${spring.application.name}-sentinel |
9 | groupId: DEFAULT_GROUP |
10 | data-type: json |
11 | rule-type: flow |
- 在Nacos中添加配置:
配置信息如下:
1 | [ |
2 | { |
3 | "resource": "/rateLimit/byUrl", |
4 | "limitApp": "default", |
5 | "grade": 1, |
6 | "count": 1, |
7 | "strategy": 0, |
8 | "controlBehavior": 0, |
9 | "clusterMode": false |
10 | } |
11 | ] |
相关参数解释:
- resource:资源名称;
- limitApp:来源应用;
- grade:阈值类型,0表示线程数,1表示QPS;
- count:单机阈值;
- strategy:流控模式,0表示直接,1表示关联,2表示链路;
- controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
- clusterMode:是否集群。
发现Sentinel控制台已经有了如下限流规则:
- 快速访问测试接口: http://localhost:8401/rateLimit/byUrl,可以发现返回了限流处理信息:`Blocked by Sentinel (flow limiting)`
使用到的模块
1 | ZBCN-SERVER |
2 | ├── zbcn-sentinel/lib/sentinel-dashboard-1.8.0.jar -- sentinel主服务 |
3 | ├── zbcn-nacos/lib/nacos-server-1.4.0.zip -- 注册监控主服务 |
4 | ├── zbcn-sentinel/sentinel-service -- sentinel功能测试服务 |
5 | └── zbcn-business/nacos-user-service -- 注册到nacos的提供User对象CRUD接口的服务 |
参考文档:
Spring Cloud Alibaba 官方文档:https://github.com/alibaba/spring-cloud-alibaba/wiki