Pop несколько значений из структуры данных Redis атомарно?
есть ли структура данных Redis, которая позволила бы атомарной операции popping (get+remove) нескольких элементов, которые она содержит?
есть хорошо известные SPOP или RPOP, но они всегда возвращают одно значение. Поэтому, когда мне нужны первые N значений из set / list, мне нужно вызвать команду N-times, что дорого. Допустим, набор / список содержит миллионы элементов. Есть ли что-нибудь вроде SPOPM "setName" 1000
, который вернет и удалит 1000 случайных элементов из набора или RPOPM "listName" 1000
, что вернет 1000 правых элементов из списка?
Я знаю, что есть такие команды, как SRANDMEMBER и LRANGE, но они не удаляют элементы из структуры данных. Их можно удалить отдельно. Однако, если есть больше клиентов, читающих из одной структуры данных, некоторые элементы могут быть прочитаны более одного раза, а некоторые могут быть удалены без чтения! Следовательно, атомарность-это то, о чем я спрашиваю.
кроме того, я в порядке, если сложность времени для такой операции более дорогой. Я сомневаюсь, что это будет дороже, чем выдача N (скажем, 1000, N из предыдущего примера) отдельных запросов на сервер Redis.
Я также знаю о поддержке отдельных транзакций. Однако это предложение из Redis docs не позволяет мне использовать его для параллельных процессов, изменяющих набор (деструктивное чтение из него):
при использовании WATCH EXEC будет выполнять команды только в том случае, если наблюдаемые клавиши не были изменены, что позволяет проверить и установить механизм.
7 ответов
начиная с Redis 3.2, команда SPOP
есть [count]
аргумент для извлечения нескольких элементов из набора.
использовать LRANGE
с LTRIM
на трубопровод. Конвейер будет выполняться как одна атомарная транзакция. Ваше беспокойство выше о WATCH
, EXEC
не будет применяться здесь, потому что вы используете LRANGE
и LTRIM
как одна транзакция без возможности каких-либо других транзакций от любых других клиентов, чтобы встать между ними. Попробовать ее.
чтобы развернуть ответ Eli с полным примером для коллекций списков, используя lrange
и ltrim
builtins вместо Lua:
127.0.0.1:6379> lpush a 0 1 2 3 4 5 6 7 8 9
(integer) 10
127.0.0.1:6379> lrange a 0 3 # read 4 items off the top of the stack
1) "9"
2) "8"
3) "7"
4) "6"
127.0.0.1:6379> ltrim a 4 -1 # remove those 4 items
OK
127.0.0.1:6379> lrange a 0 999 # remaining items
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
6) "0"
если бы вы хотели сделать операцию атомной, вы бы обернули lrange и ltrim в multi
и exec
команды.
также, как отмечалось в другом месте, вы, вероятно, должны ltrim
на количество возвращенных предметов не количество предметов, которые вы просили. например, если вы сделали lrange a 0 99
но получил 50 предметов, которые вы бы ltrim a 50 -1
не ltrim a 100 -1
.
чтобы реализовать семантику очереди вместо стека, замените lpush
С rpush
.
Если вы хотите сценарий lua, это должно быть быстро и легко.
local result = redis.call('lrange',KEYS[1],0,ARGV[1]-1)
redis.call('ltrim',KEYS[1],ARGV[1],-1)
return result
тогда вам не нужно петлять.
обновление: Я попытался сделать это с помощью srandmember (в 2.6) со следующим скриптом:
local members = redis.call('srandmember', KEYS[1], ARGV[1])
redis.call('srem', KEYS[1], table.concat(table, ' '))
return members
но я получаю сообщение об ошибке:
error: -ERR Error running script (call to f_6188a714abd44c1c65513b9f7531e5312b72ec9b):
Write commands not allowed after non deterministic commands
Я не знаю, позволит ли это будущая версия, но я предполагаю, что нет. Я думаю, что это будет проблема с репликацией.
Я думаю, вы должны посмотреть на поддержку LUA в Redis. Если вы пишете сценарий LUA и выполняете его на redis, гарантируется, что он является атомарным (потому что Redis является монопоточным). Никакие запросы не будут выполняться до конца вашего сценария LUA (т. е. вы не можете реализовать большую задачу в LUA или redis будет медленным).
Итак, в этом скрипте вы добавляете SPOP и RPOP, вы можете добавить результаты от каждой команды redis в массив LUA, например, а затем вернуть массив в свой redis клиент.
то, что документация говорит о MULTI, заключается в том, что это оптимистичная блокировка, что означает, что она будет повторять попытку делать multi с часами, пока наблюдаемое значение не будет изменено. Если у вас много записей о наблюдаемом значении, это будет медленнее, чем "пессимистическая" блокировка (как и многие базы данных SQL: POSTGRESQL, MYSQL...) что каким-то образом "останавливает мир", чтобы запрос был выполнен первым. Пессимистическая блокировка не реализована в redis, но ее можно реализовать если вы хотите, но это сложно, и, возможно, Вам это не нужно (не так много пишет об этом значении: оптимистического должно быть достаточно).
вы, вероятно, можете попробовать сценарий lua (script.lua) вот так:
local result = {}
for i = 0 , ARGV[1] do
local val = redis.call('RPOP',KEYS[1])
if val then
table.insert(result,val)
end
end
return result
вы можете назвать это так:
redis-cli eval "$(cat script.lua)" 1 "listName" 1000
Redis 4.0 + теперь поддерживает модули которые добавляют все виды новых функций и типов данных с гораздо более быстрой и безопасной обработкой, чем сценарии Lua или multi
/exec
трубопроводов.
Redis Labs, текущий спонсор Redis, имеет полезный набор модулей расширения под названием redex здесь:https://github.com/RedisLabsModules/redex
на rxlists
модуль добавляет несколько операций списка, включая LMPOP
и RMPOP
так вы можете атомарно вывести несколько значений из списка Redis. Логика по-прежнему O(n) (в основном делает один pop в цикле), но все, что вам нужно сделать, это установить модуль один раз и просто отправить эту пользовательскую команду. Я использую его в списках с миллионами элементов, и тысячи выскочили сразу, генерируя 500MB + сетевого трафика без проблем.