原子地从Redis数据结构中弹出多个值?

时间:2013-12-16 21:58:40

标签: data-structures redis time-complexity atomicity

是否有一个Redis数据结构,它允许弹出(获取+删除)多个元素的原子操作,它包含哪些?

有众所周知的SPOP或RPOP,但它们总是返回单个值。因此,当我需要来自set / list的前N个值时,我需要调用命令N次,这很昂贵。假设集/列表包含数百万个项目。是否有类似SPOPM "setName" 1000的内容,它会从集合或RPOPM "listName" 1000返回并删除1000个随机项,这将从列表中返回1000个最右边的项目?

我知道有像SRANDMEMBER和LRANGE这样的命令,但它们不会从数据结构中删除这些项目。它们可以单独删除。但是,如果有更多客户端从同一数据结构中读取,则可以多次读取一些项目,并且可以在不读取的情况下删除一些项目!因此,原子性是我的问题所在。

另外,如果这种操作的时间复杂度更高,我也没关系。我怀疑它会比发布N(比如上一个例子中的1000,N)单独请求Redis服务器更昂贵。

我也知道单独的交易支持。但是,Redis文档中的这句话不鼓励我将其用于修改集合的并行过程(破坏性地从中读取):
When using WATCH, EXEC will execute commands only if the watched keys were not modified, allowing for a check-and-set mechanism.

9 个答案:

答案 0 :(得分:17)

pipeline中将LRANGELTRIM一起使用。管道将作为一个原子事务运行。您对WATCHEXEC的担忧将不适用于此,因为您将LRANGELTRIM作为一项交易运行,而无法与任何其他客户进行任何其他交易介于他们之间。尝试一下。

答案 1 :(得分:9)

使用lrangeltrim内置而不是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" multi内容来扩展Eli的回复,并使用完整的列表集示例:

exec

如果你想使操作成为原子,你可以在ltrimlrange a 0 99命令中包装lrange和ltrim。

另外,如其他地方所述,您应该ltrim a 50 -1 返回的项目数而不是您要求的项目数。例如如果您执行了ltrim a 100 -1但获得了50项,则lpush不会rpush

要实现队列语义而不是堆栈,请将formatter.dateFormat = "MM/dd/yy hh:mm a" let end = formatter.date(from: dateString) 替换为nil

答案 2 :(得分:5)

从Redis 3.2开始,命令SPOP有一个[count]参数,用于从集合中检索多个元素。

请参阅http://redis.io/commands/spop#count-argument-extension

答案 3 :(得分:3)

如果你想要一个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

我不知道未来版本是否允许这样做,但我不认为。我认为复制会有问题。

答案 4 :(得分:1)

Redis 4.0+ now supports modules添加各种新功能和数据类型,比Lua脚本或multi / exec管道更快,更安全。

Redis Labs是Redis背后的现有赞助商,在这里有一组有用的扩展模块,名为 redex https://github.com/RedisLabsModules/redex

rxlists模块添加了多个列表操作,包括LMPOPRMPOP,因此您可以从Redis列表中自动弹出多个值。逻辑仍然是O(n)(基本上在循环中执行单个弹出)但您只需要安装模块一次并发送该自定义命令。我在列表中使用它,包含数百万个项目,数千个弹出,同时产生500MB +的网络流量而没有问题。

答案 5 :(得分:1)

Here is a python snippet that can achieve this using redis-py and pipeline:

from redis import StrictRedis

client = StrictRedis()

def get_messages(q_name, prefetch_count=100):
    pipe = client.pipeline()
    pipe.lrange(q_name, 0, prefetch_count - 1)  # Get msgs (w/o pop)
    pipe.ltrim(q_name, prefetch_count, -1)  # Trim (pop) list to new value
    messages, trim_success = pipe.execute()
    return messages

I was thinking that I could just do a a for loop of pop but that would not be efficient, even with pipeline especially if the list queue is smaller than prefetch_count. I have a full RedisQueue class implemented here if you want to look. Hope it helps!

答案 6 :(得分:0)

我认为你应该看看Redis中的LUA支持。如果您编写LUA脚本并在redis上执行它,则可以保证它是原子的(因为Redis是单线程的)。在LUA脚本结束之前不会执行任何查询(即:您无法在LUA中执行大任务或redis会变慢)。

因此,在此脚本中添加SPOP和RPOP,您可以将每个redis命令的结果附加到LUA数组中,然后将该数组返回到redis客户端。

文档中关于MULTI的说法是乐观锁定,这意味着它将使用WATCH重试多项操作,直到观察到的值未被修改。如果你对被监视的值有很多写入,它将比“悲观”锁定(如许多SQL数据库:POSTGRESQL,MYSQL ...)慢,以某种方式“停止世界”以便首先执行查询。悲观锁定不是在redis中实现的,但你可以根据需要实现它,但它很复杂,也许你不需要它(对这个值没有那么多写:乐观应该足够了)。

答案 7 :(得分:0)

你可能会尝试像这样的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

答案 8 :(得分:0)

从 Redis 6.2 开始,您可以使用 count 参数来确定您希望从列表中弹出多少个元素。 count 可用于 LPOPRPOP。这是实现计数功能的 pull request

redis> rpush foo a b c d e f g
(integer) 7
redis> lrange foo 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
7) "g"
redis> lpop foo
"a"
redis> lrange foo 0 -1
1) "b"
2) "c"
3) "d"
4) "e"
5) "f"
6) "g"
redis> lpop foo 3
1) "b"
2) "c"
3) "d"
redis> lrange foo 0 -1
1) "e"
2) "f"
3) "g"
redis> rpop foo 2
1) "g"
2) "f"
redis>