1.Sentinel控制台的安装
下载地址:
Releases · alibaba/Sentinelx
下载后是一个jar包
进入目录
CMD命令
java -jar "sentinel-dashboard-1.8.8 .jar"
如果发生了端口冲突则使用以下命令启动 修改端口号为8090
java -Dserver.port=8090 -jar "sentinel-dashboard-1.8.8 .jar"
启动成功:
访问控制台
Sentinel Dashboard
账号密码默认都是sentinel
进入首页:
此时控制台已经安装完成
2.整合微服务
sentinel 的 starter 依赖:
将此sentinel依赖引入项目中
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
yml配置:
spring:cloud:sentinel:transport:# 添加sentinel的控制台地址dashboard: 127.0.0.1:8090
如果是mvc的接口无需 @SentinelResource. 如果是业务方法, 需要 @SentinelResource
@SentinelResource("hello")public String hello() {return "Hello Sentinel";}
@SentinelResource 注解用来标识资源是否被限流、降级。上述例子上该注解的属性 hello 表示资源名。 @SentinelResource 还提供了其它额外的属性如 blockHandler,blockHandlerClass,fallback 用于表示限流或降级的操作,更多内容可以参考 Sentinel 注解支持。
启用feign对sentinel的支持:
#启用feign对sentinel的支持
feign:sentinel:enabled: true # 启用 Sentinel 对 Feign 的支持
此时我们只需要访问集成服务中的任意一个路径就能在sentinel中查看到
2.1 sentinel限流(blockHandler) 讲解
2.1.1没有使用@SentinelResource注解(使用默认限流响应)
创建一个类用于测试限流
SentinelRatilimitConteroller
@RestController
@RequestMapping("/order/sentinelRatilimitConteroller")
public class SentinelRatilimitConteroller {/*** 测试sentinel限流 (没有添加@)* @return*/@GetMapping("/testSentinelRatilimitA")public String testSentinelRatilimitA() {return "没有达到限流的规则 --- 正常访问";}}
此时我们启动服务 访问地址
localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitA
能够正常访问
进入sentinel的控制台,给当前路径添加限流规则
当QPS超过1时的请求会快速失败返回默认的失败响应
此时限流返回到响应为默认的响应
2.1.2使用@SentinelResource注解自定义限流响应
写法1:
在controller中添加以下代码
/*** 测试sentinel限流 (添加了@SentinelResource)* @SentinelResource value 为 限流规则的资源名称 blockHandler 为限流规则的异常处理方法*/@GetMapping("/testSentinelRatilimitB")@SentinelResource(value = "testSentinelRatilimitB",blockHandler = "testSentinelRatilimitBHandleException")public String testSentinelRatilimitB() {return "没有达到限流的规则 --- 添加了@SentinelResource 正常访问";}/***作为(testSentinelRatilimitB)的限流异常返回方法*/public String testSentinelRatilimitBHandleException(BlockException e) {return "限流了 --- 添加了@SentinelResource 异常处理";}
* @SentinelResource value 为 限流规则的资源名称 blockHandler 为限流规则的异常处理方法
使得方法testSentinelRatilimitB 触发限流时 返回 testSentinelRatilimitBHandleException方法
正常访问:localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitB
添加限流规则:
快速访问:
执行了自定义限流处理
对应关系图
写法2:(推荐)
如果需要独立设置一个 限流类还可以这样写
添加一个新的controller
指定了blockHandler类中的指定方法作为限流处理
@GetMapping("/testSentinelRatilimitC")@SentinelResource(value = "testSentinelRatilimitC",blockHandlerClass = OrderBlockHandler.class,// 限流异常返回方法所在类blockHandler = "testSentinelRatilimitC") //指定限流异常返回方法public String testSentinelRatilimitC() {return "没有达到限流的规则 --- 添加了@SentinelResource 默认异常处理";}
访问localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitC
为他编写一个类 和方法 (方法必须被 pblic static 修饰)
public class OrderBlockHandler {// 必须是public static方法// 参数和返回类型要与原方法一致,最后加一个BlockException参数public static String testSentinelRatilimitC(BlockException ex) {return "触发限流了";}}
设置限流规则
快速访问:
2.2异常处理(fallback)
为一个接口配置 @SentinelResource(value = "testSentinelRatilimitD",
fallback = "testSentinelRatilimitDfallback")
fallback属性表示当前方法遇到异常会调用指定的方法
@GetMapping("/testSentinelRatilimitD/{id}")@SentinelResource(value = "testSentinelRatilimitD",blockHandler = "testSentinelRatilimitDBlockHandler",fallback = "testSentinelRatilimitDfallback")public String testSentinelRatilimitD(@PathVariable("id") Integer id) {if (id == 0){throw new RuntimeException("id 不能为0");}return "没有达到限流的规则 --- id为"+ id;}//作为限流 处理public String testSentinelRatilimitDBlockHandler(@PathVariable("id") Integer id,BlockException e) {return "已触发限流 --- id为"+ id;}//作为 熔断 降级 异常处理public String testSentinelRatilimitDfallback(@PathVariable("id") Integer id,Throwable e) {return "触发率异常处理,传入id为:"+ id;}
2.2.1测试限流
访问地址:localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitD/1
为他设置限流规则:
多次访问:
触发限流规则,走了BlockHandler 所配置的方法
2.2.2测试异常
访问
http://localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitD/0
传入id值为0,我们设置id为0时会抛出异常,看看是否会走fallback所配置的方法
成功触发fallback
3.热点参数限流
在我们比如商城服务中的商品流量是不均匀的,我们可能要对同一个接口的不同参数进行限流
例如
在查询某个商品信息时,同样根据商品id进行查询商品,商品ID: 1:华为 2:小米 3:苹果
查询接口
queryPhone(int id);
传入id为1的量较大,那么我们就需要对id为1的商品进行限流,如图:
写一个方法:
/*** 热点参数限流*/@GetMapping("/testSentinelRatilimitE/{id}")@SentinelResource(value = "testSentinelRatilimitE",blockHandler = "testSentinelRatilimitEBlockHandler")public String testSentinelRatilimitE(@PathVariable("id") int id){return "没有触发限流传入id为:"+ id;}/*** 热点参数限流处理*/public String testSentinelRatilimitEBlockHandler(int id,BlockException ex){return "触发限流处理,传入id为:"+ id;}
访问 :localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitE/0
参数索引: 写0时则为接口中的第一个参数
索引为0
统计窗口:表示*秒内统计的量
表示这个接口 带上参数ID时 QPS达到1开始限流
访问接口:localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitE/0
触发了限流
参数例外项:
例如当我们传入id为1时,我们的限流规则变成QPS为3才进行限流
上面的参数表示,当带参数索引为0时,限流QPS为1 ,参数例外项:当参数索引0 传入值为1 时,限流阈值QPS提升为3才限流
务必要点击添加按钮
测试发现id等于1时 ,只有QPS达到3才会触发限流
4.sentinel规则持久化(不会因为微服务重启丢失配置)
我们服务配置完成后,我们的微服务重启会导致我们这个服务的所有的配置丢失,不可能每重启一次我们就重新配置一次限流规则,所以需要持久化,持久化到哪里呢? 我们会选择持久化到配置中心 nacos中
在服务中引入新的Maven坐标
<!--sentinel持久化--> <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId> </dependency>
修改yml配置
配置ds1 ds2 ds3 分别代表(流控规则)(熔断降级规则) (热点参数规则)的持久化配置
我们以流控规则作为演示
打开nacos创建对应的配置文件
内容:
[{"resource": "testSentinelRatilimitE","limitApp": "default","grade": 1,"count": 1,"strategy": 0,"controlBehavior": 0,"clusterMode": false}
]
// [
// {
// // 🔹【资源名称】你想保护的是哪个接口或功能?
// // 这里是:testSentinelRatilimitE
// // 代码中要用 SphU.entry("testSentinelRatilimitE") 来标记这个资源
// "resource": "testSentinelRatilimitE",// // 🔹【限流对象】对谁限流?
// // "default" 表示:所有人都一样,一视同仁
// // 你也可以写成 "appA"、"appB",实现不同应用不同策略
// "limitApp": "default",// // 🔹【限流方式】按什么标准限流?
// // 1 = 按每秒请求数(QPS)来限
// // 0 = 按并发线程数来限(不常用)
// "grade": 1,// // 🔹【限流阈值】最多允许多少流量?
// // 这里是:每秒最多 1 个请求
// // 超过第 1 个就直接拒绝,不让进
// "count": 1,// // 🔹【限流策略】怎么判断要不要限?
// // 0 = 直接对自己限流(最简单常用)
// // 1 = 当另一个资源被限流时,我也跟着限(关联限流)
// // 2 = 只对某个调用链路限流(比如 A 调 B 才限)
// "strategy": 0,// // 🔹【触发限流后怎么办?】
// // 0 = 立刻拒绝,抛出“被限流”错误(快速失败)
// // 1 = 先预热,慢慢放行(适合突发流量)
// // 2 = 排队等待,一个一个处理(削峰填谷)
// "controlBehavior": 0,// // 🔹【是单机还是集群?】
// // false = 每台机器独立限流(单机模式,适合大多数场景)
// // true = 多台机器共享一个总限流值(需要额外配置集群)
// "clusterMode": false
// }
// ]
!!!!非常非常难用,需要自己手动写json用于配置持久化,Sentinel控制台配置成为摆设,因为控制台上写的配置不会持久化,必须自己写!!!!
同样是阿里巴巴的产品 Nacos比sentinel好用太多了!
重启 当前服务
访问localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitE/1
nacos 配置限流规则成功
来到sentinel控制台查看限流规则
成功从nacos中读取限流规则
5.Sentinel集成OpenFeign实现fallback熔断降级
往公共API模块中引入sentinel maven 依赖
<!--sentinel--> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
在api接口中添加fallbackFactory属性并创建对应的类
创建
StockApiFallback.class
@Component
public class StockApiFallback implements FallbackFactory<stockApi> {@Overridepublic stockApi create(Throwable cause) {return new stockApi() {@Overridepublic ResultData<String> reduceInventory() {throw new RPCException("201","调用库存服务调用失败");}@Overridepublic Integer reduceStock() {throw new RPCException("201","调用库存服务调用失败");}};}}
在fallback 的时候为了保证fallback后全局事务还能正常的回滚,fallback一般抛出自定义异常让事务回滚,并且全局异常处理器 处理整形后才返回给用户
自定义异常类:
@Data
public class RPCException extends RuntimeException{private String code;private String message;private Object data;public RPCException(String code,String message,Object data) {super(message);this.message = message;this.code = code;this.data =data ;}public RPCException(String code,String message) {super(message);this.message = message;this.code = code;}}
自定义异常捕获
@RestControllerAdvice
@Slf4j
public class RPCExceptionHandler {// 拦截自定义业务异常@ExceptionHandler(RPCException.class)public ResultData handleBizException(RPCException e) {log.warn("远程调用异常: code={}, msg={}", e.getCode(), e.getMessage());return ResultData.fail(e.getCode(),"系统出现错误,请稍后再试"); // 通常返回 200,业务码区分}}
重点:!!!! 然后在调用者模块 配置文件中开启OpenFeign 支持Sentinel
feign:sentinel:enabled: true # 启用 Sentinel 对 Feign 的支持
,然后重新启动 测试全局事务回滚是否正常
发现系统出现异常后,使用的是全局异常处理器返回的消息
并且,事务全部正确回滚
任务完成
6.Sentinel结合网关Gateway(限流熔断,貌似有BUG希望得到指点)
在Gateway网关服务中添加Maven依赖
<!--Gateway 结合 Sentinel做限流--> <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> </dependency> <!--javax--> <dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>1.3.2</version><scope>compile</scope> </dependency>
创建Gateway配置类
package com.xiaog.config;import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import javax.annotation.PostConstruct;
import java.util.*;@Configuration
public class GatewaySentinelConfiguration {private final List<ViewResolver> viewResolvers;private final ServerCodecConfigurer serverCodecConfigurer;public GatewaySentinelConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,ServerCodecConfigurer serverCodecConfigurer) {this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);this.serverCodecConfigurer = serverCodecConfigurer;}@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {// Register the block exception handler for Spring Cloud Gateway.return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);}@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public GlobalFilter sentinelGatewayFilter() {return new SentinelGatewayFilter();}/*** ========================================分割线======================================= 以上的代码不需要改动*//*** Spring 容器初始化的时候执行该方法*/@PostConstructpublic void doInit() {// 加载网关限流规则 initGatewayRules(); // 加载网关限流规则和熔断规则// 加载自定义限流异常处理器(处理+自定义返回信息的内容,类似触发流控规则保护)initBlockHandler(); //触发限流和熔断时会调用自定义的限流异常处理器}/*** 网关限流规则* 建议直接在 Sentinel 控制台上配置*/private void initGatewayRules() {Set<GatewayFlowRule> rules = new HashSet<>();/*resource:资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称count:限流阈值intervalSec:统计时间窗口,单位是秒,默认是 1 秒*/rules.add(new GatewayFlowRule("order-service") //限流id 在yml中配置的id Gatewa.routes.id.setCount(3) // 限流阈值.setIntervalSec(3)); // 统计时间窗口,单位是秒,默认是 1 秒rules.add(new GatewayFlowRule("stock-service").setCount(5) // 限流阈值.setIntervalSec(3)); // 统计时间窗口,单位是秒,默认是 1 秒// --------------------限流分组-----------end-----------// 加载网关限流规则GatewayRuleManager.loadRules(rules);// 加载限流分组
// initCustomizedApis();// ---------------熔断-降级配置-------------DegradeRule degradeRule = new DegradeRule("order-service") // 资源名称.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) // 异常比率模式(秒级).setCount(0.5) // 异常比率阈值(50%).setTimeWindow(10) // 熔断降级时间(10s).setMinRequestAmount(2);//最小请求数为2DegradeRule degradeRule2 = new DegradeRule("stock-service") //调用这个服务 的时候 异常比例过高则会触发熔断.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) // 异常比率模式(秒级).setCount(0.5) // 异常比率阈值(50%).setTimeWindow(10).setMinRequestAmount(2);// 加载规则.DegradeRuleManager.loadRules(Arrays.asList(degradeRule,degradeRule2));// 打印当前所有规则System.out.println("=== 当前生效的熔断规则 ===");DegradeRuleManager.getRules().forEach(System.out::println);// DegradeRuleManager.loadRules(Collections.singletonList(degradeRule));}/*** 自定义限流异常处理器(当服务被限流或者熔断时都会执行这个方法)*/private void initBlockHandler() {BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {@Overridepublic Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {Map<String, String> result = new HashMap<>(3);result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()));result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase()+"系统繁忙");if (throwable instanceof DegradeException) {result.put("中文信息", "服务熔断,请稍后重试");} else if (throwable instanceof FlowException ||throwable instanceof ParamFlowException ||throwable instanceof SystemBlockException) {result.put("中文信息", "请求过多,请稍后重试");} else {// 兜底逻辑result.put("中文信息", "系统限流或熔断,请稍后重试");}return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS).contentType(MediaType.APPLICATION_JSON)//.body(BodyInserters.fromValue(result));.body(BodyInserters.fromObject(result));}};// 加载自定义限流异常处理器 GatewayCallbackManager.setBlockHandler(blockRequestHandler);}}
当前存在熔断BUG 当服务异常请求很多时 没办法进行熔断 依旧可以继续访问
有懂的通信麻烦帮忙找一下bug解决一下博主的疑惑
!!!!这个注解务必使用
package javax.annotation;下的
然后重启测试限流
限流成功