php uses lua+redis current limiting, counter mode, token bucket mode

Posted by MattG on Sat, 07 Dec 2019 19:42:14 +0100

lua advantages
Reduce network overhead: code without Lua needs to send multiple requests to Redis, while script only needs one time to reduce network transmission;
Atomic operation: Redis executes the entire script as an atom without worrying about concurrency or transaction;
Reuse: the script will be permanently saved in Redis, and other clients can continue to use it

Counter mode:
Use lua script to finish processing at one time to achieve atomicity, judge whether it reaches the limit value through INCR auto increment count, return the current limit when it reaches the limit value, and the expiration time of adding key should be too long

$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
Each request takes a token in the bucket. If there is a token, it will pass. Otherwise, it will return, and put the token into the bucket slowly according to the algorithm

$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 number of keys, 3 search for data key, 4 limit key, 5 number in barrels, 6 time stamp, 7 expiration time
            Redis::eval(1,2,3,4,5,6,7 parameter);

Topics: PHP Redis network Laravel