我遇到了同樣的問題,只使用Redis 2.8.24,但也使用它的API速率限制。
我懷疑你正在做限制這樣的(使用Ruby代碼只是爲例子)速度:
def consume_rate_limit
# Fetch the current limit for a given account or user
rate_limit = Redis.get('available_limit:account_id')
# It can be nil if not already initialized or if TTL has expired
if rate_limit == nil
# So let's just initialize it to the initial limit
# Let's use a window of 10,000 requests, resetting every hour
rate_limit = 10000
Redis.setex('available_limit:account_id', 3600, rate_limit - 1)
else
# If the key already exists, just decrement the limit
Redis.decr('available_limit:account_id')
end
# Return true if we are OK or false the limit has been reached
return (rate_limit > 0)
end
嗯,我就是用這個方法,發現有間「搞定」一個cocurrency問題和「decr」調用導致你描述的確切問題。
當速率限制密鑰的TTL在「get」調用之後但在「decr」調用之前到期時,會發生此問題。將會發生什麼:
首先,「get」調用將返回當前的限制。假設它返回500. 然後,只需幾分之一毫秒的時間,該密鑰的TTL就會過期,因此它在Redis中不再存在。 所以代碼繼續運行,並達到「decr」調用。也達到了錯誤的位置:
的decr documentation狀態(我的重點):
遞減一保存在按鍵的號碼。 如果密鑰不存在 ,則在執行操作之前將其設置爲0。 (...)
由於密鑰已被刪除(因爲它已過期),「decr」指令將初始化密鑰爲零,然後遞減密鑰值,這就是密鑰值爲-1的原因。密鑰將在沒有TTL的情況下創建,因此發行TTL key_name
也會發布-1。
解決方案可能是使用MULTI和EXEC命令將所有代碼包含在transaction block中。但是,這可能會很慢,因爲它需要多次往返Redis服務器。
我用過的解決方案是編寫一個Lua腳本並使用EVAL命令運行它。它具有原子化的優勢(意味着沒有併發問題),並且只有一個RTT連接到Redis服務器。
local expire_time = ARGV[1]
local initial_rate_limit = ARGV[2]
local rate_limit = redis.call('get', KEYS[1])
-- rate_limit will be false when the key does not exist.
-- That's because redis converts Nil to false in Lua scripts.
if rate_limit == false then
rate_limit = initial_rate_limit
redis.call('setex', KEYS[1], initial_rate_limit, rate_limit - 1)
else
redis.call('decr', KEYS[1])
end
return rate_limit
要使用它,我們可以把consume_rate_limit
功能如下:
def consume_rate_limit
script = <<-LUA
... that script above, omitting it here not to bloat things ...
LUA
rate_limit = Redis.eval(script, keys: ['available_limit:account_id'], argv: [3600, 10000]).to_i
return (rate_limit > 0)
end
'-1'意味着沒有與相關的關鍵到期。我想有人在密鑰上調用了「設置密鑰值」,並且過期已被重置。 –
@for_stack這將是我的回答太 –
有趣。謝謝,我會研究爲什麼會發生這種情況。 –