Hystrix常用的线程池隔离会造成线程上下切换的overhead比较大;Hystrix使用的信号量隔离对某个资源调用的并发数进行控制,效果不错,但是无法对慢调用进行自动降级;Sentinel通过并发线程数的流量控制提供信号量隔离的功能
Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关作为流量的,在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用。 Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。(github.com/alibaba/Sen…
<!-- SpringCloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel Gateway -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
/**
* 自定义限流异常处理
*/
public class SentinelFallbackHandler implements WebExceptionHandler
{
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
{
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试");
}
@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);
}
}
/**
* 网关限流配置
*/
@Configuration
public class GatewayConfig
{
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelFallbackHandler sentinelGatewayExceptionHandler()
{
return new SentinelFallbackHandler();
}
}
Sentinel限流
Sentinel 是阿里开源的一款面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来保障微服务的稳定性。
除了Spring Cloud Gateway官方提供的RequestRateLimiterGatewayFilterFactory过滤器工厂来实现网关限流之外,我们还可以选择集成 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 下执行这个命令来查看实时的统计信息
输出内容格式如下:
- 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
日志含义:
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 中那么触发限流之后返回的是这样的异常信息
———————————————— 版权声明:本文为CSDN博主「程序猿秃头之路」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/zhang335654…