【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端口没有被占用
  1. 下载sentinel-dashboard-X.X.X.jar到本地(这里下载1.8.6):https://github.com/alibaba/Sentinel/releases

  2. 打开 sentinel-dashboard-1.8.6.jar所在目录,运行java -jar sentinel-dashboard-1.8.6.jar

  3. 浏览器打开 localhsot:8080。 默认账号密码:sentinel。
    image

入门案例

目前控制台除了欢迎首页,空空如也。我们应该如何使用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/testAhttp://localhost:8401/sentinel/testB

测试

  • 直接访问sentinel控制台,仍然空空如也。
  • 执行一次访问测试接口后,可以看到控制台的实时监控。

image

Sentinel执行懒加载。想使用Sentinel对某个接口进行限流和降级等操作,一定要先访问下接口,使用Sentinel检测出相应的接口。

流控规则

基本介绍

位置:【流程规则】→【新增流程控制】

Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程等指标,如果达到指定的阈值,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可用性。
image

参数见如下:

资源名 资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。
针对来源 具体对某个微服务进行限流,默认值为default,表示不区分来源,全部限流
阈值类型 QPS表示通过QPS进行限流,并发线程数表示通过并发线程限流
单击阈值 与阈值类型组合使用。如果阈值类型选择QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作
是否集群 选中表示集群环境,不选择表示非集群环境。

流量模式

流量模式类型包括:直接关联链路

直接:默认的流量控制模式,当接口达到限制条件时,直接开启限流功能。

测试,sentinel/testA接口。这里设置1s内查询1次,超过次数则报错。
image
测试接口:http://localhost:8401/sentinel/testA

报错信息:
image

关联:当关联的资源达到阈值时,就限流自己。当A关联的资源B达到阈值后,就限流A自己()。

测试,sentinel/testA接口。当关联资源/sentinel/testB的QPS阈值超过1时,限流资源testA(B惹事,A挂了)。

image

可以使用apache JMeter密集访问testB。

线程组:
image

HTTP请求:
image

链路:来自不同链路的请求对同一个目标访问时,实施针对性不同的限流措施。比如请求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";
}

image

testD不限流,testC超过1s一次以后,就发生了限流。
image

流量效果:流量效果包括:快速失败、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

image

测试:多次点击testB(刚开始不行,后面慢慢OK)。

应用场景:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。

排队等待:

应用场景:这种方式主要用于处理间隔突发流量,例如消息队列。

匀速排队模式暂时不支持QPS>1000的场景

新增用于测试排队等待的接口。

@GetMapping("/sentinel/testE")
public String testE()
{
System.out.println(System.currentTimeMillis()+" testE,排队等待");
return "------testE";
}

测试端口:http://localhost:8401/sentinel/testE
image

按照单击阈值,一秒钟通过一个请求,10s后的请求作为超时处理,放弃。
image
流程控制效果:

并发线程数:同时并发的线程数。

image

熔断规则

Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源进行限制。

让请求快速失败,避免影响到其他资源导致几连错误。当资源降级后,在接下来的降级时间窗口内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。

熔断规则包括:

  • 慢调用比例
  • 异常比例
  • 异常数

慢调用比例

慢调用比例(SLOW_REQUEST_RAITIO):选择以慢调用比例作为阈值,需要设置雨荨的慢调用比例RT(即最大响应时间),请求的响应时间大于该值则统计为慢调用。
image

配置如下:
image

进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数    且      实际慢调用比例>比例阈值 ,进入熔断状态。

名称解释:

调用 一个请求发送到服务器,服务器给与响应,一个是响应,一个是调用
最大RT 即最大的响应时间,指系统对请求作出响应的业务处理时间
慢调用 处理业务逻辑的实际时间>设置的最大RT时间,这个调用叫做慢调用
慢调用比列 在所用比例中,慢调用占有实际的比例=慢调用次数/总调用次数
比例阈值 自己设定的,比例阈值=慢调用次数/调用次数
统计时长 时间的判断依据
最小请求数 设置的调用最小请求数

进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数    且      实际慢调用比例>比例阈值 ,进入熔断状态。

1熔断状态(保险丝跳闸断电,不可访问):在接下来的熔断时长内请求会自动被熔断

2探测恢复状态(探路先锋):熔断时长结束后进入探测恢复状态

3结束熔断(保险丝闭合恢复,可以访问):在探测恢复状态,如果接下来的一个请求响应时间小于设置的慢调用 RT,则结束熔断,否则继续熔断。

异常比例

image
image

异常数
image
image

@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。
image

授权规则

在某些场景下,需要根据调用接口来判断是否允许本次请求。此时就可以使用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

配置,授权规则配置如下:

image

测试: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
image

配置内容如下:

[
{
"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降级
image

编码

除了成功启动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

编辑流量控制规则:
image

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网关,加快点击频率,出现限流容错。

posted @   陆陆无为而治者  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示