Я столкнулся с тем же вопросом, только используя 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 ключа ограничения скорости истекает сразу после вызова «получить», но до вызова «decr». Что произойдет:
Сначала звонок «get» вернет текущий предел. Предположим, что оно вернулось 500. Затем, всего за несколько миллисекунд, TTL этого ключа истекает, поэтому в Redis он больше не существует. Таким образом, код продолжает работать и достигается вызов «decr». Кроме того, ошибка достигается здесь:
В decr documentation государства (курсив мой):
Уменьшает число, сохраненное на ключ один. Если ключ не , он установлен в 0 перед выполнением операции. (...)
Поскольку ключ был удален (поскольку он истек), инструкция «decr» инициализирует ключ до нуля, а затем уменьшает его, поэтому значение ключа равно -1. И ключ будет создан без TTL, поэтому выпуск TTL key_name
также выдаст -1.
Решение для этого может заключаться в том, чтобы обернуть весь этот код внутри transaction block с использованием команд MULTI и EXEC. Однако это может быть медленным, поскольку для повторного подключения к серверу 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, который был бы моим ответом тоже –
интересный. спасибо, я посмотрю, почему это произойдет. –