SpringCloud-Alibaba集成Sentinel以及流控+熔断改造
一:项目搭建环境:
Jdk:17
Nacos:2.2.1
Sentinel:1.8.6
二:引入sentinel的原因
微服务可能存在的问题
1.流量激增CPU符合过高无法正常处理请求
2.数据未进行预热导致的缓存击穿
3.消息太多导致消息积压
4.慢sql导致数据库连接占满
5.三方系统无响应重复点击导致线程卡满
6.系统异常内存无法正常释放OOM
7.服务器内存瓶颈,程序异常退出
所以服务不可用的问题我们是需要分场景进行解决的,Sentinel就是做这个使用的,它被称为分布式系统的流量防卫兵,他的主要作用就是保证系统的稳定性(reliability)和恢复性(resilience),他支持多种系统的容错机制,常见的有:
1.超时机制
设置超时时间,在指定的时间内处理,超时以后处理超时逻辑
2.限流机制
可根据QPS来对服务进行流程管控
3.线程隔离机制
利用线程池来对请求进行管理,超出部分可定义其他处理策略
4.服务熔断
当服务到达定义的熔断标准或者降级标准时对服务进行次一级的信息处理,可能是消息排队处理,可能是入库等待最终一致性的处理
5.服务降级
感觉这个感念其实和服务熔断类似,只是服务异常处理颗粒度的问题,都是需要对服务进行次一级处理。
三:Sentinel
Sentinel是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
源码地址:https://github.com/alibaba/Sentinel
官方文档:https://github.com/alibaba/Sentinel/wiki
下载地址:https://github.com/alibaba/Sentinel/releases/tag/1.8.6
四:可视化界面-Sentinel-dashboard
根据下载地址选择1.8.6版本下载源码或者jar选其一启动
启动方式
1:源码
-
找到对应sentinel-dashboard/resources下的application.propertis,修改链接的nacos信息。
-
启动sentinel-dashboard/java下的DashboardApplication.java。
2:jar包
- cmd 命令
java -jar jar包名
访问对应地址 http://ip:8080 用户名/密码:sentinel/sentinel
五:在SpringCloudAlibaba中整合Sentinel
1:添加对应版本pom
<!--SpringCloud-sentinel集成-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
2:Sentinel 对应yml中配置
spring:
cloud:
# sentinel降级配置
sentinel:
transport:
client-ip: ${spring.cloud.client.ip-address} # 获取本机IP地址
port: 8719 #默认8719端口,如果被占用会向上扫描。
#控制台地址
dashboard: 127.0.0.1:19527
log:
#日志路径
dir: logs/sentinel
# 默认将调用链路收敛,导致链路流控效果无效
web-context-unify: false
#持久化nacos配置中
datasource:
#sentinel-rule: 唯一名称可自定义
#限流
flow:
nacos:
# 设置Nacos的连接地址、命名空间和Group ID
server-addr: 127.0.0.1:8848
namespace: sentinel
# 设置Nacos中配置文件的命名规则
data-id: transport-sentinel-flow-rules
group-id: SENTINEL_GROUP
data-type: json
# 必填的重要字段,指定当前规则类型是"限流"
rule-type: flow
#熔断
degrade:
nacos:
server-addr: 127.0.0.1:8848
namespace: sentinel
data-id: transport-sentinel-degrade-rules
group-id: SENTINEL_GROUP
#熔断
rule-type: degrade
#取消慢加载
eager: true
上诉配置完成即能进入sentinel页面查看数据
六:服务降级
服务降级-Sentinel
这里需要使用Sentinel的注解 @SentinelResource ,这个注解是Sentinel的核心注解,这里先简单说下后面会详细介绍这个注解的使用及各个参数的释义。该注解这里只是用于做服务降级,可以不去注册资源(声明value才会注册)。该注解有四个属性和服务降级有关:
关于@SentinelResource注解的官方详细介绍:注解@SentinelResource
1.fallback降级
2.fallbackClass降级
3.defaultFallback降级
4.fallbackClass降级
exceptionsToIgnore属性:忽略异常,被忽略的异常不会触发降级
1.降级:fallback
fallback需要传入一个字符串,这个字符串就是我们降级的处理方法的方法名。降级方法必须遵循以下规则
- 返回值类型必须与原函数返回值类型一致
- 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable类型的参数用于接收对应的异常。
- fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static函数(使用fallbackClass时),否则无法解析
2.应用示例
以下仅放入熔断示例,限流一样处理即可。
/**
* 限流测试示例
* @return
*/
//value将该方法定义为sentinel的资源,blockHandlerClass指明流控处理的类,blockHandler是流控时调用的方法。
//这里需要注意处理异常的方法必须是静态方法添加static, 并需要添加sentinel的异常参数BlockException。
@RequestMapping(value = "sentinelFlowDemo", method = RequestMethod.GET)
@SentinelResource(value = "sentinelFlowDemo", blockHandlerClass = CustomBlockExceptionHandler.class, blockHandler = "sentinelFlowBlock")
public HttpResult sentinelFlowDemo() {
return HttpResult.ok("sentinelFlowDemo正常返回");
}
/**
* 熔断测试示例
* @param id
* @return
*/
@RequestMapping(value = "sentinelDegradeDemo", method = RequestMethod.GET)
@SentinelResource(value = "sentinelDegradeDemo", blockHandlerClass = SysUserController.class, blockHandler = "sentinelDegradeBlock")
public HttpResult sentinelDegradeDemo(@RequestParam("id") String id) {
if ("2".equals(id)){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return HttpResult.ok("sentinelDegradeDemo正常返回");
}
3.自定义sentinel异常
/**
* @author lfdc
* @version 1.0
* @className CustomBlockExceptionHandler
* @description 自定义sentinel异常Handler
* @company
* @date 2023-06-08 09:06:59
*/
@Slf4j
@Configuration
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
HttpResult httpResult = null;
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
log.info("urlBlock.......................");
//不同的异常返回不同的提示语
//FlowException //限流异常
//DegradeException //降级异常
//ParamFlowException //参数限流异常
//SystemBlockException //系统负载异常
//AuthorityException //授权异常
boolean einstanceof;
if (e instanceof FlowException) {
httpResult.setCode(HttpStatus.CODE_60701);
httpResult.setMsg(HttpStatus.FLOW_EXCEPTION_ERROR_MESSAGE);
} else if (e instanceof DegradeException) {
httpResult.setCode(HttpStatus.CODE_60801);
httpResult.setMsg(HttpStatus.DEGRADE_EXCEPTION_ERROR_MESSAGE);
} else if (e instanceof ParamFlowException) {
httpResult.setCode(HttpStatus.CODE_60901);
httpResult.setMsg(HttpStatus.PARAM_FLOW_EXCEPTION_ERROR_MESSAGE);
} else if (e instanceof SystemBlockException) {
httpResult.setCode(HttpStatus.CODE_601001);
httpResult.setMsg(HttpStatus.SYSTEM_BLOCK_EXCEPTION_ERROR_MESSAGE);
} else if (e instanceof AuthorityException) {
httpResult.setCode(HttpStatus.CODE_601101);
httpResult.setMsg(HttpStatus.AUTHORITY_EXCEPTION_ERROR_MESSAGE);
} else {
//其他规则
httpResult.setCode(HttpStatus.CODE_601201);
httpResult.setMsg(HttpStatus.OTHER_EXCEPTION_ERROR_MESSAGE);
}
}
public static HttpResult sentinelFlowBlock(BlockException ex){
log.info("flowException ERROR:{}",ex.getRule(),ex);
return HttpResult.error(HttpStatus.CODE_60701, HttpStatus.FLOW_EXCEPTION_ERROR_MESSAGE);
}
public static HttpResult sentinelDegradeBlock(BlockException ex){
// log.info("sentinelDegradeDemo进入熔断");
log.info("degradeException ERROR:{}",ex.getRule(),ex);
return HttpResult.error(HttpStatus.CODE_60801, HttpStatus.DEGRADE_EXCEPTION_ERROR_MESSAGE);
}
public static HttpResult sentinelParamFlowBlock(BlockException ex){
log.info("paramFlowException ERROR:{}",ex.getRule(),ex);
return HttpResult.error(HttpStatus.CODE_60901, HttpStatus.PARAM_FLOW_EXCEPTION_ERROR_MESSAGE);
}
public static HttpResult sentinelSystemBlock(BlockException ex){
log.info("systemException ERROR:{}",ex.getRule(),ex);
return HttpResult.error(HttpStatus.CODE_601001, HttpStatus.SYSTEM_BLOCK_EXCEPTION_ERROR_MESSAGE);
}
public static HttpResult sentinelAuthorityBlock(BlockException ex){
log.info("authorityException ERROR:{}",ex.getRule(),ex);
return HttpResult.error(HttpStatus.CODE_601101,HttpStatus.AUTHORITY_EXCEPTION_ERROR_MESSAGE);
}
public static HttpResult sentinelOtherBlock(BlockException ex){
log.info("otherException ERROR:{}",ex.getRule(),ex);
return HttpResult.error(HttpStatus.CODE_601201, HttpStatus.OTHER_EXCEPTION_ERROR_MESSAGE);
}
@PostConstruct
public void init() {
new CustomBlockExceptionHandler();
}
}
4.熔断数据配置
对接口进行熔断配置,快速调用会进行熔断响应
新增熔断规则
七:总结
以上为之前搭建项目时,根据版本简单集成并实现,当前仅仅为简单,但新服务宕机或者服务重启,之前的限流、降级、熔断等配置会丢失。需要重新进行配置,不仅繁琐还容易配置不当。后续文档会新增Sentinel持久化nacos操作,可以使用脚本推送、Nacos脚本配置、或者Sentinel本身项目进行持久化Nacos。