Spring Cloud Alibaba - Sentinel
Sentinel
简介
在项目配置文件中添加配置
打开控制台dashboard页面需要下载Sentinel-dashboard.jar,并启动该spring boot项目。控制页面默认为:localhost:8080 密码和用户名都为:sentinel
Sentinel控制台调用的AIP:
localhost:8719/api
Dashboard页面所有的信息都是通过这个地址进行获取的
流量控制规则
打开对应的服务面板,点击簇点链路 进入配置页面。选择对应的资源名称,点击流控按钮开始配置。
在面板中直接配置即可
配置完成后会显示配置记录信息:
当访问请求数量超过设置的阈值时,默认返回的信息为:
此信息可以自定义
tips:没有做持久化的Sentinel每次重启都不会保存做过的配置,所有的配置在无持久化的情况下都是保存在内存中的
实现限流自定义返回结果
自定义RestObject,返回结果部分信息存在此对象中:
@Builder //生成构建器模式
@Data
public class RestObject {
private int statusCode;
private String statusMessage;
private Object data;
}
编写一个类实现BlockExceptionHandler
接口:
@Slf4j
@SuppressWarnings("all")
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
log.info("UrlBolckHandler--------------------------");
RestObject restObject = null;
//不同异常返回不同提示语
if (e instanceof FlowException){
restObject = RestObject.builder().statusCode(100).statusMessage("接口限流了").build();
}else if (e instanceof DegradeException){
restObject = RestObject.builder().statusCode(101).statusMessage("服务降级了").build();
}else if (e instanceof ParamFlowException){
restObject = RestObject.builder().statusCode(102).statusMessage("热点参数限流了").build();
}else if (e instanceof SystemBlockException){
restObject = RestObject.builder().statusCode(103).statusMessage("触发系统保护规则").build();
}else if (e instanceof AuthorityException){
restObject = RestObject.builder().statusCode(104).statusMessage("授权规则不通过 ").build();
}
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
//Spring MVC 的一个JSON转换类(Jackson)
new ObjectMapper().writeValue(httpServletResponse.getWriter(),restObject);
}
}
当超过限流设定的阈值时,返回信息:
如果需要限流以后返回一个页面则需要更改MyBlockExceptionHandler
中的部分代码:
@Slf4j
@SuppressWarnings("all")
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
log.info("UrlBolckHandler--------------------------");
RestObject restObject = null;
//不同异常返回不同提示语
if (e instanceof FlowException){
restObject = RestObject.builder().statusCode(100).statusMessage("接口限流了").build();
}else if (e instanceof DegradeException){
restObject = RestObject.builder().statusCode(101).statusMessage("服务降级了").build();
}else if (e instanceof ParamFlowException){
restObject = RestObject.builder().statusCode(102).statusMessage("热点参数限流了").build();
}else if (e instanceof SystemBlockException){
restObject = RestObject.builder().statusCode(103).statusMessage("触发系统保护规则").build();
}else if (e instanceof AuthorityException){
restObject = RestObject.builder().statusCode(104).statusMessage("授权规则不通过 ").build();
}
httpServletResponse.sendRedirect("http://www.baidu.com");
//进行页面重定向,前后端分离不知道是否可行
}
}
Sentinel配置中一些参数
关联设定:
链路:
对入口资源监听,超过设定阈值限制本资源的访问。入口资源为所属组名
Sentinel服务降级RT
Sentinel服务降级异常比例和异常数
异常比例:范围为0.0 - 1.0,总请求数量*异常比例的请求数会进入服务降级时间为时间窗口设定的时间
异常数:
Sentinel热点参数规则
通过@SentinelResource注解对方法加入Sentinel监控:
@RestController
@SuppressWarnings("all")
public class SentinelController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/app")
@SentinelResource(value = "app",fallback = "fallback2",fallbackClass = MyFallbackClass.class)
public String app(@RequestParam(value = "a",required = false)String a,
@RequestParam(value = "b",required = false)String b){
System.out.println("/app/--->"+a+"---"+b);
return restTemplate.getForObject("http://nacos-discovery-provider/test",String.class);
}
}
设定的发生熔断时,调用的类:MyFallbackClass
@Component
public class MyFallbackClass {
public static String fallback(String a,String b){
System.out.println("Fall back--->"+a+"---"+b);
return "Fall back";
}
public static String fallback2(String a,String b){
System.out.println("Fall back2--->"+a+"---"+b);
return "Fall back2";
}
}
blockHandler = "block",blockHandlerClass = MyBlockHanlderClass.class处理限流
fallback = "fallback",fallbackClass = MyFallbackClass.class 处理降级
在这里,发生熔断时调用MyFallbackClass中的fallback2方法
设定规则:
超过阈值则显示:
如果设置如下:
则表示,当设定的参数值为5时,它的阈值为2。超过阈值进行限流服务降级。
Sentinel系统保护规则
Sentinel授权规则
白名单:可以调用;黑名单:不能调用
对于黑白名单的标识可以放在参数、header中。
对于下列设置:
则请求为:
localhost:9000/app?origin=order
对于这里的源origin,我们需要实现一个解析类,将其中的源名返回,如果和设定的流程应用名相同,并且设定为黑名单则该请求无法访问。返回:
解析类:
@Component
public class MyRequestOriginParser implements RequestOriginParser {
//其中获取的字段名字需要和请求中的名字相对应
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
//从参数中获取
String origin = httpServletRequest.getParameter("origin");
//从header中获取
String origin_header = httpServletRequest.getHeader("origin");
if (StringUtils.isBlank(origin)){
try {
throw new IllegalAccessException("origin参数未指定");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return origin;
}
}
请求中的origin字段也可以变为其他的,但是需要和解析类中的获取的字段一致即可(猜测)。如果使用origin_header,则请求中就不应该再带有origin=order,而是应该将该信息放入到请求header中去。
Sentinel-dashboard控制台通信原理
微服务启动后,暴露很多http接口,Sentinel-Dashboard通过调用这些接口(localhost:8719/api)进行对微服务的操控。
tips: Sentinel的一些配置
Sentinel三种保护应用的方式
这种方式用的比较少:
@GetMapping("/protect")
public String protect(@PathVariable("/app")String app){
System.out.println("protect: "+app);
ContextUtil.enter("protect","protect");//origin 服务请求来源,去掉也可以。用来实现授权规则的。
Entry entry = null;
try {
entry = SphU.entry("protect"); //分布式锁,开始保护下面的代码
//业务逻辑
int a = 10/0;
return restTemplate.getForObject("http://nacos-discovery-provider/test",String.class);
} catch (BlockException e) {
e.printStackTrace();
//手动写服务降级代码 (参考MyBlockExceptionHandler.class)
return "熔断降级了";
}catch (ArithmeticException e){
Tracer.trace(e); //对此类异常进行跟踪(trace),Sentinel才能对异常进行监控
return "除数不能为0";
}finally {
if (entry!=null){
entry.exit();
}
ContextUtil.exit();
}
}
MyFallbackClass:
@Component
public class MyFallbackClass {
public static String fallback(String a,String b){
System.out.println("Fall back--->"+a+"---"+b);
return "Fall back";
}
public static String fallback2(String a,String b){
System.out.println("Fall back2--->"+a+"---"+b);
return "Fall back2";
}
}
也可使用blockHandler+blockHandlerClass,规则与fallback+fallbackClass一致。需要注意的是:在MyBlockHandlerClass和MyFallbackClass中的所有被调用的方法都要定义为Static类型!!!
RestTemplate整合Sentinel
这样Sentinel可以对RestTemplate的调用进行保护(限流,权限......)注意注解中需要的是降级还是限流。
限流-->blockHandler+blockHandlerClass
降级-->fallback+fallbackClass
Feign整合Sentinel
第一种方法-配置fallback:
//name 远程调用服务提供者的名字 fallback 降级处理 configuration feign 配置
@FeignClient(name = "nacos-discovery-provider",
fallback = EchoServiceFallback.class,
configuration = FeignConfiguration.class)
public interface EchoService {
//getMapping 中的定义需要和对应的服务提供者中的接口调用一致
@GetMapping("/echo-feign")
public String echo();
}
EchoServiceFallback:
@SuppressWarnings("all")
public class EchoServiceFallback implements EchoService{
//对EchoService中的同名方法进行服务降级时的操作
@Override
public String echo() {
return "has been low down";
}
}
第二种方法-配置fallbackFactory:
@FeignClient(name = "nacos-discovery-provider",
// fallback = EchoServiceFallback.class,
fallbackFactory = EchoServiceFallbackFactory.class,
configuration = FeignConfiguration.class)
public interface EchoService {
//getMapping 中的定义需要和对应的服务提供者中的接口调用一致
@GetMapping("/echo-feign")
public String echo();
}
EchoServiceFallbackFactory.class:
@SuppressWarnings("all")
//FallbackFactory<EchoService>中泛型的种类为controller接口中调用的类型
public class EchoServiceFallbackFactory implements FallbackFactory<EchoService> {
@Override
public EchoService create(Throwable throwable) {
return new EchoService() {
//对其中的所有方法设置降级实现
@Override
public String echo() {
return "from fallbackFactory";
}
};
}
Sentinel持久化
原始模式
拉模式
Pull模式
pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的 WritableDataSourceRegistry
中。以本地文件数据源为例:
public class FileDataSourceInit implements InitFunc {
@Override
public void init() throws Exception {
String flowRulePath = "xxx";
ReadableDataSource<String, List<FlowRule>> ds = new FileRefreshableDataSource<>(
flowRulePath, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
);
// 将可读数据源注册至 FlowRuleManager.
FlowRuleManager.register2Property(ds.getProperty());
WritableDataSource<List<FlowRule>> wds = new FileWritableDataSource<>(flowRulePath, this::encodeJson);
// 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
// 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
WritableDataSourceRegistry.registerFlowDataSource(wds);
}
private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
}
本地文件数据源会定时轮询文件的变更,读取规则。这样我们既可以在应用本地直接修改文件来更新规则,也可以通过 Sentinel 控制台推送规则。以本地文件数据源为例,推送过程如下图所示:
首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。使用 pull 模式的数据源时一般不需要对 Sentinel 控制台进行改造。
这种实现方法好处是简单,不引入新的依赖,坏处是无法保证监控数据的一致性。
实操:自定义类FileDataSourceInit:
@SuppressWarnings("all")
public class FileDataSourceInit implements InitFunc {
@Override
public void init() throws Exception {
//可以根据需要指定规则文件的存放位置
String ruleDir = "E:\\software\\JAVA\\springcloud-alibaba\\package\\Sentinel-dashboard";
String flowRulePath = ruleDir+"/flow-rule.json";
String degradeRulePath = ruleDir+"/degrade-rule.json";
String paramFlowRulePath = ruleDir+"/param-flow-rule.json";
String systemRulePath = ruleDir+"/system-rule.json";
String authorityRulePath = ruleDir+"/authority-rule.json";
this.mkdirIfNotExist(ruleDir);
this.createFileIfNotExist(flowRulePath);
this.createFileIfNotExist(degradeRulePath);
this.createFileIfNotExist(paramFlowRulePath);
this.createFileIfNotExist(systemRulePath);
this.createFileIfNotExist(authorityRulePath);
//流控规则:可读数据源
ReadableDataSource<String, List<FlowRule>> ds = new FileRefreshableDataSource<>(
flowRulePath, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
);
// 将可读数据源注册至 FlowRuleManager.
FlowRuleManager.register2Property(ds.getProperty());
WritableDataSource<List<FlowRule>> wds = new FileWritableDataSource<>(flowRulePath, this::encodeJson);
// 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
// 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
WritableDataSourceRegistry.registerFlowDataSource(wds);
//降级规则,可读数据源
ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
degradeRulePath, source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {})
);
// 将可读数据源注册至 FlowRuleManager.
DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(degradeRulePath, this::encodeJson);
// 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
// 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
//热点参数规则:可读数据源
ReadableDataSource<String,List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<List<ParamFlowRule>>(
paramFlowRulePath,source-> JSON.parseObject(source,new TypeReference<List<ParamFlowRule>>(){})
);
ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
//热点参数规则:可写数据源
WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<List<ParamFlowRule>>(
paramFlowRulePath,
this::encodeJson
);
ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
//系统规则:可读数据源
ReadableDataSource<String,List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<List<SystemRule>>(
systemRulePath,source-> JSON.parseObject(source,new TypeReference<List<SystemRule>>(){})
);
SystemRuleManager.register2Property(systemRuleRDS.getProperty());
//系统规则:可写数据源
WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<List<SystemRule>>(
systemRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
//授权规则:可读数据源
ReadableDataSource<String,List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<List<AuthorityRule>>(
authorityRulePath,source-> JSON.parseObject(source,new TypeReference<List<AuthorityRule>>(){})
);
AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
//授权规则:可写数据源
WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<List<AuthorityRule>>(
authorityRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
}
private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
private void mkdirIfNotExist(String filePath) throws IOException{
File file = new File(filePath);
if (!file.exists())
{
file.mkdirs();
}
}
private void createFileIfNotExist(String filePath) throws IOException{
File file = new File(filePath);
if (!file.exists())
{
file.createNewFile();
}
}
}
然后在项目的 resources/META-INF/services
目录下创建文件,名为 com.alibaba.csp.sentinel.init.InitFunc
,内容为:
# 改成上面FileDataSourceInit的包名类名全路径即可。
#(copy reference)
com.nacos.consumer.nacosconsumer.sentinel.FileDataSourceInit
优点
- 简单易懂
- 没有多余依赖(比如配置中心、缓存等)
缺点
- 由于规则是用 FileRefreshableDataSource 定时更新的,所以规则更新会有延迟。如果FileRefreshableDataSource定时时间过大,可能长时间延迟;如果FileRefreshableDataSource过小,又会影响性能;
- 规则存储在本地文件,如果有一天需要迁移微服务,那么需要把规则文件一起迁移,否则规则会丢失。
推模式
我们提供了 ZooKeeper, Apollo, Nacos 等的动态数据源实现。以 ZooKeeper 为例子,如果要使用第三方的配置中心作为配置管理,您需要做下面的几件事情:
- 实现一个公共的 ZooKeeper 客户端用于推送规则,在 Sentinel 控制台配置项中需要指定 ZooKeeper 的地址,启动时即创建 ZooKeeper Client。
- 我们需要针对每个应用(appName),每种规则设置不同的 path(可随时修改);或者约定大于配置(如 path 的模式统一为
/sentinel_rules/{appName}/{ruleType}
,e.g.sentinel_rules/appA/flowRule
)。 - 规则配置页需要进行相应的改造,直接针对应用维度进行规则配置;修改同个应用多个资源的规则时可以批量进行推送,也可以分别推送。Sentinel 控制台将规则缓存在内存中(如
InMemFlowRuleStore
),可以对其进行改造使其支持应用维度的规则缓存(key 为 appName),每次添加/修改/删除规则都先更新内存中的规则缓存,然后需要推送的时候从规则缓存中获取全量规则,然后通过上面实现的 Client 将规则推送到 ZooKeeper 即可。 - 应用客户端需要注册对应的读数据源以监听变更,可以参考 相关文档。
从 Sentinel 1.4.0 开始,Sentinel 控制台提供 DynamicRulePublisher
和 DynamicRuleProvider
接口用于实现应用维度的规则推送和拉取,并提供了相关的示例。Sentinel 提供应用维度规则推送的示例页面(/v2/flow
),用户改造控制台对接配置中心后可直接通过 v2 页面推送规则至配置中心。改造详情可参考 应用维度规则推送示例。
部署多个控制台实例时,通常需要将规则存至 DB 中,规则变更后同步向配置中心推送规则。
实操:
在组件中有的话就可以不用添加
#基于Nacos配置中心进行规则持久化
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=localhost:80
spring.cloud.sentinel.datasource.ds1.nacos.data-id=${spring.application.name}.json
spring.cloud.sentinel.datasource.ds1.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow
ds1为自定义名字,需要报纸配置信息中的一致
Data id :nacos-discovery-consumer.json 项目名称.json
Group:DEFAULT_GROUP
配置格式:JSON
配置内容:根据上图的格式,自行配置。count = QPS 配置
流程:从Nacos读取配置,缓存到本地。Sentinel 从本地读取配置完成持久化操作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律