• Spring Cloud Gateway实现限流


    Spring Cloud Gateway实现限流

    背景

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

    zuul中的限流

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

    引入guava依赖:

    
      com.google.guava
      guava
    
    
    • 1
    • 2
    • 3
    • 4

    具体代码案例:

    /**
     * 自定义过滤器
     *
     * @author yuanzhihao
     * @since 2022/4/27
     */
    @Component
    @Slf4j
    public class RequestRateLimitFilter implements Filter {
    
        private static final Cache 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 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 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
     */
    @Component
    public class CustomRequestRateLimitGatewayFilterFactory extends AbstractGatewayFilterFactory {
        public CustomRequestRateLimitGatewayFilterFactory() {
            super(Config.class);
        }
    
        private static final Cache 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 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 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
     */
    @Component
    public class HostAddrKeyResolver implements KeyResolver {
        @Override
        public Mono 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限流:

    
      org.springframework.boot
      spring-boot-starter-data-redis-reactive
    
    
    • 1
    • 2
    • 3
    • 4

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

    redis:
      host: 127.0.0.1
      port: 6379
    
    
    - 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
    • 12
    • 13
    • 14
    • 15
    • 16

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

    结语

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

    参考地址:https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#the-requestratelimiter-gatewayfilter-factory

    代码地址:https://github.com/yzh19961031/SpringCloudDemo/tree/main/gateway

  • 相关阅读:
    Ubuntu20.04软件安装顺序
    QFramework Pro 开发日志(七)v0.4 版本审核通过 与 对话编辑器功能预告
    GO语言网络编程(并发编程)定时器
    conan2 基础入门(04)-指定编译器(gcc为例)
    用DIV+CSS技术设计的体育主题网站(足球介绍)
    刷题知识回顾《四》多数元素反转链表
    【CSAPP+电流+梯度下降法】九阳神功-速览1
    Vue进阶(幺陆玖)项目部署后IE报 SCRIPT1002:语法错误 解决方案探讨
    告别BeanUtils,Mapstruct从入门到精通
    不会说就少说点
  • 原文地址:https://blog.csdn.net/m0_67401417/article/details/126496854