동일한 문제가 발생하여 Redis 2.8.24 만 사용했지만 API 속도 제한에도 사용했습니다.
난 당신이 (그냥 예를 들어 사용하여 루비 코드)이 같은 제한 속도 일을 생각 : 나는이 방법을 사용하고 있었다
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
음을 밖으로 발견 "GET"사이 cocurrency 문제가있다 그리고 당신이 묘사 한 정확한 문제로 이끄는 "decr"호출.
속도 제한 키의 TTL이 "get"호출 직후 "decr"호출 전에 만료되면 문제가 발생합니다. 무슨 일이 생길까요 :
먼저 "get"호출은 현재 한계를 반환합니다. 그것이 500을 반환했다고 가정 해 봅시다. 밀리 세컨드의 일부분만으로, 해당 키의 TTL이 만료되므로 더 이상 Redis에 존재하지 않습니다. 그래서 코드는 계속 실행되고 "decr"호출에 도달합니다. 또한 버그가 여기에 도달 :
decr documentation 상태 (내 강조) :
은 하나 키에 저장된 번호를 감소시킵니다. 키가없는 경우 작업을 수행하기 전에 키가 0으로 설정됩니다. (...)
키가 만료되었으므로 "decr"명령은 키를 0으로 초기화 한 다음 감소시켜 키 값이 -1 인 이유입니다. 그리고 키는 TTL없이 생성되므로 TTL key_name
을 발행하면 -1이 발행됩니다.
그 해결책은 MULTI 및 EXEC 명령을 사용하여 transaction block 안에 모든 코드를 래핑하는 것일 수 있습니다. 그러나 Redis 서버로 여러 번 왕복해야하므로 속도가 느릴 수 있습니다.
내가 사용한 솔루션은 Lua 스크립트를 작성하고 EVAL 명령을 사용하여 실행하는 것입니다. 그것은 atomically (동시성 문제를 의미하지 않음)의 이점을 가지며 Redis 서버에 단 하나의 RTT를가집니다.
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도 내 대답이 될 것입니다. –
흥미 롭습니다. 고마워, 내가 왜 이런 일이 일어나는지 살펴볼거야. –