SpringCloudAlibaba-Sentinel服务高可用

一、什么是Sentinel

Sentinel是Alibaba开源的,面向分布式服务架构的高可用组件。其拥有多维度的流控降级能力,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者稳定微服务的稳定性。我们可以通过官网的文档了解到更多。

二、依赖引入配置

这里只是使用了单纯的Sentinel,并没有整合SpringCloudAlibaba,后面会写到整合到SpringCloudAlibaba框架的项目中。

  1. 依赖引入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- sentinel -->
    <dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    </dependency>
    <!-- 使用sentinel的注解 -->
    <dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    </dependency>
  2. 如果要使用Sentinel的注解还需要将SentinelResourceAspect类型的bean加入到spring的容器中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * @ClassName SentinelConfig
    * @Description
    * @Author sunyu
    * @Date 2023/3/6 21:56
    * @Version 1.0
    **/
    @Component
    public class SentinelConfig {


    /**
    * @Description 配置启动Sentinel的注解
    * @Author sy
    * @Date 2023/3/6
    * @Version 1.0
    **/
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
    return new SentinelResourceAspect();
    }
    }

三、基于代码控流和降级的示例

这里只演示简单的流控和降级的代码示例,值得注意的是 流控规则一般在被调用端熔断降级规则一般在调用端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/**
* @ClassName SentinelContoller
* @Description Sentinel 流控实例
* @Author sunyu
* @Date 2023/3/5 20:32
* @Version 1.0
**/
@Slf4j
@RestController
@RequestMapping(value = "/sentinel")
public class SentinelController {

private static final String RESOURCE_NAME = "hello";

private static final String USER_RESOURCE_NAME = "user";

private static final String DEGRADE_RESOURCE_NAME = "degrade";

/**
* @Description 测试流控接口
* @Author sy
* @Date 2023/3/6
* @Version 1.0
**/
@GetMapping("/hello")
public String hello() {
Entry entry = null;
try {
// sentinel 针对资源进行的限制
entry = SphU.entry(RESOURCE_NAME);
String result = "Hello World!";
log.info(result);
return result;
} catch (BlockException e) {
// 自愿访问组织,被限流或被降级
// 进行相应的处理操作
log.info("被限流了");
return "被流控了!";
} catch (Exception e) {
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(e, entry);
}finally {
if (entry != null) {
entry.exit();
}
}
return null;
}


/**
* @Description 测试流控注解接口
* @SentinelResource 改善接口中资源定义和被流控降级后的处理方法
* value: 定义的资源名称
* blockHandler: 被流控降级后的处理方法(默认该方法必须生命在同一个类中)(优先级高于fallback)
* blockHandlerClass: 可以将处理方法放到别的类中
* fallback: 异常的处理方法
* fallbackClass: 可以异常的处理方法放在白的类中
* ExceptionsToIgnore:排除异常不被fallback拦截
* @Author sy
* @Date 2023/3/6
* @Version 1.0
**/
@GetMapping("/user")
@SentinelResource(value = USER_RESOURCE_NAME, blockHandler = "blockHandleForUser")
public String user(String id) {
log.info("user:" + id);
return "user";
}

/**
* @Description 被流控后的处理方法,该方法必须是由public声明的,入参与返回值要与原方法一致
* @Author sy
* @Date 2023/3/6
* @Version 1.0
**/
public String blockHandleForUser(String id, BlockException e) {
e.printStackTrace();
log.info("user被流控了");
return "user被流控了";
}


/**
* @Description 测试熔断降级接口
* @Author sy
* @Date 2023/3/6
* @Version 1.0
**/
@GetMapping("/degrade")
@SentinelResource(value = DEGRADE_RESOURCE_NAME, entryType = EntryType.IN, blockHandler = "blockHandleForDegrade")
public String degrade() {
int s = 1 / 0;
return "degrade";
}

/**
* @Description 被熔断降级后的处理方法,该方法必须是由public声明的,入参与返回值要与原方法一致
* @Author sy
* @Date 2023/3/6
* @Version 1.0
**/
public String blockHandleForDegrade(BlockException e) {
e.printStackTrace();
log.info("user被熔断了");
return "user被熔断了";
}


/**
* @Description 初始化流控规则
* @PostConstruct 注解是Spring的注解,作用就是当当前类被加载的时候就会初始化当前这个方法
* @Author sy
* @Date 2023/3/5
* @Version 1.0
**/
@PostConstruct
private static void initFlowRules() {
// 流控规则
List<FlowRule> rules = new ArrayList<>();
// 定义流控对象
FlowRule ruleHello = new FlowRule();
// 设置受保护的资源
ruleHello.setResource(RESOURCE_NAME);
// 设置流控规则策略 QPS/线程数
ruleHello.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 阈值
ruleHello.setCount(1);
rules.add(ruleHello);

// 为使用@SentinelResouce注解的接口定义流控规则
// 定义流控对象
FlowRule ruleUser = new FlowRule();
// 设置受保护的资源
ruleUser.setResource(USER_RESOURCE_NAME);
// 设置流控规则策略 QPS/线程数
ruleUser.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 阈值
ruleUser.setCount(1);
rules.add(ruleUser);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);

}

/**
* @Description 初始化降级规则
* @Author sy
* @Date 2023/3/6
* @Version 1.0
**/
@PostConstruct
public void initDegradeRule() {
List<DegradeRule> list = new ArrayList<>();
// 定义降级规则对象
DegradeRule degradeRule = new DegradeRule();
// 定义降级规则资源
degradeRule.setResource(DEGRADE_RESOURCE_NAME);
// 设置熔断降级的策略:异常数 多种策略:慢调用策略、异常比例、异常数策略
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
// 阈值
degradeRule.setCount(2);
// 熔断时长 熔断过后会进入半开状态,回复调用的第一次调用就发生异常的话会直接进行熔断,不会根据这里的配置进行判断
degradeRule.setTimeWindow(10);
// 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
degradeRule.setMinRequestAmount(2);
// 间隔时长,可以理解为触发异常需要在这个时间间隔内
degradeRule.setStatIntervalMs(60 * 1000);
list.add(degradeRule);

// 加载降级规则
DegradeRuleManager.loadRules(list);

}

}

到这里简单的通过代码方式定义流控和熔断降级规则的示例就完成了,可以通过调用下面的接口测试:

  • 基于catch的方式捕获流控:xxxxxxxx/hello
  • 基于@SentinelResource 的方式捕获流控:xxxxxxx/user
  • 熔断降级:xxxxxxx/degrade

四、整合到SpringCloudAlibaba架构的项目中

这里可以参考SpringCloudAlibaba的官方文档来做

1、引入依赖

这里先将我们之前引入的依赖注释掉,在加入👇🏻的依赖,在测试一下依然是可以实现上面的流控和熔断降级规则的。

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2、加入Sentinel 控制台

我们可以从sentinel tag页面获取到想要的版本

  1. 通过一下命令启动sentinel dashboard服务,可以根据我们的实际情况修改端口,更多用法及参数可以查看sentinel-dashboard文档

    1
    java -Dserver.port=8858 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

    访问8858端口就可以进入到控制台页面了

    图片加载失败

3、将服务注册进入到Sentinel控制台中

一共有两种方式,分别是通过启动参数配置和通过yml文件配置

  1. 通过启动参数配置:在启动参数中添加-Dcsp.sentinel.dashboard.server=sentinel服务的ip:8858

    图片加载失败

    当我们启动后会发现服务并没有注册到Sentinel的控制台中,这时我们只需要访问几次要注册进去的服务的接口,你就会发现服务已经注册到Sentinel中了

    图片加载失败

  2. 通过yml配置:在yml中添加👇🏻配置,这里要注意client-ip的不要写错,我就因为没有写和写成了Sentinel服务端的ip导致应用服务注册不到Sentinel,卡了一晚上

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    spring:
    cloud:
    sentinel:
    transport:
    # 客户端ip,就是你要注册进Sentinel的服务的所在ip
    client-ip: 192.168.0.101
    # Sentinel控制ip端口
    dashboard: 192.168.0.107:8858
    # 服务启动自动注册到Sentinel的服务端
    eager: true

    图片加载失败

五、整合OpenFeign

1、添加相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--nacos服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${alibaba.sentinel.version}</version>
</dependency>

上面三个依赖是必须的。

2、配置yml配置文件

开启OpenFeign对Sentinel的支持

1
2
3
feign:
sentinel:
enabled: true

3、实现你的OpenFeign接口类

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class StockSentinelFallback implements Stock{
@Override
public String reduceStock() {
return null;
}

@Override
public String reduceStock2() {
return "降级了2";
}
}

4、修改对应的OpenFeign接口类

只需要在@FeignClient注解中增加fallback入参即可,指定实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@FeignClient(name = "stock", path = "/stock", fallback = StockSentinelFallback.class)
public interface Stock {

/**
* @Description 这里的接口就与调用的Controller里面的接口一直就可以了,其实将对应的接口复制过来就可以了,只不过是没有实现的
* @Author sy
* @Date 2023/3/5
* @Version 1.0
**/
@PostMapping(value = "/reduceStock")
public String reduceStock();

@PostMapping(value = "/reduceStock2")
public String reduceStock2();
}

上面的代码中/reduceStock2是用于做降级熔断测试的接口,所以在测试类中只写了reduceStock2的返回值。

问题记录

  1. 本地服务无法注册到服务器的Sentinel dashboard中

    这个问题可能有以下几种原因,这里只写我遇到的:

    • yml配置文件中的 client-ip 配置项没有配置或者配置错误,这个配置项在上面有提到过,是配置你的服务所在ip,如果Sentinel dashboard和服务是在一台机器启动的则可以忽略这个配置

    • 版本对应的问题,我一直以为官方的版本对应应该是没有问题的。各种怀疑自己的配置或者网络问题。突然想起版本对应才发现这个问题,spring-cloud-alibaba-dependencies:2.2.9.RELEASE对应spring-cloud-starter-alibaba-sentinel:2.2.8.RELEASE,这个是经过我不懈测试后可以正常将服务注册到Sentinel dashboard中的版本