微服务系列:服务网关 Spring Cloud Gateway 集成 Sentinel 限流
Sentinel
是阿里开源的一款面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来保障微服务的稳定性。
除了Spring Cloud Gateway
官方提供的RequestRateLimiterGatewayFilterFactory
过滤器工厂来实现网关限流之外,我们还可以选择集成 Sentinel
来实现限流。
本文将主要学习 Spring Cloud Gateway
集成 Sentinel
实现限流,对 Sentinel
的基础知识不做过多的讲解,大家可以直接参考官方文档:Sentinel 官方文档 (sentinelguard.io)
话不多说,开始今天的学习。
Sentinel 限流
从 1.6.0 版本开始,Sentinel
提供了 Spring Cloud Gateway
的适配模块,可以提供两种资源维度的限流:
- route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
- 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组
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
输出内容格式如下:
其中:
- thread: 代表当前处理该资源的线程数;
- pass: 代表一秒内到来到的请求;
- blocked: 代表一秒内被流量控制的请求数量;
- success: 代表一秒内成功处理完的请求;
- total: 代表到一秒内到来的请求以及被阻止的请求总和;
- RT: 代表一秒内该资源的平均响应时间;
- 1m-pass: 则是一分钟内到来的请求;
- 1m-block: 则是一分钟内被阻止的请求;
- 1m-all: 则是一分钟内到来的请求和被阻止的请求的总和;
- exception: 则是一秒内业务本身异常的总和。
除此之外,无论触发了限流、熔断降级还是系统保护,它们的秒级拦截详情日志都在 ${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无意义可忽略 |
Sentinel分组限流
对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自定义异常
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
熔断降级等会专门去学习下。
更多推荐
所有评论(0)