Sentinel与Hystrix区别
● 隔离策略:
信号量隔离,一种轻量级的隔离方式,通过控制并发请求的数量来保护系统资源的,适用于对性能要求较高的场景。
线程池隔离,通过为每个依赖服务分配独立的线程池来实现隔离,虽然提供了更好的隔离性,但会带来额外的线程切换开销。
● 熔断降级策略:
慢调用比例或异常比例的熔断策略,可以根据请求的响应时间或异常比例来决定是否触发熔断,灵活性较高。
失败比率的熔断策略。当请求的失败率达到一定阈值时,触发熔断机制。
两种策略相比较,前者是预防服务异常,后者是当服务产生异常的时候,才触发处理。
● 实时指标实现:
采用的是滑动窗口来实现,可以提供更精确的实时数据,帮助系统更好地做出流量控制和熔断决策。
hystrix,基于RxJava的滑动窗口来统计实现,方式相对复杂;
● 配置规则:
支持多种数据源,比如文件、Nacos、Zookeeper等动态配置形式;两者相比,sentinel灵活性和实时性更高。
● 扩展性:
sentinel,提供多个扩展点,供用户自定义;
hystrix,通过插件提供扩展,扩展性相对较弱;
● 基于注解的支持:
都支持注解,但hystrix功能有限;
● 限流:
Sentinel,支持基于 QPS 的限流(每秒查询率)、基于调用关系的限流(如根据调用链路进行限流)、支持慢启动(主要用于系统初始化或者流量突然增加的场景,过程中系统允许通过的流量会按照一定的斜率逐渐上升)和匀速排队模式(较为严格的流量控制方式,它主要用于处理突发流量,以固定的速率对请求进行处理),适用于不同的流量控制场景。
Hystrix, 限流功能有限,主要依赖线程池的大小来控制并发请求。
两种对比,Sentinel的限流方式更多;
● 流量整形:
Sentinel,支持流量整形,可以通过匀速排队模式来控制请求的通过速率,避免突发流量对系统的冲击。
hystrix,不支持流量整形。
● 系统的自我保护:
Sentinel,支持系统自适应保护,可以根据系统的实时负载和资源使用情况动态调整流量控制策略。
hystrix,不支持系统的自我保护。
● 控制台:
Sentinel,提供了开箱即用的控制台,支持规则配置、秒级监控、机器发现等功能,便于运维和监控。
hystrix,控制台功能相对简单;
● 常见框架的适配:
Sentine,支持多种框架,如 Servlet、Spring Cloud、Dubbo、gRPC 等,适配性较好。
hystrix,主要支持 Servlet 和 Spring Cloud Netflix,适配性相对较弱。
Sentinel概述
初识Sentinel
🍎 事前准备
雪崩问题:
在微服务的调用链中,如果某个节点出现问题,会导致整个微服务链出现问题;
解决方式:
1 超时处理,超过固定的时间,直接返回错误,不做无休止等待;
2 舱壁模式,限定每个业务能使用的线程数,避免消耗整个tomcat资源,即线程隔离;
3 熔断降级,断路器,统计业务异常比例,超过阈值,直接拦截该请求;
4 流量控制,限制业务的qps,避免服务因流量的突增而故障; 预防故障出现;
是什么?
Sentinel 是阿里中间件团队研发的面向分布式服务架构的轻量级、高可用流量控制组件,于今年2018年正式开源。
主要以流量为切入点,从流量控制、熔断降级、系统负载保护、实时监控和控制台 等多个维度来帮助用户提升服务的稳定性。
sentinel 服务启动
参考链接:https://developer.aliyun.com/article/1476696
具体过程:
下载sentinel-dashboard-1.8.3.jar
所属文件夹打开cmd 执行java -jar sentinel-dashboard-1.8.3.jar
出现 Started DashboardApplication in 5.224 seconds (JVM running for 5.819) 启动成功;
访问:localhost:8080
可以得到sentinel控制台界面,账号密码均是sentinel;
🍍 问题1:启动失败 java -jar
问题描述:
java.lang.IllegalStateException: Cannot load configuration class:
com.alibaba.csp.sentinel.dashboard.DashboardApplication
Caused by: java.lang.ExceptionInInitializerError: null
Caused by: org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @7bf58d89
解决:
将环境变量的path 将C:\Program Files\Common Files\Oracle\Java\javapath; 该地址去掉了;
参考博客是:https://blog.csdn.net/m0_37450089/article/details/121503987
🍍 问题2 修改sentinel基础信息
修改sentinel服务相关信息,基于java -jar运行命令进行修改
服务端口
-Dserver.port=8090
控制台账号
-Dserver.dashboard.auth.username=xxx
控制台密码
-Dserver.dashboard.auth.password=xxx
实现修改是:
java + 上述内容 + -jar sentinel-dashboard-1.8.x.jar
其余配置信息,sentinel官方网站--wiki--控制台
sentinel整合springboot
🍇 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2023.0.1.2</version>
</dependency>
🍇 application配置文件
spring:
# sentinel配置
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel 控制台地址
port: 8719 # Sentinel 连接端口
application:
name: applicationName # 根据运行项目自定义名字
🍇 访问端点,触发监控
访问服务的接口;
Sentinel总述
Sentinel是阿里开源的项目,提供了流量控制、热点流控、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。
Sentinel 的使用可以分为两个部分:
控制台(Dashboard):控制台主要负责管理和推送各种规则、集群限流分配管理、机器发现等。
核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 7 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
Nacos概述
概念
定义:
Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的配置管理、服务发现和服务治理的综合解决方案。
特性:
服务发现与服务健康检查;动态配置管理;动态DNS管理;服务与元数据管理;
🍇 基础配置信息
🍅 定义:
配置 = 项目启动所需要的信息;
配置中心 = 管理所有微服务所需的配置信息;
🍅 思考:
配置中心-微服务之间配置信息如何获取?
工作人员发布配置信息到配置中心;
微服务通过网络协议获取到所需配置信息;
如果配置信息发生更新,会给各个微服务发送更新通知;
微服务得到更新通知后,会获取最新配置信息;
市面主流配置中心的对比
spring cloud config = 是spring cloud官方推出的配置中心;
apollo = 携程开源的配置中心;
Nacos = 阿里开源的配置中心;
nacos配置
代码实现
🍋 发布配置信息
🍋 通过java程序获取配置
配置nacos客户端api依赖
具体代码:
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.StringReader;
import java.util.Map;
import java.util.Properties;
@Slf4j
@Configuration
@ConfigurationProperties(prefix = "nacos")
public class NacosConfig {
private String serverAddr ="127.0.0.1:8848"; //nacos 地址
private String dataId ="nacos_simple_demo.yaml"; //Data Id
private String group ="DEFAULT_GROUP"; //Group
@Autowired
private ConfigurableEnvironment environment;
@PostConstruct
public void init() throws NacosException, IOException {
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
ConfigService configService = NacosFactory.createConfigService(properties);
String content = configService.getConfig(dataId, group, 5000);
// 解析配置内容为 Properties
Properties configProperties = new Properties();
try {
configProperties.load(new StringReader(content));
} catch (IOException e) {
log.error("Failed to load Nacos config", e);
throw new RuntimeException(e);
}
// 将配置注入到 Spring 环境中
MapPropertySource propertySource = new MapPropertySource("nacosConfig", (Map) configProperties);
environment.getPropertySources().addLast(propertySource);
log.info("Nacos Config Loaded: " + content);
}
}
配置文件
# 该内容写在bootstrap中
spring:
cloud:
nacos:
config:
# Nacos 服务器地址
server-addr: localhost:8848
# 若使用了命名空间,填写命名空间 ID
namespace: cce62d68-51e5-4716-9968-a52bca7f8359
name: nacos_simple_demo.yaml
group: DEFAULT_GROUP
file-extension: properties # 确保格式匹配
application:
# 应用名称
name: test1Project
main:
allow-bean-definition-overriding: true
# 解决:The bean 'nacosRefreshProperties', defined in class path resource [com/alibaba/cloud/nacos/NacosConfigAutoConfiguration.class], could not be registered.
🍎 结论
# application.yml配置文件
Data ID 默认是 ${prefix}-${spring.profiles.active}.${file-extension}。
prefix = spring.application.name的值;
file-extension = spring.cloud.nacos.config.file-extension的值,只支持properties和yaml,默认是properties;
如果spring.profiles.active不存在,则会变成${prefix}.${file-extension} ;
data-id 这个key,也可以是name
多配置文件
🍇 配置形式1——nacos多个配置文件配置
spring:
cloud:
nacos:
config:
# Nacos 服务器地址
server-addr: 127.0.0.1:8848
group: dev
namespace: xxxx
shared-configs: # 共享配置
// shared-configs必须这样写;
- data-id: my-application.properties
group: dev
refresh: true
file-extension: properties
extension-configs: # 扩展配置
// extension-configs 必须这样写;
- data-id: flow_rules.json
group: dev
refresh: true
file-extension: json
application:
# 应用名称
name: my-application
🍇 配置形式2——sentinel多个流控文件配置
spring:
cloud:
nacos:
config:
# Nacos 服务器地址
server-addr: 127.0.0.1:8848
# Nacos 配置的 Group
group: prod
# 若使用了命名空间,填写命名空间 ID
namespace: xxx
sentinel:
datasource:
flow-rules: # 自定义数据源名称
nacos:
server-addr: localhost:8848
dataId: sentinel-rules
groupId: prod
namespace: xxx
rule-type: flow
param-rules: # 热点限流
nacos:
server-addr: localhost:8848
dataId: sentinel-paramRules
groupId: prod
namespace: xxx
rule-type: param_flow
application:
# 应用名称
name: application
服务发现
配置
// 通常在application.yml中
spring:
application:
name: application # 服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos 地址
service: application # 注册到 Nacos 的服务名
namespace: xxx
启动
🍋 单机部署:
🍇 nacos下载;
从git下载源码,然后mvn编译;
下载xx.zip包=编译后的压缩包,直接解压运行;
🍇 进入nacos所在文件夹的bin目录下;D:\soft\nacos\nacos-server-2.5.0\nacos\bin
🍇 运行startup.cmd -m standalone 实现单机部署,默认端口是8848
🍇 访问nacos服务:http://localhost:8848/nacos 默认账密为nacos-nacos
🍇 测试nacos启动无误
通过curl命令行工具,用HTTP协议测试;
windows系统通过where curl得到所在目录,在c:/Windwos/System32
版本信息:
curl 8.10.1 (Windows) libcurl/8.10.1 Schannel zlib/1.3 WinIDN
Release-Date: 2024-09-18
发布配置:
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test&content=HelloWorld"
执行后返回true,说明实现;
获取配置:
curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test"
执行后命令行输出配置信息:
配置存在,则为 HelloWorld
配置不存在,则为 config data not exist
🍇 服务关闭
关闭当前cmd窗口
🍋 修改数据存储方式
单机部署,通常采用嵌入式数据库存储(Derby),不方便数据备份与查询,故此修改为mysql数据库;
数据库变更从Derby到mysql:
下载mysql数据库,5.6+,8以下;
创建nacos_config数据库,执行数据库创建sql文件;
现有博客都是1.x.x版本:(执行nacos-mysql.sql文件);本地测试采用2.5.0版本(执行mysql-schema.sql文件);
配置application.properties文件;在目录:D:\soft\nacos\nacos-server-2.5.0\nacos\conf
具体配置信息:
# spring.datasource.platform=mysql
# spring.sql.init.platform=mysql
### Count of DB:
# db.num=1
### Connect URL of DB:
# db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
# db.user.0=nacos
# db.password.0=nacos
🍋 思考:
Nacos Server 的配置数据是存在哪里呢?
D:\soft\nacos\nacos-server-2.5.0\nacos\data\derby-data\文件夹中;
Nacos Server的数据源是Derby还是MySQL由运行模式决定;
standalone=Derby;cluster=MySQL;
安装curl?
参考博客:https://blog.csdn.net/g310773517/article/details/143906337
🍋 集群部署:
Nacos Server需要配置MySQL数据库;
需要采用Nginx转发到多个节点,最前面挂域名;
使用
流控
问题
🍐nacos配置不起效问题
描述:
springboot项目中configurationProperties配置的nacos如下:
private String serverAddr ="127.0.0.1:8848"; //nacos 地址
private String dataId ="nacos_simple_demo.yaml"; //Data Id
private String group ="DEFAULT_GROUP"; //Group
但项目运行显示的nacos配置如下:
springboot的启动日志是[fixed-localhost_8848] [add-listener] ok, tenant=, dataId=test1Project.properties, group=DEFAULT_GROUP, cnt=1
两者不符合;
分析:
如果项目中引入了 spring-cloud-starter-alibaba-nacos-config,它会自动从 bootstrap.yml 或环境变量中读取 dataId。
如果 bootstrap.yml 中定义了 spring.cloud.nacos.config.name 或 spring.application.name,这些值会被用作默认的 dataId。
解决:
将项目中的spring.application.name注释掉;
🍐 nacosRefreshProperties could not be registered
// 在application.yml文件配置如下内容即可解决;但本质问题仍旧没有解决;
spring:
# 后面的bean会覆盖前面相同名称的bean
main:
allow-bean-definition-overriding: true
限流规则+控制台
控制台涉及到:实时监控、簇点链路、流控规则、熔断规则、热点规则、系统规则、授权规则、集群流控、机器列表等;
实时监控
簇点链路
簇点链路:
项目内的调用链路,即访问controller--service--mapper,这就是调用链路;
而sentinel监控不会监控所有,而只会监控springMVC的每一个端点(Endpoint),即controller方法;
请求链路的根是:所有springmvc项目的根是 sentinel-spring-web-context
而每一个controller都在该根下;
流控规则
熔断规则
热点规则限流
概述
基础参数限流
🍇依赖导入
🍇 基础sentinel配置
上述两个过程均采用sentinel整合springboot的操作实现
🍇sentinel配置
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Configuration // 配置类
public class SentinelConfig {
private static String LimitApikey = "a";
private static String LimitSource = "hello";
@PostConstruct
public void initFlowRules() {
initFlowRulesApikey();
}
// 测试sentinel启动正常
public void initFlowRulesTest(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("hello"); // 资源名称
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 等级
rule.setCount(10); // 阈值 限制 QPS 为 1
rule.setLimitApp("default"); // 限制请求来源 表示这个规则对所有来源的请求都有效。
rules.add(rule);
FlowRuleManager.loadRules(rules);
System.out.println("initFlowRulesTest end");
}
// 测试根据请求参数可以直接限制流控
public void initFlowRulesApikey(){
List<ParamFlowRule> rules = new ArrayList<>();
ParamFlowRule a = new ParamFlowRule(SentinelConfig.LimitSource)
.setCount(Double.MAX_VALUE) // 允许访问的数量
.setParamIdx(0) // 假设apikey是URL参数列表中的第一个参数
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setDurationInSec(1) // 持续时间,单位为秒
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT) // 即当流量达到阈值时,直接拒绝多余的请求。
.setParamFlowItemList(
Collections.singletonList(new ParamFlowItem()
.setObject("a")
.setCount(0))
);
rules.add(a);
ParamFlowRuleManager.loadRules(rules);
}
}
// 备注:
// rule.setCount,是针对资源 "SentinelConfig.LimitSource" 的所有参数值的默认限流阈值设置。也就是说,当一个参数值没有在 ParamFlowItem 里被单独配置限流规则时,就会使用这个默认值。
🍇实现方法代码
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.unicom.opn.api.core.dto.base.BaseParams;
import com.unicom.opn.api.core.dto.base.ResultDto;
import com.unicom.opn.api.core.enums.ResultCodeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@Service
public class TestHandler {
@SentinelResource(value = "hello",blockHandler = "handleBlockForHello")
// 设置资源
public ResultDto hello(String apikey) {
return ResultDto.result(ResultCodeEnum.SUCCESS,"Hello,Sentinel!");
}
public ResultDto handleBlockForHello(String apikey, BlockException e) {
log.error("sentinel block",e);
return ResultDto.result(ResultCodeEnum.ApikeyError);
}
}
🍇调用接口
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.unicom.opn.api.core.annotation.RequestJson;
import com.unicom.opn.api.core.controller.core.TestHandler;
import com.unicom.opn.api.core.dto.base.BaseParams;
import com.unicom.opn.api.core.dto.base.ResultDto;
import com.unicom.opn.api.core.dto.req.PrefetchReq;
import com.unicom.opn.api.core.dto.req.PrefetchWechatReq;
import com.unicom.opn.api.core.enums.BusinessEnum;
import com.unicom.opn.api.core.enums.ResultCodeEnum;
import com.unicom.opn.api.core.trace.ServiceId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.alibaba.csp.sentinel.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@RestController
public class TestController {
@Autowired
private TestHandler testHandler;
@RequestMapping("/hello")
@ServiceId(BusinessEnum.TEST_XHJ)
public ResultDto hello(@RequestParam("apikey") String apikey){
return testHandler.hello(apikey);
}
}
🍎总结问题1
问题描述:
上述相同代码,只更换依赖就不能正常拦截请求了;(暂时未探索出来是什么问题)
可以正常使用的是:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2023.0.1.2</version>
</dependency>
不可以正常使用的:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.8.6</version>
</dependency>
🍎总结问题2:
情景1:
通过postman发起的请求:http://127.0.0.1:8080/hello?apikey=b
多次请求,返回结果有时成功,有时失败;
已知拦截规则是apikey=a拦截;
出现频次,请求5次就会出现异常拦截的情况;
解决:
修改ParamFlowRule的setDurationInSec,设置持续时间这个属性,将原来的1s改成10s 可以解决该问题,但具体该问题的上线是多少次请求以及什么频次内会出现上述问题 还未进行测试;
情景2:
修改ParamFlowRule的setDurationInSec,设置持续时间这个属性,将原来的1s改成10s
测试模拟 1s 访问1500次 即循环1500次 每次间隔0.66ms
解决:
已完成测试,没有出现异常拦截的情况;
对象参数限流——新增设限参数
🍇 sentinel版本
本地运行sentinel为1.8.3版本;
idea中使用spring-cloud-starter-alibaba-sentinel该依赖;
🍇 依赖导入以及application配置都保持和基础参数限流一致;
🍇 xxxController方法参数输入为对象
@RequestMapping("/hello1")
@ServiceId(BusinessEnum.TEST_XHJ)
public ResultDto hello1(TestObject testObject){
return testHandler.hello1(testObject,testObject.getApikey());
} // 需要限制的参数 testObject.getApikey()
🍇 修改xxxhandler方法参数,将需要限制的对象属性作为参数输入
@SentinelResource(value = "test1",blockHandler = "handleBlockForHello1")
public ResultDto hello1(TestObject testObject,String apikey) {
return ResultDto.result(ResultCodeEnum.SUCCESS,"Hello,Sentinel!");
}
public ResultDto handleBlockForHello1(TestObject testObject,String apikey,BlockException e) {
log.error("sentinel block1 :",e);
return ResultDto.result(ResultCodeEnum.ApikeyError);
}
🍇 sentinelConfig配置
public void limitApikey(){
List<ParamFlowRule> rules = new ArrayList<>();
ParamFlowRule a = new ParamFlowRule("test1")
.setCount(Double.MAX_VALUE) // 允许访问的数量
.setParamIdx(1)
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setDurationInSec(10) // 持续时间,单位为秒
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT) //即当流量达到阈值时,直接拒绝多余的请求。
.setParamFlowItemList(
Collections.singletonList(new ParamFlowItem()
.setObject("a")
.setCount(0))
);
rules.add(a);
ParamFlowRuleManager.loadRules(rules);
}
问题1:
假设对象的参数apikey不存在时,这个会报什么错误?
可以给对象参数的属性添加注解@NotBlack(message = "")
这样就会被代码直接拦截;
对象参数限流——修改toString
参考内容:
https://blog.csdn.net/qq_28618027/article/details/131476599
https://cloud.tencent.com/developer/article/1754662
该方式最终没有实现。
博客跟学:
https://cloud.tencent.com/developer/article/1754662
本地版本是:Maven:com.alibaba.csp:sentinel-parameter-flow-control:1.8.6
checkFlow方法 = com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot#checkFlow
passCheck方法 = com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker#passCheck
该版本的代码中,passCheck代码没有针对 博客中的“根据索引获取方法参数的值” 以及 “针对参数为对象的情况,通过实现ParamFlowArgument重写paramFlowKey”
passClusterCheck = QPS限流并集群流控
passLocalCheck = 非集群流控
🍎 报错情形1:
依赖配置文件:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.8.6</version>
</dependency>
<!-- 添加 Sentinel 核心依赖 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.6</version> <!-- 可以根据需要选择合适的版本 -->
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-webmvc-adapter</artifactId>
<version>1.8.6</version>
</dependency>
将上述配置文件的版本号改成1.8.3 会出现如下问题:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sentinelConfig': Invocation of init method failed;
nested exception is java.lang.NoClassDefFoundError: com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sentinelConfig': Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule Caused by: java.lang.NoClassDefFoundError: com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule Caused by: java.lang.ClassNotFoundException: com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule
1.8.6版本也出现了上述问题;
已知 本地运行的sentinel版本是1.8.3
解决:
修改后的配置文件:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2023.0.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.8.6</version>
</dependency>
<!-- 添加 Sentinel 核心依赖 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.6</version> <!-- 可以根据需要选择合适的版本 -->
</dependency>
🍎 报错内容2:
在testObject对象中,实现ParamFlowArgument接口,重写paramFlowKey方法;
报错:
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'testController': Lookup method resolution failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class
Caused by: java.lang.IllegalStateException: Failed to introspect Class [com.unicom.opn.api.core.controller.TestController]
Caused by: java.lang.NoClassDefFoundError: com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowArgument
说的是ParamFlowArgument没有找到,是不是因为运行的sentinel是1.8.3 而使用的是1.8.6 导致的问题? 不是该问题
该问题暂时没有想法去修改;
根据sentinel官网文档的说明,发现paramFlowRule的paramFlowItemList只支持基本数据类型和字符串类型,是不支持对象数据类型的。
集群流控
概述
🍇流量控制(flow control):
其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
原理:
Sentinel以Bucket(桶)为单位记录一个时间窗口内的请求总数、异常总数、总耗时等指标数据。
一个Bucket可以是记录一秒内的数据,也可以是10毫秒内的数据,称这个时间窗口为Bucket的统计单位,由使用者自定义。
单机模式下:如果设置某接口的 QPS 阈值为 100,每个实例会独立统计自己的 QPS,当某个实例的 QPS 超过 100 时,该实例会触发限流。
各实例之间互不影响 ,因为它们的统计桶是隔离的。
集群模式下:所有实例的流量统计会通过一个中心化存储(如 Redis)共享同一个统计桶 。例如:如果设置某接口的全局 QPS 阈值为 100,所有实例的请求会汇总到中心化的统计桶中,总 QPS 超过 100 时,所有实例都会触发限流。
🍇集群流控:
用来统计数据的称为 Sentinel 的 token server,其他的实例作为 Sentinel 的 token client 会向 token server 去请求 token,如果能获取到 token,则说明当前的 qps 还未达到总的阈值,否则就说明已经达到集群的总阈值,当前实例需要被 block;
两种身份:
token client:集群流控客户端,用于向所属 token server 通信请求 token。集群限流服务端会返回给客户端结果,决定是否限流。
token server:即集群流控服务端,处理来自 token client 的请求,根据配置的集群规则判断是否应该发放 token(是否允许通过)。
🍇实现方案:
支持限流规则和热点规则两种规则,并支持两种形式的阈值计算方式:
集群总体模式:即限制整个集群内的某个资源的总体 qps 不超过此阈值。
单机均摊模式:单机均摊模式下配置的阈值等同于单机能够承受的限额,token server 会根据连接数来计算总的阈值(比如独立模式下有 3 个 client 连接到了 token server,然后配的单机均摊阈值为 10,则计算出的集群总量就为 30),按照计算出的总的阈值来进行限制。这种方式根据当前的连接数实时计算总的阈值,对于机器经常进行变更的环境非常适合。
🍇 token server部署方式:
独立部署:单独启动一个token server来接收所有token client的请求;
存在问题 = 当token server服务瘫痪,则其余的token client会自动退化成本地流控的模式;
嵌入式部署:作为内置的token server和服务在同一进程中启动。集群中的各个实例都是对等的,token server和token client可以随时变换身份。
Sentinel 为我们提供了一个 api 来进行 token server 与 token client 的切换:
http://<ip>:<port>/setClusterMode?mode=<xxx>
其中mode值包括:0=client;1=server;-1=关闭;
🍇 结合nacos实现部署:
原因:采用sentinel控制台配置token server和token client以及流控规则,会存在流控规则不能持久化、server和client配置丢失的问题;
独立部署
嵌入式部署
server相关依赖;
client相关依赖;
sentinel+Nacos流控
项目配置
🍉 springboot项目配置
spring:
cloud:
sentinel:
transport:
port: 8719
dashboard: localhost:8080
datasource:
ds:
nacos:
server-addr: localhost:8848
data-id: xxx_xxx_sentinel # 这里不能使用-如果需要划分得采用_
group-id: DEFAULT_GROUP
refresh-enabled: true
rule-type: flow
data-type: json
application:
name: opn-api
🍇 其中rule-type的值:
flow=流量控制规则 param-flow=热点参数限流规则;degrade=降级规则;system=系统规则;authority=授权规则;
🍇 其中datasource下一级ds需要根据不同的xx规则,进行再区分;
datasource:
flow:
nacos:
param-flow:
nacos:
🍎 注意:
sentinel按照上述配置,数据库的配置规则改成nacos规则,则代码中编写的sentinel规则就会失效,且在sentinel控制台不显示;
对于接口类型的资源,在配置规则的时候,需要写成全url,比如“/hello” 不能是“hello”
🍉 依赖配置
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2023.0.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2023.0.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.8</version>
</dependency>
🍇 项目1可以运行但项目2不能运行
🍟报错信息1:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.context.ApplicationContextInitializer : com.alibaba.cloud.sentinel.custom.context.SentinelApplicationContextInitializer
Caused by: java.lang.UnsupportedClassVersionError: com/alibaba/cloud/sentinel/custom/context/SentinelApplicationContextInitializer has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0
将spring-cloud-starter-alibaba-nacos-config 和 spring-cloud-starter-alibaba-sentinel版本号
从2023.0.1.2 改成 2.2.6.RELEASE 又出现下列报错;
🍟 报错信息2:
The bean 'nacosRefreshProperties', defined in class path resource [com/alibaba/cloud/nacos/NacosConfigAutoConfiguration.class], could not be registered. A bean with that name has already been defined in URL [jar:file:/D:/soft/maven/responsitory/com/alibaba/cloud/spring-cloud-starter-alibaba-nacos-config/2.2.6.RELEASE/spring-cloud-starter-alibaba-nacos-config-2.2.6.RELEASE.jar!/com/alibaba/cloud/nacos/refresh/NacosRefreshProperties.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
改成2021.1
可运行配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.dianping.cat</groupId>
<artifactId>cat-client</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2021.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-webmvc-adapter</artifactId>
<version>1.8.3</version> <!-- 确保与 spring-cloud-starter-alibaba-sentinel 兼容 -->
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.0</version>
</dependency>
<!-- Sentinel+Nacos配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
项目运行
🍇 初始化一个springboot基础项目;
参考博客:
https://blog.csdn.net/yuran06/article/details/122012790
https://blog.csdn.net/qq_33210338/article/details/118961132
nacos和sentinel规则互相同步的实现:https://blog.csdn.net/zhuocailing3390/article/details/138139180
🍇 springboot项目配置sentinel+nacos 在application.yml中
在前文中描述得到。
🍇 sentinel运行
cmd到sentinel所在目录;
运行命令:java -jar sentinel-dashboard-x.x.x.jar
或者:Java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-x.x.x.jar
🍇 nacos运行
参考:https://blog.csdn.net/crazymakercircle/article/details/143141533
定义:是Naming and configuration service的简写,是阿里巴巴开发的一个开源分布式服务发现和配置管理平台;
nacos下载:https://github.com/alibaba/nacos/releases
解压后,到目录D:\soft\nacos\nacos-server-2.5.0\nacos\bin中
在cmd中,输入startup.cmd -m standalone
访问nacos控制台:http://localhost:8848/nacos
🍇 运行springboot项目即可
实战案例
以请求路径限流
🍇 测试controller类
@RestController
public class TestController {
@GetMapping("/hello")
@SentinelResource("helloResource")
// @SentinelResource 注解将 /hello 接口标记为一个受保护的资源,资源名为 helloResource。
public String hello(){
return "Hello,Sentinel!";
}
}
🍇 Sentinel 配置类
@Configuration
public class SentinelConfig {
@PostConstruct
public void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("helloResource");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1); // 限制 QPS 为 1
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
请求路径限流-修改返回结果
🍇 新增类实现implements BlockExceptionHandler该接口;
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {
// 设置响应状态码为 200
response.setStatus(HttpServletResponse.SC_OK);
// 设置响应内容类型为 JSON
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
ResponseDto result = new ResponseDto(ResultCodeEnum.SeqOverError);
JSONObject jsonData = new JSONObject(result);
// 将 JSON 数据写入响应
try (PrintWriter out = response.getWriter()) {
out.print(jsonData);
out.flush();
}
}
}
🍇 修改依赖包版本号
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.6</version>
</dependency>