A use case of Redis Lua script:Rate Limiter in Spring Cloud Gateway

I had been looking for a tangible use case where Redis Lua script is needed, and finally found a perfect example in reading the source code of Spring Cloud Gateway.

Background

The rate limiter algorithm used in Spring Cloud Gateway is the Token Bucket Algorithm. Which has two parameters.

1.replenishRate:
how many requests per second do you want a user to be allowed to do.
This is the rate that the token bucket is filled.

2.burstCapacity:
the maximum number of requests a user is allowed to do in a single second.
This is the number of tokens the token bucket can hold.

If at time t1, there are c1 tokens available, at a later time t2, the available tokens are min(c1+(t2-t1)* replenishRate, burstCapacity)

Implementation

For each key that corresponds to the rate limit unit(for example, the combination of routeId and userId, or userId alone), the most recent timestamp and remaining available token number are saved in Redis, with request_rate_limiter.{key}.tokens and request_rate_limiter.{key}.timestamp as the Redis key name.( using `{}` around keys to use Redis Key hash tags allows for using redis cluster )

As a naive implementation, to check whether a new request is allowed, we just calculate the current available token counts using the following logic(roughly).

  boolean is allowed=false;
  int current_available_tokens = min(
    redis_get(request_rate_limiter.{key}.tokens)
       +(current_time_in_second-redis_get(request_rate_limiter.{key}.timestamp))* replenishRate, 
    burstCapacity
    )
  if(current_available_tokens>=1) {
    allowed=true;
    // update the current token resource status
    redis_set(request_rate_limiter.{key}.tokens, current_available_tokens-1);
    redis_set(request_rate_limiter.{key}.timestamp, current_time_in_second);
  }

But there are two problems if the above logic was implemented in the application side(namely, the Java code of Spring Cloud Gateway)
1.low performance :
To check a single request is allowed, 4 Redis commands need to be executed, considering the network traffic speed, a large performance penalty will be incurred.
2.concurrency problem :
We should provide a mechanism that concurrent access to the same key is forbidden in the duration from current_available_tokens is calculated until current_available_tokens was updated. This incurs both complexity and performance penalty.

So how to solve the following problems?
The answer is using Lua script, implementing the check logic above in the Redis server side.
This perfectly solves the above problems.
1.Only one Redis command(EVAL) is issued.
2.Because Redis server is single-threaded, concurrency problems can be avoided.

The complete source code of the rate limiter Lua script  can be found here.