赞
踩
Sentinel
是阿里开源的一款面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来保障微服务的稳定性。
除了Spring Cloud Gateway
官方提供的RequestRateLimiterGatewayFilterFactory
过滤器工厂来实现网关限流之外,我们还可以选择集成 Sentinel
来实现限流。
本文将主要学习 Spring Cloud Gateway
集成 Sentinel
实现限流,对 Sentinel
的基础知识不做过多的讲解,大家可以直接参考官方文档:Sentinel 官方文档 (sentinelguard.io)
话不多说,开始今天的学习。
从 1.6.0 版本开始,Sentinel
提供了 Spring Cloud Gateway
的适配模块,可以提供两种资源维度的限流:
1、添加依赖
<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel Gateway -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
2、自定义限流异常处理类
/** * 自定义限流异常处理 * * @author ezhang */ public class SentinelFallbackHandler implements WebExceptionHandler { private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) { ServerHttpResponse serverHttpResponse = exchange.getResponse(); serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); byte[] datas = "{"code":429, "msg":"请求超过最大数,请稍后再试"}".getBytes(StandardCharsets.UTF_8); DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas); return serverHttpResponse.writeWith(Mono.just(buffer)); } @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { if (exchange.getResponse().isCommitted()) { return Mono.error(ex); } if (!BlockException.isBlockException(ex)) { return Mono.error(ex); } return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange)); } private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) { return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); } }
该类实现了 Spring WebFlux
中的异常处理接口 WebExceptionHandler
。
这个类除了自定义之外,也可以直接使用 Sentinel
自带的 SentinelGatewayBlockExceptionHandler
类。
public class SentinelGatewayBlockExceptionHandler implements WebExceptionHandler { private List<ViewResolver> viewResolvers; private List<HttpMessageWriter<?>> messageWriters; private final Supplier<Context> contextSupplier = () -> { return new Context() { public List<HttpMessageWriter<?>> messageWriters() { return SentinelGatewayBlockExceptionHandler.this.messageWriters; } public List<ViewResolver> viewResolvers() { return SentinelGatewayBlockExceptionHandler.this.viewResolvers; } }; }; public SentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolvers; this.messageWriters = serverCodecConfigurer.getWriters(); } private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) { return response.writeTo(exchange, (Context)this.contextSupplier.get()); } public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { if (exchange.getResponse().isCommitted()) { return Mono.error(ex); } else { return !BlockException.isBlockException(ex) ? Mono.error(ex) : this.handleBlockedRequest(exchange, ex).flatMap((response) -> { return this.writeResponse(response, exchange); }); } } private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) { return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); } }
SentinelGatewayBlockExceptionHandler
类同样是实现了 WebExceptionHandler
接口。
3、限流规则配置类
/** * 限流规则配置类 * * @author ezhang */ @Configuration public class GatewayConfig { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelFallbackHandler sentinelGatewayExceptionHandler() { return new SentinelFallbackHandler(); } @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void doInit() { // 加载网关限流规则 initGatewayRules(); } /** * 网关限流规则 */ private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("cloud-gateway") .setCount(3) // 限流阈值 .setIntervalSec(60)); // 统计时间窗口,单位是秒,默认是 1 秒 // 加载网关限流规则 GatewayRuleManager.loadRules(rules); } }
4、application.yml 配置
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: cloud-gateway
uri: http://192.168.1.211:8088
predicates:
- Path=/ytb/**
filters:
- StripPrefix=1
5、启动测试
启动项目后我们还是访问这个接口 http://localhost:8080/ytb/file/getFileList
此时返回是正常的,当我们一分钟之内访问第四次的时候就会返回异常了,此时说明限流成功。
同时我们还可以在 cmd
下执行这个命令来查看实时的统计信息
curl http://localhost:8719/cnode?id=cloud-gateway
输出内容格式如下:
其中:
除此之外,无论触发了限流、熔断降级还是系统保护,它们的秒级拦截详情日志都在 ${user_home}/logs/csp/sentinel-block.log
里。如果没有发生拦截,则该日志不会出现。日志格式如下:
2021-12-24 15:46:02|1|cloud-gateway,ParamFlowException,$D,|2,0
2021-12-24 15:46:03|1|cloud-gateway,ParamFlowException,$D,|1,0
日志含义:
index | 例子 | 说明 |
---|---|---|
1 | 2021-12-24 15:46:03 | 时间戳 |
2 | 1 | 该秒发生的第一个资源 |
3 | cloud-gateway | 资源名称 |
4 | XXXException | 拦截的原因, 通常 FlowException 代表是被限流规则拦截,DegradeException 则表示被降级,SystemBlockException 则表示被系统保护拦截 |
5 | 2,0 | 2 被拦截的数量,0无意义可忽略 |
对cloud-system
、cloud-ytb
分组限流配置
1、application.yml
配置文件
server: port: 8080 spring: application: name: api-gateway cloud: gateway: routes: - id: cloud-ytb uri: http://192.168.1.211:8088 predicates: - Path=/ytb/** filters: - StripPrefix=1 - id: cloud-system uri: http://192.168.1.211:8088 predicates: - Path=/system/** filters: - StripPrefix=1
2、限流规则配置类
/** * 限流规则配置类 * * @author ezhang */ @Configuration public class GatewayConfig { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelFallbackHandler sentinelGatewayExceptionHandler() { return new SentinelFallbackHandler(); } @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void doInit() { // 分组 initCustomizedApis(); // 加载网关限流规则 initGatewayRules(); } /** * 网关限流规则 */ private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("system-api") .setCount(3) // 限流阈值 .setIntervalSec(60)); // 统计时间窗口,单位是秒,默认是 1 秒 rules.add(new GatewayFlowRule("ytb-api") .setCount(6) // 限流阈值 .setIntervalSec(60)); // 加载网关限流规则 GatewayRuleManager.loadRules(rules); } private void initCustomizedApis() { Set<ApiDefinition> definitions = new HashSet<>(); // cloud-system 组 ApiDefinition api1 = new ApiDefinition("system-api") .setPredicateItems(new HashSet<ApiPredicateItem>() { { // 匹配 /file 以及其子路径的所有请求 add(new ApiPathPredicateItem().setPattern("/system/file/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); } }); // cloud-ytb 组 ApiDefinition api2 = new ApiDefinition("ytb-api") .setPredicateItems(new HashSet<ApiPredicateItem>() { { // 只匹配 /file/getFileList add(new ApiPathPredicateItem().setPattern("/ytb/file/getFileList")); } }); definitions.add(api1); definitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); } }
访问:http://localhost:8080/system/file/getFileList
(触发限流)
访问:http://localhost:8080/system/user/list
(不会触发限流)
访问:http://localhost:8080/ytb/file/getFileList
(触发限流)
访问:http://localhost:8080/ytb/file/updateFileInfo
(不会触发限流)
Sentinel支持自定义异常处理。
方案一:yml
配置
# Spring
spring:
cloud:
sentinel:
scg:
fallback:
mode: response
response-body: '{"code":403,"msg":"请求超过最大数,请稍后再试"}'
启动测试触发限流之后:
方案二:就是上面自定义的 SentinelFallbackHandler
注入到 GatewayConfig
中
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelFallbackHandler sentinelGatewayExceptionHandler() {
return new SentinelFallbackHandler();
}
自定义异常可以使触发限流后返回信息更规整,如果我们直接使用 Sentinel
自带的 SentinelGatewayBlockExceptionHandler
类注入到 GatewayConfig
中那么触发限流之后返回的是这样的异常信息
这里只是简单的学习一下 Spring Cloud Gateway
集成 Sentinel
怎样配置实现限流。没有深入、系统的学习 Sentinel
。后面针对 Sentinel
熔断降级等会专门去学习下。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。