【SpringCloud】9.Spring Cloud Alibaba Sentinel——服务限流与降级
概述
Sentinel是什么?是轻量级的流量控制、熔断降级的Java库。等价于之前学过的Circuit Break。同时,测试熔断、降级的效果,可能使用批量请求,可以使用压力测试工具:JMeter.
官网地址:https://sentinelguard.io/zh-cn/
如何使用,可以查看官网的入门文档:https://sentinelguard.io/zh-cn/docs/quick-start.html
github介绍:https://github.com/alibaba/Sentinel/wiki/介绍
感觉sentinel确实比Circuit Break方便。对流量的的控制不是写在配置文件。只需要配置sentinel并持久化到nacos中,可以图形化的管理,根据实际情况配置,更利于运维。
安装和运行
Sentinel由两个部分组成:核心库(Java客户端,默认端口8719),控制台(Dashboard,默认端口8080)。
运行Sentinel前提
- java环境没有问题,能运行.jar文件
- 8080端口没有被占用
-
下载sentinel-dashboard-X.X.X.jar到本地(这里下载1.8.6):https://github.com/alibaba/Sentinel/releases
-
打开 sentinel-dashboard-1.8.6.jar所在目录,运行
java -jar sentinel-dashboard-1.8.6.jar
-
浏览器打开 localhsot:8080。 默认账号密码:sentinel。
入门案例
目前控制台除了欢迎首页,空空如也。我们应该如何使用sentinel呢?我们新建一个微服务(8401)看看。
前提:
- 启动nacos8848成功
- 启动sentinel8080成功
新建使用sentinel的微服务(将被哨兵纳入管控的8401微服务提供者)步骤如下,
-
新建子项目:
cloudalibaba-sentinel-service8401
. -
POM
-
YML
-
主启动
-
业务类FlowLimitController
-
启动微服务8401并访问
POM,引入通用的依赖和nacos-discovery及sentinel。
<!-- nacos-discovery--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud alibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
写YAML.当前项目端口号,项目名称,注册入Nacos服务,配置sentinel控制台
主启动
@SpringBootApplication @EnableDiscoveryClient public class sentinelMain8401 { public static void main(String[] args) { SpringApplication.run(sentinelMain8401.class, args); } }
业务类FlowLimitController
/** * * @Author:lyj * @Date:2025/1/10 09:22 */ @RestController public class FlowLimitController { @GetMapping("/sentinel/testA") public String testA(){ return "--------testA"; } @GetMapping("/sentinel/testB") public String testB(){ return "--------testB"; } }
测试方法接口: http://localhost:8401/sentinel/testA ,http://localhost:8401/sentinel/testB
测试:
- 直接访问sentinel控制台,仍然空空如也。
- 执行一次访问测试接口后,可以看到控制台的实时监控。
Sentinel执行懒加载。想使用Sentinel对某个接口进行限流和降级等操作,一定要先访问下接口,使用Sentinel检测出相应的接口。
流控规则
基本介绍
位置:【流程规则】→【新增流程控制】
Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程等指标,如果达到指定的阈值,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可用性。
参数见如下:
资源名 | 资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。 |
---|---|
针对来源 | 具体对某个微服务进行限流,默认值为default,表示不区分来源,全部限流 |
阈值类型 | QPS表示通过QPS进行限流,并发线程数表示通过并发线程限流 |
单击阈值 | 与阈值类型组合使用。如果阈值类型选择QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作 |
是否集群 | 选中表示集群环境,不选择表示非集群环境。 |
流量模式
流量模式类型包括:直接、关联、链路。
直接:默认的流量控制模式,当接口达到限制条件时,直接开启限流功能。
测试,sentinel/testA接口。这里设置1s内查询1次,超过次数则报错。
测试接口:http://localhost:8401/sentinel/testA
报错信息:
关联:当关联的资源达到阈值时,就限流自己。当A关联的资源B达到阈值后,就限流A自己()。
测试,sentinel/testA接口。当关联资源/sentinel/testB的QPS阈值超过1时,限流资源testA(B惹事,A挂了)。
可以使用apache JMeter密集访问testB。
线程组:
HTTP请求:
链路:来自不同链路的请求对同一个目标访问时,实施针对性不同的限流措施。比如请求C访问就限流,D请求访问就OK(大概是普通用户和VIP的区别)。
设置链路配置,我们需要对8401微服务进行修改(写YAML和新增业务类)。
写YAML:配置spring.cloud.sentinel.web-context-unify
值为false.
server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务中心地址 sentinel: transport: dashboard: localhost:8080 #配置Sentinel dashboard控制台服务地 port: 8719 # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口 web-context-unify: false # controller层方法对service调用不认为是同一根链路
业务类:service层新建common方法。
@Service public class FlowLimitService { @SentinelResource(value = "common") public void common(){ System.out.println("------ FlowLimitService come in"); } }
新建两个请求,都访问common.
@Resource private FlowLimitService flowLimitService; /**流控-链路演示demo * C和D两个请求都访问flowLimitService.common()方法,阈值到达后对C限流,对D不管 */ @GetMapping("/sentinel/testC") public String testC(){ flowLimitService.common(); return "-----------testC"; } @GetMapping("/sentinel/testD") public String testD(){ flowLimitService.common(); return "-----------testD"; }
testD不限流,testC超过1s一次以后,就发生了限流。
流量效果:流量效果包括:快速失败、warm Up, 排队等待。
快速失败:默认的流量控制方式。直接失败,抛出异常。“Blocked By sentinel(flow limit)!”
预热:WarmUp
限流_冷启动:当流量突然增大的时候,我们常常希望系统从空闲到繁忙状态的切换时间长一些。
预热公式:预热时长= 阈值/冷却因子(coldFactor,默认值为3 )
案例:单击阈值设置为10,预热时长设置为5s。
默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。 |
---|
案例,单机阈值为10,预热时长设置5秒。系统初始化的阈值为10 / 3 约等于3,即单机阈值刚开始为3(我们人工设定单机阈值是10,sentinel计算后QPS判定为3开始);然后过了5秒后阀值才慢慢升高恢复到设置的单机阈值10,也就是说5秒钟内QPS为3,过了保护期5秒后QPS为10 |
测试:多次点击testB(刚开始不行,后面慢慢OK)。
应用场景:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。
排队等待:
应用场景:这种方式主要用于处理间隔突发流量,例如消息队列。
匀速排队模式暂时不支持QPS>1000的场景
新增用于测试排队等待的接口。
@GetMapping("/sentinel/testE") public String testE() { System.out.println(System.currentTimeMillis()+" testE,排队等待"); return "------testE"; }
测试端口:http://localhost:8401/sentinel/testE
按照单击阈值,一秒钟通过一个请求,10s后的请求作为超时处理,放弃。
流程控制效果:
并发线程数:同时并发的线程数。
熔断规则
Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源进行限制。
让请求快速失败,避免影响到其他资源导致几连错误。当资源降级后,在接下来的降级时间窗口内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。
熔断规则包括:
- 慢调用比例
- 异常比例
- 异常数
慢调用比例
慢调用比例(SLOW_REQUEST_RAITIO
):选择以慢调用比例作为阈值,需要设置雨荨的慢调用比例RT(即最大响应时间),请求的响应时间大于该值则统计为慢调用。
配置如下:
进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数 且 实际慢调用比例>比例阈值 ,进入熔断状态。
名称解释:
调用 | 一个请求发送到服务器,服务器给与响应,一个是响应,一个是调用 |
---|---|
最大RT | 即最大的响应时间,指系统对请求作出响应的业务处理时间 |
慢调用 | 处理业务逻辑的实际时间>设置的最大RT时间,这个调用叫做慢调用 |
慢调用比列 | 在所用比例中,慢调用占有实际的比例=慢调用次数/总调用次数 |
比例阈值 | 自己设定的,比例阈值=慢调用次数/调用次数 |
统计时长 | 时间的判断依据 |
最小请求数 | 设置的调用最小请求数 |
进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数 且 实际慢调用比例>比例阈值 ,进入熔断状态。
1熔断状态(保险丝跳闸断电,不可访问):在接下来的熔断时长内请求会自动被熔断
2探测恢复状态(探路先锋):熔断时长结束后进入探测恢复状态
3结束熔断(保险丝闭合恢复,可以访问):在探测恢复状态,如果接下来的一个请求响应时间小于设置的慢调用 RT,则结束熔断,否则继续熔断。
异常比例
异常数
@SentinelResource 注解说明
SentinelResource是一个流量防伪防护组件注解,用于指定防护资源,对配置的资源进行流量控制、熔断降级等功能。
热点规则
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作 。
新增测试热点规则业务类
@GetMapping("/testHotKey") @SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey") public String testHotKey(@RequestParam(value = "p1",required = false) String p1, @RequestParam(value = "p2",required = false) String p2){ return "------testHotKey"; } public String dealHandler_testHotKey(String p1, String p2, BlockException exception) { return "-----dealHandler_testHotKey"; }
限流模式只支持QPS模式,固定写死了。(这才叫热点)
@SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推
单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法。
测试:
http://localhost:8401/testkey?p1=abc , 含有参数P1,当每秒访问的频率超过1次时,会触发Sentinel限流操作
http://localhost:8401/testkey?p1=abc&p2=33 , 含有参数P1,当每秒访问的频率超过1次时,会触发Sentinel限流操作
http://localhost:8401/testHotKey?p2=abc , 没有热点参数P1,不断范围则不会触发限流操作
参数例外项
上述例子包含参数p1,则限流(阈值为1)。我们期望p1为特殊值(比如5)是,阈值可达到200。
授权规则
在某些场景下,需要根据调用接口来判断是否允许本次请求。此时就可以使用Sentinel提供的授权规则来实现。Sentinel的授权规则能够根据请求的来源判断是否允许本次请求通过。
在Sentinel的授权规则中,提供了白名单与黑名单两种授权类型。白放行、黑禁止。接下来演示黑名单禁止。
业务类
@RestController // Empower授权规则,用来处理请求的来源 @Slf4j public class EmpowerController { @GetMapping(value = "/empower") public String requestSentinel4(){ log.info("测试Sentinel授权规则empower"); return "Sentinel授权规则"; } }
/** * @Author:lyj * @Date:2025/1/14 10:43 */ @Component public class MyRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { return httpServletRequest.getHeader("serverName"); } }
访问地址:http://localhost:8401/empower
配置,授权规则配置如下:
测试:http://localhost:8401/empower?serverName=test1, http://localhost:8401/empower?serverName=test2 无法访问,被限流
http://localhost:8401/empower?serverName=ab 等正常访问。
Sentinel 持久化
可以使用sentinel控制台,配置熔断和限流规则。不过,这有一个问题,一旦我们重启微服务应用,sentinel规则将消失,生产环境需要将配置规则进行持久化。
解决方案:将限流配置规则持久化到Nacos进行保存。
接下来我们将对8401进行sentinel持久化。这样,只要刷新8401某个rest地址,sentinel控制台的流控制规则就能看到,只要里面配置不删除,针对8401上的sentinel的流控规则就持续有效。
案例的持久化步骤如下:
- POM
- YML
- 添加Nacos业务规则配置
- 快速访问测试接口
- 停止8401再看sentinel
- 重新启动8401再看sentinel
POM,POM添加支持nacos持久化的依赖。
<!--SpringCloud ailibaba sentinel-datasource-nacos --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
YAML, 添加nacos数据源配置
spring: cloud: sentinel: datasource: ds1: nacos: server-addr: localhost:8848 data-id: ${spring.application.name} group-id: DEFAULT_GROUP data-type: json rule-type: flow # com.alibaba.cloud.sentinel.datasource.RuleType
添加Nacos业务规则配置。新建配置项cloudalibaba-sentinel-service
配置内容如下:
[ { "resource": "/rateLimit/byUrl", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]
配置项说明:
resource
: 资源名称;limitApp
: 来源应用grade
: 阈值类型, 0 表示线程数,1 表示QPS;count
: 单击阈值strategy
: 流控模式,0 表示直接,1表示关联,2表示链路;controlBehavior
: 流控效果,0表示快速失败,1表示Warm Up, 2 表示排队等待;clusterMode
:是否集群。
持久化测试:
启动nacos和sentinel,访问测试地址后:http://localhost:8401/rateLimit/byUrl。 等待3s后,发现sentinel业务规则就有了。
停机后,sentinel没有了。重启后,稍等片刻,持久化规则生效。
直接改sentinel的的配置重启后不生效。想要持久化,需要修改nacos中的配置文件。
OpenFeign和Sentinel集成实现fallbacl服务降级
参见nacos的使用和服务注册:https://www.cnblogs.com/luyj00436/p/18650038
前面nacos例子中,有代码膨胀问题。Fallback降级
编码
除了成功启动nacos和sentinel服务器外。我们需要修改的代码有这些:
- 修改服务提供方
cloudalibaba-provider-payment9001
- 修改公共方法
cloud-api-commons
- 修改调用方
cloudalibaba-consumer-nacos-order83
修改服务提供方cloudalibaba-provider-payment9001
POM,引入通用包、openfeign、sentinel依赖。
<!-- 引入自己定义的api通用包 --> <dependency> <groupId>com.atguigu.cloud</groupId> <artifactId>cloud-api-commons</artifactId> </dependency> <!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- alibaba-sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
写YAML,在配置文件中添加注入sentinel。
server: port: 9001 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: localhost:8848 #配置nacos地址 sentinel: transport: dashboard: localhost:8080 # 配置Sentinel dashboard控制台服务地址 port: 8719 # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
业务类。接口@SentinelResource
注释,用于测试调用。
@GetMapping("/pay/nacos/get/{orderNo}") @SentinelResource(value = "getPayOrderNo",blockHandler = "handlerBlockHander") public ResultData getPayOrderNo(@PathVariable("orderNo") String orderNo){ // 模拟从数据库查询数据,并赋值给DTO PayDTO payDTO = new PayDTO(); payDTO.setOrderNo(orderNo); payDTO.setAmount(BigDecimal.valueOf(9.9)); payDTO.setPayNo("pay:"+ IdUtil.fastSimpleUUID()); payDTO.setUserId(1); return ResultData.success("nacos registry,serverPort:"+payDTO); } public ResultData handlerBlockHander(@PathVariable("orderNo") String orderNo, BlockException exception){ return ResultData.fail(ReturnCodeEnum.RC500.getCode(), "getPayByOrder 服务不可用," +"触发sentinel流控配置规则"+"\t"+"o(╥﹏╥)o"); }
修改公共方法cloud-api-commons
POM,添加openfeign和sentinel依赖
<!-- openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- alibaba-sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
业务类,新增PayFeignSentinelAPi
,并且为远程调用,新建统一的服务降级类。
@Component public class PayFeignSentinelApiFallback implements PayFeignSentinelApi { @Override public ResultData getPayByOrderNo(String orderNo) { return ResultData.fail(ReturnCodeEnum.RC500.getCode(),"对方服务宕机或不可用,FallBack服务降级o(╥﹏╥)o"); } } @FeignClient(value = "nacos-payment-provider",fallback = PayFeignSentinelApiFallback.class) public interface PayFeignSentinelApi { @GetMapping("/pay/nacos/get/{orderNo}") public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo); }
修改调用方cloudalibaba-consumer-nacos-order83
POM,添加openfeign,sentinel和通用方法。
<!-- 引入自己定义的api通用包 --> <dependency> <groupId>com.atguigu.cloud</groupId> <artifactId>cloud-api-commons</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> </dependency>
YAML,激活sentinel对YAML的支持。
# 激活sentinel对Feign的支持 feign: sentinel: enabled: true
主启动,添加@EnableFeignClients
注解
业务类,
@RestController public class OrderNacosController { @Resource private PayFeignSentinelApi payFeignSentinelApi; @GetMapping(value = "/consumer/pay/nacos/get/{orderNo}") public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo) { return payFeignSentinelApi.getPayByOrderNo(orderNo); } }
测试
测试地址: http://localhost:83/consumer/pay/nacos/get/order1024
编辑流量控制规则:
GateWay和Sentinel集成实现服务限流
建module,新建网关子模块9528
POM,引入网关、sentinel相关依赖
<!-- 指标监控健康actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <scope>compile</scope> </dependency> <!-- nacos-discovery--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
写YAML,匹配服务断言
server: port: 9528 spring: application: name: cloudalibaba-sentinel-gateway # sentinel+gataway整合Case cloud: nacos: discovery: server-addr: localhost:8848 gateway: routes: - id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:9001 #匹配后提供服务的路由地址 predicates: - Path=/pay/** # 断言,路径相匹配的进行路由
主启动
@SpringBootApplication @EnableDiscoveryClient public class GateWayMain9528 { public static void main(String[] args) { SpringApplication.run(GateWayMain9528.class, args); } }
业务类,添加业务配置
@Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration(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(-1) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct //javax.annotation.PostConstruct public void doInit() { initBlockHandler(); } //处理/自定义返回的例外信息 private void initBlockHandler() { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("pay_routh1").setCount(2).setIntervalSec(1)); GatewayRuleManager.loadRules(rules); BlockRequestHandler handler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) { Map<String,String> map = new HashMap<>(); map.put("errorCode", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase()); map.put("errorMessage", "请求太过频繁,系统忙不过来,触发限流(sentinel+gataway整合Case)"); return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(map)); } }; GatewayCallbackManager.setBlockHandler(handler); } }
测试
原生接口:http://localhost:9001/pay/nacos/get/order1024
加网关接口:http://localhost:9528/pay/nacos/get/order1024
sentinel + gateway网关,加快点击频率,出现限流容错。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库