php uses lua+redis to implement current limiting counter mode and token bucket mode

  • 2021-12-04 18:26:44
  • OfStack

Advantages of lua

Reduce network overhead: code that does not use Lua needs to send multiple requests to Redis, while scripts only need one time, reducing network transmission;

Atomic operation: Redis executes the whole script as an atom, without worrying about concurrency, so there is no need for transactions;

Reuse: The script will be permanently saved in Redis, and other clients can continue to use it.

Counter mode:

Using lua script to complete the processing once to achieve atomicity, through INCR self-increasing count, judge whether it reaches the limit value, reach the limit value, return to current limit, add key expiration time should be excessive range


$lua = '
        local i = redis.call("INCR", KEYS[1]) 
        if i > 10 then
          return "wait"
        else
          if i == 1
          then
            redis.call("expire", KEYS[1], KEYS[2])
          end
          return redis.call("get", KEYS[3])
        end
      ';

laravel request code:


Redis::eval($lua, 3, sprintf(RedisKey::API_LIMIT, $key, $callService['service']), 60, $cache_key);

Token bucket mode

Take 1 token in the bucket every time you request, pass if there is a token, otherwise return, and slowly put the token into the bucket according to Algorithm 1


$lua = '
        local data = redis.call("get", KEYS[2])
        if data then
        
          local dataJson = cjson.decode(data)
          local newNum = math.min(KEYS[3], math.floor(((dataJson["limitVal"] - 1) + (KEYS[3]/KEYS[5]) * (KEYS[4] - dataJson["limitTime"]))))
          
          if newNum > 0 then
          
            local paramsJson = cjson.encode({limitVal=newNum,limitTime=KEYS[4]})
            redis.call("set", KEYS[2], paramsJson)
            return redis.call("get", KEYS[1])
          
          end
           return "wait"
        end
        
        local paramsJson = cjson.encode({limitVal=KEYS[3],limitTime=KEYS[4]})
        redis.call("set", KEYS[2], paramsJson)
        return redis.call("get", KEYS[1])
      ';
      
      // 1. lua Script , 2 KEYS Quantity , 3  Find data key, 4  Limit key, 5  Quantity in barrel , 6  Time stamp , 7  Expired time 
      Redis::eval(1,2,3,4,5,6,7 Parameter );

Related articles: