系统定制开发Spring Cloud Gateway实现限流

Spring Cloud Gateway实现限流

背景

切换为spring cloud gateway时,系统定制开发需要重新实现限流逻辑。系统定制开发本文主要整理了spring cloud gateway系统定制开发中如何实现限流。

zuul中的限流

之前zuul系统定制开发的限流是通过guava系统定制开发提供的令牌桶算法实现的,通过一个全局的过滤器,对所有经过的请求,以IP地址作区分进行限流。

引入guava依赖:

<dependency>  <groupId>com.google.guava</groupId>  <artifactId>guava</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4

具体代码案例:

/** * 自定义过滤器 * * @author yuanzhihao * @since 2022/4/27 */@Component@Slf4jpublic class RequestRateLimitFilter implements Filter {    private static final Cache<String, RateLimiter> RATE_LIMITER_CACHE = CacheBuilder            .newBuilder()            .maximumSize(1000)            .expireAfterAccess(1, TimeUnit.HOURS)            .build();    private static final double DEFAULT_PERMITS_PER_SECOND = 1; // 令牌桶每秒填充速率    @SneakyThrows    @Override    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)            throws IOException, ServletException {        String remoteAddr = servletRequest.getRemoteAddr();        RateLimiter rateLimiter = RATE_LIMITER_CACHE.get(remoteAddr, () -> RateLimiter.create(DEFAULT_PERMITS_PER_SECOND));        if (rateLimiter.tryAcquire()) {            filterChain.doFilter(servletRequest, servletResponse);        } else {            ((HttpServletResponse) servletResponse).setStatus(HttpStatus.TOO_MANY_REQUESTS.value());            servletResponse.setContentType("application/json;charset=UTF-8");            servletResponse.getWriter().write("Too Many Request!!!");        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

Spring Cloud Gateway实现限流

编写自定义的限流过滤器

参考zuul中限流方法,可以很容易的编写一个全局过滤器来进行限流,具体代码:

/** * 自定义过滤器 * * @author yuanzhihao * @since 2022/4/27 */@Component@Slf4j@Order(-1)public class RequestRateLimitFilter implements GlobalFilter {    private static final Cache<String, RateLimiter> RATE_LIMITER_CACHE = CacheBuilder            .newBuilder()            .maximumSize(1000)            .expireAfterAccess(1, TimeUnit.HOURS)            .build();    private static final double DEFAULT_PERMITS_PER_SECOND = 1; // 令牌桶每秒填充速率    @SneakyThrows    @Override    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {        String remoteAddr = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();        RateLimiter rateLimiter = RATE_LIMITER_CACHE.get(remoteAddr, () -> RateLimiter.create(DEFAULT_PERMITS_PER_SECOND));        if (rateLimiter.tryAcquire()) {            return chain.filter(exchange);        }        ServerHttpResponse response = exchange.getResponse();        response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");        DataBuffer dataBuffer = response.bufferFactory().wrap("Too Many Request!!!".getBytes(StandardCharsets.UTF_8));        return response.writeWith(Mono.just(dataBuffer));    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

不过这种限流的粒度非常大,对于所有的请求都进行了限流,不能进行定制化的限流。之前博客里面整理过gatewayFilters局部过滤器的用法,这边可以参考进行限流过滤器的编写。

贴一下案例代码:

/** * 自定义局部限流 * * @author yuanzhihao * @since 2022/4/27 */@Componentpublic class CustomRequestRateLimitGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomRequestRateLimitGatewayFilterFactory.Config> {    public CustomRequestRateLimitGatewayFilterFactory() {        super(Config.class);    }    private static final Cache<String, RateLimiter> RATE_LIMITER_CACHE = CacheBuilder            .newBuilder()            .maximumSize(1000)            .expireAfterAccess(1, TimeUnit.HOURS)            .build();    @Override    public GatewayFilter apply(Config config) {        return new GatewayFilter() {            @SneakyThrows            @Override            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {                String remoteAddr = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();                RateLimiter rateLimiter = RATE_LIMITER_CACHE.get(remoteAddr, () ->                        RateLimiter.create(Double.parseDouble(config.getPermitsPerSecond())));                if (rateLimiter.tryAcquire()) {                    return chain.filter(exchange);                }                ServerHttpResponse response = exchange.getResponse();                response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);                response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");                DataBuffer dataBuffer = response.bufferFactory().wrap("Too Many Request!!!".getBytes(StandardCharsets.UTF_8));                return response.writeWith(Mono.just(dataBuffer));            }        };    }    @Override    public List<String> shortcutFieldOrder() {        return Collections.singletonList("permitsPerSecond");    }    @Data    public static class Config {        private String permitsPerSecond; // 令牌桶每秒填充速率    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

对应请求路由生效过滤器:

- id: server1 uri: lb://eureka-server1 predicates:   - Path=/server1/hello filters:   - CustomRequestRateLimit=1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Spring Cloud Gateway自实现的限流过滤器

spring cloud gateway里面也提供了一个自实现的限流过滤器org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory,这个过滤器里面有两个参数,一个是KeyResolver,这个参数可以动态的指定限流的一些key(个人理解,这边还是详细参考下官方文档~~~),比如这个key可以是访问的IP。

还有一个是RateLimiter,这个参数是具体的限流策略,在spring cloud gateway里面,它的默认实现是RedisRateLimiter,它采用的也是。

首先我们实现KeyResolver接口,指定限流的key是访问的IP地址:

/** * 根据ip地址进行限流 *  * @author yuanzhihao * @since 2022/4/27 */@Componentpublic class HostAddrKeyResolver implements KeyResolver {    @Override    public Mono<String> resolve(ServerWebExchange exchange) {        return Mono.just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress());    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

添加spring-boot-starter-data-redis-reactive依赖,使用RedisRateLimiter限流:

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4

配置文件中添加redis和限流的配置信息:

redis:  host: 127.0.0.1  port: 6379
  • 1
  • 2
  • 3
- id: server2 uri: lb://eureka-server1 predicates:   - Path=/server1/twoDog filters:   - name: RequestRateLimiter     args:      key-resolver: "#{@hostAddrKeyResolver}"      redis-rate-limiter.replenishRate: 1 # 令牌桶填充的速率 秒为单位      redis-rate-limiter.burstCapacity: 1 # 令牌桶总容量      redis-rate-limiter.requestedTokens: 1 # 每次请求获取的令牌数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这边的参数表示填充的速率是1/s,桶的总容量也为1,每次请求获取一个令牌。也就是一秒只允许一次请求。测试生效:

结语

最后还是倾向于使用自定义的限流,他不需要引入redis组件,而且也可以自己重写响应到页面,更加灵活一点。

参考地址:

代码地址:

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发