2017-02-20 1 views
2

redis를 사용하여 API의 속도 제한을 관리하고 SETEX을 사용하여 속도 제한이 매시간 자동으로 재설정되도록합니다.TTL을 -1로 유지하는 키 다시 걸림

나는 redis가 일부 키를 제거하지 않고 TTL-1에보고하지 않는 것을 발견했습니다. 다음은 자리 표시 자 IP 주소를 사용하여,이 보여주는 레디 스 - CLI 세션의 예 :의 TTL이 부정적 임에도 불구하고 나는 그것을 GET 때, 레디 스이 키를 제거하지 않습니다

> GET allowance:127.0.0.1 
> 0 
> TTL allowance:127.0.0.1 
-1 
> GET allowance:127.0.0.1 
0 

공지있다.

이 상태를 재현하려고 시도했지만 시도 할 수 없습니다.

이러한 키가 만료되지 않는 불행한 경쟁 조건입니까? 성공적으로 만료 된 수만 개 중에서 약 10 개만이 -1 상태에 머물러 있습니다.

나는 redis_version:2.8.9을 사용하고 있습니다.

+1

는'-1'에는 키와 만료 관련이 없습니다 것을 의미합니다. 누군가가 키에 '키 값 설정'을 호출하고 만료가 재설정 된 것 같습니다. –

+0

@for_stack도 내 대답이 될 것입니다. –

+0

흥미 롭습니다. 고마워, 내가 왜 이런 일이 일어나는지 살펴볼거야. –

답변

0

동일한 문제가 발생하여 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 
관련 문제