RequestRateLimiter 限流

  1. 目前gateway的限流是基于redis,前提是部署有redis
  2. 不运行访问,返回HTTP 429 - Too Many Requests

 

10.4.1 搭建基于gateway的限流

10.4.1.1 pom.xml

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>

        </dependency>

 

10.4.1.2 bootstrap.yml

server:

  port: 9101

spring:

  profiles:

    active: redis

  application:

name: gateway

 

10.4.1.3 application.yml

eureka:

  client:

    service-url:

     defaultZone: http://localhost:8086/eureka

spring:

  cloud:

    gateway:

     default-filters:

     routes:

      - id: SERVICE-HI

        uri: lb://service-hi

        predicates:

         - Path=/hi/**

        filters:

         - StripPrefix=1

         - name: RequestRateLimiter

           args:

             redis-rate-limiter.replenishRate: 2

             redis-rate-limiter.burstCapacity: 5

             key-resolver: "#{@ipKeyResolver}"

  redis:

    host: localhost

port: 6379

 

10.1.1.4 启动类和限流方式

@SpringBootApplication

public class GatewayApplication {


    public static void main(String[] args) {

        SpringApplication.run(GatewayApplication.class, args);

    }

@SpringBootApplication

    /*地址限流*/

    @Bean

    @Primary

    public KeyResolver ipKeyResolver() {

        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostString());

    }


    /*用户限流*/

    @Bean

    KeyResolver userKeyResolver() {

        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));

    }


    /*接口限流*/

    @Bean

    KeyResolver apiKeyResolver() {

        return exchange -> Mono.just(exchange.getRequest().getPath().value());

    }

}

 

10.4.2 讲解

10.4.2.1 限流的方式

 

1.ip限流

 public KeyResolver ipKeyResolver() {

        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostString());

    }

 

2.用户限流

 @Bean

    KeyResolver userKeyResolver() {

        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));

    }

  1. 接口限流

    

@Bean

    KeyResolver apiKeyResolver() {

        return exchange -> Mono.just(exchange.getRequest().getPath().value());

}

 

10.4.2.2 配置文件

spring:

  cloud:

    gateway:

     default-filters:

     routes:

      - id: SERVICE-HI

        uri: lb://service-hi

        predicates:

         - Path=/hi/**

        filters:

         - StripPrefix=1

         - name: RequestRateLimiter

           args:

             redis-rate-limiter.replenishRate: 2

             redis-rate-limiter.burstCapacity: 5

             key-resolver: "#{@apiKeyResolver}"

  redis:

    host: localhost

port: 6379

 

  1. filter名称必须是RequestRateLimiter
  2. redis-rate-limiter.replenishRate:允许用户每秒处理多少个请求
  3. redis-rate-limiter.burstCapacity:令牌桶的容量,允许在一秒钟内完成的最大请求数
  4. key-resolver:使用SpEL按名称引用bean

 

可以通过访问接口进行测试,查看redis中的key

注意:

  1. 大括号中就是我们的限流Key,这边是IP,本地的就是localhost
  2. timestamp:存储的是当前时间的秒数,也就是System.currentTimeMillis() / 1000或者Instant.now().getEpochSecond()
  3. tokens:存储的是当前这秒钟的对应的可用的令牌数量

10.4.3 限流源码

10.4.3.1 RequestRateLimiterGatewayFilterFactory.apply()方法

// routeId也就是我们的fsh-house,id就是限流的key,也就是localhost。

public Mono<Response> isAllowed(String routeId, String id) {

 // 会判断RedisRateLimiter是否初始化了

 if (!this.initialized.get()) {

  throw new IllegalStateException("RedisRateLimiter is not initialized");

 }

 // 获取routeId对应的限流配置

 Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig);


 if (routeConfig == null) {

  throw new IllegalArgumentException("No Configuration found for route " + routeId);

 }


 // 允许用户每秒做多少次请求

 int replenishRate = routeConfig.getReplenishRate();


 // 令牌桶的容量,允许在一秒钟内完成的最大请求数

 int burstCapacity = routeConfig.getBurstCapacity();


 try {

  // 限流key的名称(request_rate_limiter.{localhost}.timestamp,request_rate_limiter.{localhost}.tokens)

  List<String> keys = getKeys(id);



  // The arguments to the LUA script. time() returns unixtime in seconds.

  List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",

    Instant.now().getEpochSecond() + "", "1");

  // allowed, tokens_left = redis.eval(SCRIPT, keys, args)

  // 执行LUA脚本

  Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);

    // .log("redisratelimiter", Level.FINER);

  return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))

    .reduce(new ArrayList<Long>(), (longs, l) -> {

     longs.addAll(l);

     return longs;

    }) .map(results -> {

     boolean allowed = results.get(0) == 1L;

     Long tokensLeft = results.get(1);


     Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));


     if (log.isDebugEnabled()) {

      log.debug("response: " + response);

     }

     return response;

    });

 }

 catch (Exception e) {

  log.error("Error determining if user allowed from redis", e);

 }

 return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));

}

10.4.3.2 request_rate_limiter.lua

local tokens_key = KEYS[1]

local timestamp_key = KEYS[2]

--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)


#rate为redis-rate-limiter.replenishRate

#capacity为redis-rate-limiter.brustCapacity

#now当前时间

#requested 源码中是1

local rate = tonumber(ARGV[1])

local capacity = tonumber(ARGV[2])

local now = tonumber(ARGV[3])

local requested = tonumber(ARGV[4])


local fill_time = capacity/rate

local ttl = math.floor(fill_time*2)


--redis.log(redis.LOG_WARNING, "rate " .. ARGV[1])

--redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2])

--redis.log(redis.LOG_WARNING, "now " .. ARGV[3])

--redis.log(redis.LOG_WARNING, "requested " .. ARGV[4])

--redis.log(redis.LOG_WARNING, "filltime " .. fill_time)

--redis.log(redis.LOG_WARNING, "ttl " .. ttl)


local last_tokens = tonumber(redis.call("get", tokens_key))

if last_tokens == nil then

  last_tokens = capacity

end

--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)


local last_refreshed = tonumber(redis.call("get", timestamp_key))

if last_refreshed == nil then

  last_refreshed = 0

end

--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)



#0和(当前时间)-(在redis中request_rate_limiter.{localhost}.timestamp)取大

local delta = math.max(0, now-last_refreshed)


#允许的最大令牌数和last_tokens+(delta*rate)之间取小

local filled_tokens = math.min(capacity, last_tokens+(delta*rate))


#请求个数小于filled_tokens,运行访问

local allowed = filled_tokens >= requested

local new_tokens = filled_tokens

local allowed_num = 0

if allowed then

  new_tokens = filled_tokens - requested

  allowed_num = 1

end


--redis.log(redis.LOG_WARNING, "delta " .. delta)

--redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens)

--redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num)

--redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens)


redis.call("setex", tokens_key, ttl, new_tokens)

redis.call("setex", timestamp_key, ttl, now)


return { allowed_num, new_tokens }

 

 

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值