SpringCloudGateway内存中基于令牌桶进行限流

Posted 北亮bl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloudGateway内存中基于令牌桶进行限流相关的知识,希望对你有一定的参考价值。

研究了一下网上的很多文章,基本都是使用 RedisRateLimiter,即根据Redis来进行限流操作。
这样有个好处,就是网关的集群可以使用同一套数据进行限流;
当然也有缺点,网关本来就是所有流量的集中出入口,如果每个请求都要往返一次Redis,无疑加重了网关的负担,性能有下降。

本文介绍了如何直接在内存中使用令牌桶算法进行限流,在内存中限流的缺点,当然就是对集群不友好了,比如有3个网关实例在运行,每个网关按每秒1个令牌,令牌桶容量为10,那么实际上就是每秒3个令牌,最大容量是10~30波动,不过基于令牌桶算法的控制,我觉得还是可以接受的。


本文基于 Java1.8、 spring-cloud.version:2020.0.2、springboot:2.4.4进行开发。 1、直接引入现成的令牌桶组件,就不要自己开发了: ```java com.github.vladimir-bukhtoyarov bucket4j-core 4.0.0 ``` 注:[令牌桶算法介绍](https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000)

2、新建一个限流类MyRateLimiter,并实现AbstractRateLimiter<MyRateLimiter.Config>。限流的关键代码就在这里了
注:MyRateLimiter.Config是限流类使用的配置数据类

import io.github.bucket4j.*;
import lombok.Data;
import org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter;
import org.springframework.cloud.gateway.support.ConfigurationService;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MyRateLimiter extends AbstractRateLimiter<MyRateLimiter.Config> 
    public static final String CONFIGURATION_PROPERTY_NAME = "in-memory-rate-limiter";
    private final Config defaultConfig;
    private final Map<String, Bucket> ipBucketMap = new ConcurrentHashMap<>();

    /**
     * 构造函数
     * @param service 用于读取配置文件里的令牌桶配置
     */
    public MyRateLimiter(ConfigurationService service) 
        super(Config.class, CONFIGURATION_PROPERTY_NAME, service);
        defaultConfig = new Config();
        defaultConfig.setReplenishRate(10);
        defaultConfig.setBurstCapacity(100);
    

    @Override
    public Mono<Response> isAllowed(String routeId, String id) 
        Config routeConfig = getConfig().get(routeId);
        if (routeConfig == null) 
            if (defaultConfig == null) 
                throw new IllegalArgumentException("No Configuration found for route " + routeId);
            
            routeConfig = defaultConfig;
        

        // 每秒生成多少个令牌,就是当令牌桶为空时,每秒最多允许多少个用户进入,比如10
        int replenishRate = routeConfig.getReplenishRate();
        // 令牌桶的最大容量,就是突发涌入大量请求时,最多允许多少用户进入,比如100
        int burstCapacity = routeConfig.getBurstCapacity();
        // 初始化当前id的桶
        Bucket bucket = ipBucketMap.computeIfAbsent(id, k -> 
            Refill refill = Refill.of(replenishRate, Duration.ofSeconds(1));
            Bandwidth limit = Bandwidth.classic(burstCapacity, refill);
            return Bucket4j.builder().addLimit(limit).build();
        );
        Map<String, String> headers = new HashMap<>();
        // 尝试获取,同时得到剩余令牌数
        ConsumptionProbe probe = bucket.tryConsumeAndReturnRemaining(1);
        // 把剩余令牌数写入Header
        headers.put("remaining", String.valueOf(probe.getRemainingTokens()));
        if (probe.isConsumed()) 
            // 拿到令牌,允许进入
            return Mono.just(new Response(true, headers));
         else 
            // 没令牌了,返回429,不允许进入
            return Mono.just(new Response(false, headers));
        
    
    @Data
    public static class Config 
        private int replenishRate;
        private int burstCapacity;
    

3、定义限流使用的2个Bean:
KeyResolver:用于得到限流的唯一id,就是上面类里 isAllowed方法的id;
RateLimiter:用于得到限流类,就是上面的MyRateLimiter了。

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.support.ConfigurationService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

@Configuration
public class DemoAutoConfiguration 
    @Bean
    public KeyResolver myKeyResolver() 
            return exchange -> Mono.just(exchange.getRequest().getPath().value());
    
    @Bean
    public RateLimiter myRateLimiter(ConfigurationService service) 
        return new MyRateLimiter(service);
    

4、最后就是去修改配置文件,添加一些路由的限流配置了,参考:

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      routes:
        - id: mall-service            # 路由ID,必须唯一
          uri: lb://demo-mall         # proxy_pass转发到的目标地址
          predicates:
            - Path=/mall/**           # 匹配规则
          filters:
            - name: RequestRateLimiter
              args:
                key-resolver: "#@myKeyResolver"
                #rate-limiter: "#@myRateLimiter"
                in-memory-rate-limiter:
                  replenish-rate: 1   # 对应 MyRateLimiter.Config的属性,不能用 replenishRate
                  burst-capacity: 2   # 对应 MyRateLimiter.Config的属性,不能用 burstCapacity

5、好了,到这里,基于内存的限流开发和配置就完成了,可以跑起项目来看效果了。
完整的示例代码已经上传到github,你可以直接下载并启动它进行尝试:
去看代码

以上是关于SpringCloudGateway内存中基于令牌桶进行限流的主要内容,如果未能解决你的问题,请参考以下文章

尽管设置了承载令牌,但 Spring Cloud Gateway 重定向到 Keycloak 登录页面

限流10万QPS跨域过滤器令牌桶算法-网关Gateway内容都在这儿

仅使用承载保护 Spring Cloud Gateway

基于SpringCloudGateway实现微服务网关

基于SpringCloudGateway实现微服务网关

基于SpringCloudGateway实现微服务网关