1.实现Gateway网关限流
SpringCloudGateway自带了 RequestRateLimiterGatewayFilterFactory
限流方案,依赖redis
与内置的RedisRateLimiter
过滤器进行限流操作,默认限流算法为令牌桶算法。
第一步:引入redis依赖
引入如下redis依赖或其他依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
引入依赖后配置redis:
redis:
host: 127.0.0.1
port: 6379
确保可以访问即可!
第二步:注入KeyResolver
@Configuration
public class GatewayResolver {
@Bean("keyResolver")
public KeyResolver hostAddrKeyResolver() {
return exchange -> {
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
return Mono.just(ip);
};
}
}
以上实例为根据IP进行限流,也可根据其他限流,保证Mono.just()中的参数为限流依据即可。
比如:根据url限流
@Configuration
public class GatewayResolver {
@Bean("keyResolver")
public KeyResolver hostAddrKeyResolver() {
return exchange -> {
String url = exchange.getRequest().getPath().toString();
return Mono.just(url);
};
}
}
第三步:编写配置文件
- id: test
uri: lb://test-service
predicates:
- Path=/test/**
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@keyResolver}'
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 3
其中:
- redis-rate-limiter.replenishRate字段为令牌桶恢复速度,即每秒访问个数
- redis-rate-limiter.burstCapacity字段为令牌桶大小,即峰值流量来临时最大可访问数
注意:
-
那个服务需要限流就在那个服务下加入filter的配置即可,如果不需要限流则不需要添加
-
name字段必须为
RequestRateLimiter
-
key-resolver参数对应注入到Spring中Bean的名称。
启动测试:
同一时间多次访问后出现报错提示!
2.返回自定义异常
以下内容参考:https://youbl.blog.csdn.net/article/details/115178343
目前限流为正常,但无法返回自定义数据,而是报错429异常,而且并没有相应配置改变错误处理方式,只能修改默认代码,发现限流过滤器编写在RequestRateLimiterGatewayFilterFactory
中,Gateway中配置的RequestRateLimiter
正是此过滤器去掉后缀后的结果,所以只需重写此过滤器即可。
重写默认限流过滤器:
先重写过滤器
import com.alibaba.fastjson.JSONObject;
import com.mgmiot.dlp.component.commonbase.model.ResponseCode;
import com.mgmiot.dlp.component.commonbase.model.ResponseVO;
import com.mgmiot.dlp.component.commonbase.utils.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Map;
@Slf4j
@Component
public class GatewayRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {
private final RateLimiter defaultRateLimiter;
private final KeyResolver defaultKeyResolver;
public GatewayRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
super(defaultRateLimiter, defaultKeyResolver);
this.defaultRateLimiter = defaultRateLimiter;
this.defaultKeyResolver = defaultKeyResolver;
}
@Override
public GatewayFilter apply(Config config) {
KeyResolver resolver = getOrDefault(config.getKeyResolver(), defaultKeyResolver);
RateLimiter<Object> limiter = getOrDefault(config.getRateLimiter(), defaultRateLimiter);
return (exchange, chain) -> resolver.resolve(exchange).flatMap(key -> {
String routeId = config.getRouteId();
if (routeId == null) {
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
routeId = route.getId();
}
String finalRouteId = routeId;
return limiter.isAllowed(routeId, key).flatMap(response -> {
for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
}
if (response.isAllowed()) {
return chain.filter(exchange);
}
log.warn("已限流: {}", finalRouteId);
ServerHttpResponse httpResponse = exchange.getResponse();
//修改code为500
httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
if (!httpResponse.getHeaders().containsKey("Content-Type")) {
httpResponse.getHeaders().add("Content-Type", "application/json");
}
//此处无法触发全局异常处理,手动返回
DataBuffer buffer = httpResponse.bufferFactory().wrap(("{\n"
+ " \"code\": \"1414\","
+ " \"message\": \"服务器限流\","
+ " \"data\": \"Server throttling\","
+ " \"success\": false"
+ "}").getBytes(StandardCharsets.UTF_8));
return httpResponse.writeWith(Mono.just(buffer));
});
});
}
private <T> T getOrDefault(T configValue, T defaultValue) {
return (configValue != null) ? configValue : defaultValue;
}
}
然后将配置文件配置的filtername配置为重写后的过滤器:
- id: test
uri: lb://test-service
predicates:
- Path=/test/**
filters:
- name: GatewayRequestRateLimiter #此处修改
args:
key-resolver: '#{@keyResolver}'
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 3
测试代码:
成功返回自定义内容!