一,引入依赖
采用redis的令牌桶算法实现
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
二、指定KeyResolver
我们采用IP来做限流;也可以获取request中的用户,根据用户限流;根据接口限流等实现
@SpringBootApplication
@EnableEurekaClient
public class BaseGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(BaseGatewayApplication.class);
}
@Bean(name = "ipKeyResolver")
public KeyResolver ipKeyResolver() {
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
String hostName = exchange.getRequest().getRemoteAddress().getHostName();
System.out.println("hostName:" + hostName);
return Mono.just(hostName);
}
};
}
}
三、配置限流过滤器
filters: 名称默认是 RequestRateLimiter,可以自定义后面会使用自定义的过滤器
redis-rate-limiter.replenishRate:允许用户每秒处理多少个请求。
redis-rate-limiter.burstCapacity:令牌桶的容量
key-resolver:自己定义的KeyResolver的bean对象
spring:
application:
name: ztx-basegateway
redis:
host: 192.168.252.128
port: 6379
cloud:
gateway:
globalcors:
cors-configurations: # 全局允许跨域访问
'[/**]':
allow-credentials: true
allowed-origins: "*"
allowed-headers: "*"
allowed-methods:
- OPTIONS
- GET
- POST
- PUT
- DELETE
routes:
- id: ztx-basemybatis
uri: lb://ztx-basemybatis
predicates:
- Path=/user/**
filters:
- name: RequestRateLimiter #请求数限流
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
四、自定义限流过滤器
以上就实现了gateway限流,但返回结果不利于前端处理,因此这里我们自定义一个限流过滤器对返回结果进行处理。我们需要继承RequestRateLimiterGatewayFilterFactory类,重写apply(RequestRateLimiterGatewayFilterFactory.Config config)的一个方法,具体见该类的源码,这里简单处理一下。
@Component
public class TestRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {
private final RateLimiter defaultRateLimiter;
private final KeyResolver defaultKeyResolver;
public TestRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, @Qualifier("ipKeyResolver") 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 -> {
Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
String routeId = route.getId();
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);
}
System.out.println("限流:" + routeId);
JSONObject resultJson = new JSONObject();
resultJson.put("message", "访问限制,请稍候重试");
resultJson.put("status", 20001);
byte[] bytes = JSONObject.toJSONBytes(resultJson);
ServerHttpResponse httpResponse = exchange.getResponse();
httpResponse.setStatusCode(config.getStatusCode());
if (!httpResponse.getHeaders().containsKey("Content-Type")) {
httpResponse.getHeaders().add("Content-Type", "application/json");
}
DataBuffer buffer = httpResponse.bufferFactory().wrap(bytes);
return httpResponse.writeWith(Flux.just(buffer));
});
});
}
private <T> T getOrDefault(T configValue, T defaultValue) {
return (configValue != null) ? configValue : defaultValue;
}
}
修改配置采用自定义的过滤器TestRequestRateLimiter
filters:
- name: TestRequestRateLimiter #RequestRateLimiter
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
以上就完成了gateway限流的实现,通过测试返回可以现实
{
"message": "访问限制,请稍候重试",
"status": 20001
}