-- valueKey timestampKey | limit intervalMS nowMS [amount]local valueKey = KEYS[1] -- "limit:1:V"local timestampKey = KEYS[2] -- "limit:1:T"local limit = tonumber(ARGV[1])
local intervalMS = tonumber(ARGV[2])
local amount = math.max(tonumber(ARGV[3]), 0)
local force = ARGV[4] =="true"local lastUpdateMS
local prevTokens
-- Use effects replication, not script replication;; this allows us to call 'TIME' which is non-deterministic
redis.replicate_commands()
local time = redis.call('TIME')
local nowMS = math.floor((time[1] *1000) + (time[2] /1000))
local initialTokens = redis.call('GET',valueKey)
local initialUpdateMS =falseif initialTokens ==falsethen-- If we found no record, we temporarily rewind the clock to refill-- via addTokens below
prevTokens =0
lastUpdateMS = nowMS - intervalMS
else
prevTokens = initialTokens
initialUpdateMS = redis.call('GET',timestampKey)
if(initialUpdateMS ==false) then-- this is a corruption-- 如果資料有問題,需要回推 lastUpdateMS 時間,也就是用現在時間回推殘存 Token 數量的回補時間
lastUpdateMS = nowMS - ((prevTokens / limit) * intervalMS)
else
lastUpdateMS = initialUpdateMS
endend
local addTokens = math.max(((nowMS - lastUpdateMS) / intervalMS) * limit, 0)
-- calculated token balance coming into this transactionlocal grossTokens = math.min(prevTokens + addTokens, limit)
-- token balance after trying this transactionlocal netTokens = grossTokens - amount
-- time to fill enough to retry this amountlocal retryDelta =0local rejected =falselocal forced =falseif netTokens <0then-- we used more than we haveif force then
forced =true
netTokens =0-- drain the swampelse
rejected =true
netTokens = grossTokens -- rejection doesn't eat tokensend-- == percentage of `intervalMS` required before you have `amount` tokens
retryDelta = math.ceil(((amount - netTokens) / limit) * intervalMS)
else-- polite transaction-- nextNet == pretend we did this again...local nextNet = netTokens - amount
if nextNet <0then-- ...we would need to wait to repeat-- == percentage of `invervalMS` required before you would have `amount` tokens again
retryDelta = math.ceil((math.abs(nextNet) / limit) * intervalMS)
endend
如果成功操作 ( rejected == false ),則延長 key 的過期時間
1
2
3
4
5
6
7
8
9
10
if rejected ==falsethen
redis.call('PSETEX',valueKey,intervalMS,netTokens)
if addTokens >0or initialUpdateMS ==falsethen-- we filled some tokens, so update our timestamp
redis.call('PSETEX',timestampKey,intervalMS,nowMS)
else-- we didn't fill any tokens, so just renew the timestamp so it survives with the value
redis.call('PEXPIRE',timestampKey,intervalMS)
endend