marcosbarbero / spring-cloud-zuul-ratelimit

Rate limit auto-configure for Spring Cloud Netflix Zuul
https://blog.marcosbarbero.com/spring-cloud-netflix-zuul-rate-limit/
Apache License 2.0
1.14k stars 389 forks source link

Redis current limiting performance problem #103

Closed luoyoubao closed 6 years ago

luoyoubao commented 6 years ago

When the repository=REDIS is configured, the test current limit performance is poor. Check the source code to find out whether the key is expired each time the current limit request is checked. Therefore, the optimization is as follows and the performance is improved by about 2~3 times. Is there any special consideration? I hope to get a reply, thank you

    protected void calcRemainingLimit(Long limit, Long refreshInterval,
                                    Long requestTime, String key, Rate rate) {
        if (limit != null) {
            handleExpiration(key, refreshInterval, rate);
            long usage = requestTime == null ? 1L : 0L;
            Long current = 0L;
            try {
                current = this.redisTemplate.boundValueOps(key).increment(usage);
            } catch (RuntimeException e) {
                String msg = "Failed retrieving rate for " + key + ", will return limit";
                rateLimiterErrorHandler.handleError(msg, e);
            }
            rate.setRemaining(Math.max(-1, limit - current));
        }
    }

    private void handleExpiration(String key, Long refreshInterval, Rate rate) {
        Long expire = null;
        try {
            expire = this.redisTemplate.getExpire(key);
            if (expire == null || expire == -1) {
                this.redisTemplate.expire(key, refreshInterval, SECONDS);
                expire = refreshInterval;
            }
        } catch (RuntimeException e) {
            String msg = "Failed retrieving expiration for " + key + ", will reset now";
            rateLimiterErrorHandler.handleError(msg, e);
        }
        rate.setReset(SECONDS.toMillis(expire == null ? 0L : expire));
    }

Modified to directly increase, if it is the key is set for the first time and then set the expiration time

    protected void calcRemainingLimit(Long limit, Long refreshInterval,
                                    Long requestTime, String key, Rate rate) {
        if (limit != null) {
            handleExpiration(key, refreshInterval, rate);
            long usage = requestTime == null ? 1L : 0L;
            Long current = 0L;
            try {
                current = redisTemplate.opsForValue().increment(key,  usage);
                // current = this.redisTemplate.boundValueOps(key).increment(usage);
                if (1 == current) {
                    this.redisTemplate.expire(key, refreshInterval, SECONDS);
                }
            } catch (RuntimeException e) {
                String msg = "Failed retrieving rate for " + key + ", will return limit";
                rateLimiterErrorHandler.handleError(msg, e);
            }
            rate.setRemaining(Math.max(-1, limit - current));
        }
    }
marcosbarbero commented 6 years ago

Hi @luoyoubao you can open a PR with your changes and we can evaluate.

luoyoubao commented 6 years ago

Hi @marcosbarbero The specific test situation is as follows: My configuration:

zuul:
  routes:
    user:
      ## url: http://localhost:8081
      path: /user/**
      service-id: user-service
      stripPrefix: true
  ratelimit:
    enabled: true
    repository: REDIS
    default-policy:
      limit: 100
      refresh-interval: 10
      type:
        - url

siege -c 200 -r 1 http://localhost:8088/user-service/user/say Test result===> Transactions: 200 hits Availability: 100.00 % Elapsed time: 8.27 secs Data transferred: 0.01 MB Response time: 4.52 secs Transaction rate: 24.18 trans/sec Throughput: 0.00 MB/sec Concurrency: 109.33 Successful transactions: 101 Failed transactions: 0 Longest transaction: 8.16 Shortest transaction: 1.19

siege -c 500 -r 1 http://localhost:8088/user-service/user/say Test result===> Transactions: 255 hits Availability: 100.00 % Elapsed time: 8.55 secs Data transferred: 0.02 MB Response time: 4.26 secs Transaction rate: 29.82 trans/sec Throughput: 0.00 MB/sec Concurrency: 127.17 Successful transactions: 100 Failed transactions: 0 Longest transaction: 8.34 Shortest transaction: 0.03

siege -c 200 -r 10 http://localhost:8088/user-service/user/say Test result===> Transactions: 2000 hits Availability: 100.00 % Elapsed time: 84.28 secs Data transferred: 0.16 MB Response time: 8.01 secs Transaction rate: 23.73 trans/sec Throughput: 0.00 MB/sec Concurrency: 190.12 Successful transactions: 876 Failed transactions: 0 Longest transaction: 21.21 Shortest transaction: 0.05

Test 10 times, remove the maximum and minimum values and average,Average requests per second:25.91 req/s

After optimization: siege -c 200 -r 1 http://localhost:8088/user-service/user/say Test result===> Transactions: 200 hits Availability: 100.00 % Elapsed time: 3.56 secs Data transferred: 0.02 MB Response time: 1.69 secs Transaction rate: 56.18 trans/sec Throughput: 0.01 MB/sec Concurrency: 94.92 Successful transactions: 72 Failed transactions: 0 Longest transaction: 3.46 Shortest transaction: 0.03

siege -c 500 -r 1 http://localhost:8088/user-service/user/say Test result===> Transactions: 255 hits Availability: 100.00 % Elapsed time: 3.70 secs Data transferred: 0.02 MB Response time: 1.72 secs Transaction rate: 62.92 trans/sec Throughput: 0.01 MB/sec Concurrency: 118.21 Successful transactions: 100 Failed transactions: 0 Longest transaction: 3.45 Shortest transaction: 0.01

siege -c 200 -r 10 http://localhost:8088/user-service/user/say Test result===> Transactions: 2000 hits Availability: 100.00 % Elapsed time: 30.45 secs Data transferred: 0.23 MB Response time: 2.82 secs Transaction rate: 65.68 trans/sec Throughput: 0.01 MB/sec Concurrency: 185.42 Successful transactions: 324 Failed transactions: 0 Longest transaction: 6.43 Shortest transaction: 0.02

Test 10 times, remove the maximum and minimum values and average,Average requests per second: 61.59 req/s

Also tested the performance of these two programs in springboot

private long getNum() {
    long num = redisTemplate.opsForValue().increment(REDISKEY, 1);
    if (num == 1) {
        redisTemplate.expire(REDISKEY, 10, TimeUnit.SECONDS);
    }
    return num;
}

private Long increment() {
    Long expire = this.redisTemplate.getExpire(REDISKEY);
    if (expire == null || expire == -1) {
        this.redisTemplate.expire(REDISKEY, 10, SECONDS);
    }
    return redisTemplate.opsForValue().increment(REDISKEY, 1);
}

The result is the same, because getExpire has an IO request, so it seems that the direct increment and then the return value of the program is better at the time of pressure measurement.