是否有一个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.
答案 0 :(得分:17)
在pipeline中将LRANGE
与LTRIM
一起使用。管道将作为一个原子事务运行。您对WATCH
,EXEC
的担忧将不适用于此,因为您将LRANGE
和LTRIM
作为一项交易运行,而无法与任何其他客户进行任何其他交易介于他们之间。尝试一下。
答案 1 :(得分:9)
使用lrange
和ltrim
内置而不是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
如果你想使操作成为原子,你可以在ltrim
和lrange 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]
参数,用于从集合中检索多个元素。
答案 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
管道更快,更安全。
rxlists
模块添加了多个列表操作,包括LMPOP
和RMPOP
,因此您可以从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
可用于 LPOP 和 RPOP。这是实现计数功能的 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>