BLPOP key [key ...] timeout
自2.0.0版本可用 时间复杂度:O(1)
BLPOP是阻塞列表的弹出原语。它是命令LPOP的阻塞版本,这是因为当给定列表内没有任何元素可供弹出的时候,连接将被BLPOP命令阻塞。当给定多个key参数时,按参数key的先后顺序依次检查各个列表,弹出第一个非空列表的头元素
非阻塞行为
当BLPOP被调用时,如果给定key内至少有一个非空列表,那么弹出遇到的第一个非空列表的头元素,并和被弹出元素所属的列表的名字key一起,组成结果返回给调用者。
当存在多个给定key时,BLPOP按给定key参数排列的先后顺序,依次检查各个列表。我们假设key list1 不存在, 而list2 和 list3都是非空列表。考虑以下的命令:
BLPOP list1 list2 list3 0
BLPOP保证返回一个存在于list2里的元素(因为它是从list1->list2->list3这个顺序查起的第一个非空列表)。
阻塞行为
如果所有给定key都不存在或包含空列表,那么BLPOP命令将阻塞连接,直到有另一个客户端对给定的这些key的任意一个执行LPUSH或RPUSH命令为止。 一旦有新的数据出现在其中一个列表里,那么这个命令会解除阻塞状态,并且返回key和弹出的元素值。
当BLPOP命令引起客户端阻塞并且设置了一个非零的超时参数timeout的时候,若经过了指定的timeout仍没有出现一个针对某一特定key的push操作,则客户端会解除阻塞状态并且返回一个nil的多组合值(multi-bulk value)。
timeout参数表示的是一个指定阻塞的最大秒数的整形值。当timeout为0是表示阻塞时间无限制。
什么key会被第一个处理?什么是客户端?什么是元素?优先级顺序详情
- 如果客户端尝试阻塞多个keys,但是至少一个key包含元素,会以key/element对的形式返回从左至右第一个包含一个或多个元素。在这种情况下客户端不会被阻塞。例如BLPOP key1 key2 key3 key4 0,假设key2 和 key4都不是空的,将总是返回key2的一个元素
- 如果多个客户端同时被一个key阻塞,第一个被处理的客户端是等待时间最长的(第一个被此key阻塞的)。一但客户端解除阻塞他不在包含任何优先级,当它因为下一个BLPOP命令面再次被阻塞的时候,会在处理完那些被同个key阻塞的客户端才处理它(即从第一个被阻塞的处理到最后一个被阻塞的)。
- 当一个客户端被多个key所阻塞时,多个key的元素同时可用(因为一个事务或一个Lua脚本为多个列表添加元素),客户端将会解除阻塞,并使用第一个接收到push操作的key(假设它拥有足够的元素为我们的客户端服务,因为有可能存在其他客户端同样是被这个key阻塞着)。从根本上来说,在执行完每个命令之后,Redis会把一个所有key都获得数据并且至少使一个客户端阻塞了的list运行一次。这个list按照新数据的接收时间进行整理,即是从第一个接收数据的key到最后一个。在处理每个key的时候,保要这个key里有元素,Redis就会对所有等待这个key的客户端按照“先进先出(FIFO)"的顺序进行服务。若这个key是空的,或都没有客户端在等待这个key,那么将会去处理下一个从之前的命令或事务或脚本中获得新数据的key,如此等等
当多个元素被push进入一个list时BLPOP的行为
有时候一个list会在同一概念的命令的情况下接收到多个元素:
- 像LPUSH mylist a b c 这样的可变push操作。
- 在对一个向同一个list进行多次push操作的MULTI块执行完EXEC语句后。
- 使用Redis2.6或者更新的版本执行一个Lua脚本
当多个元素被push进入一个被客户端阻塞着的list的时候,Redis2.4和Redis2.6或者更新的版本所采取行为是不一样的。
对于Redis2.6来说,所采取的行为是先执行多个push命令,然后在执行了这个命令之后再去服务被阻塞的客户端。看看下面命令顺序。
Client A: BLPOP foo 0
Client B: LPUSH foo a b c
如果上面的情况是发生在Redis2.6或更高版本的服务器,客户端A会接收到c元素,因为在LPUSH命令执行后,list包含了c, b ,a 这三个元素,所以从左边取一个元素就会返回c。
相反,Redis2.4是以不同的方式工作的:客户端会在push操作的上下文中被服务,所以当LPUSH foo a b c 开始往list中push第一个元素,它就被传送客户端A,也就是客户端A会接收到a(第一个被push的元素)。
Redis2.4的这种行为会在复制或者持续把数据存入AOF文件的时候引发很多问题,所以为了防止这些问题,很多更一般性、并且在主义上更简单的行为被引入到Redis2.6中。
需要注意的是,一个Lua脚本或都一个MULTI/EXEC块可能会push一堆元素进入一个list后,再删除这个list。在这种情况下,被阻塞的客户端完全不会被服务,并且只要在执行某个单一命令、事务或者脚本后list中没有出现元素,它就会被继续阻塞下去。
在一个MULTI/EXEC事务中的BLPOP
BLPOP可以用于流水线(pipeline,发送多个命令并且批量读取回复),特别是当它是流水线里的最后一个命令的时候,这种设定更加有意义。
在一个MULTI/EXEC块里同使用BLPOP并没有很大意义,因为它要求整个服务器被阻塞以保证块执行时的原子性,这就阻止了其他客户端执行一个push操作。因为,一个在MULTI/EXEC里面的BLPOP命令支在list为空的时候返回一个nil值,这跟超时(timeout)的时候发生的一样。 如果你喜欢科幻小说,那么想象一下时间是以无限的速度在MULTI/EXEC块中流逝......
返回值
多批量回复(multi-bulk-reply):具体来说:
- 当没有元素的时候会弹出一个nil的多批量值,并且timeout过期。
- 当有元素弹出时会返回一个双元素的多指值,其中第一个元素是弹出元素的key,第二个元素是value。
例子
redis>DEL list1 list2
(integer) 0
redis>RPUSH list1 a b c
(integer) 3
redis> BLPOP list1 list2 0
1) "list1"
2) "a"
可靠的队列
当BLPOP返回一个元素给客户端的时候,它也从list中把该元素移除。这意味着该元素就只存在于客户端的上下文中:如果客户端在处理这个返回元素的过程崩溃了,那么这个元素就永远丢失了。
在一些我们希望是更可靠的消息传递系统中的应用上,这可能会导致一些问题。在这种时候,请查看BRPOPLPUSH命令,这是BLPOP的一个变形,它会在把返回元素传给客户端之前先把该元素加入到一个目标list中。
模式:事件提醒 用来阻塞list的操作有可能是不同的阻塞原语。比如在某些应用里,你也许会为了等待新元素进入Redis Set而阻塞队列,直到有个新元素加入到Set中,这样就可以在不轮询的情况下获得元素。这就要求有一个SPOP的阻塞版本,而这事实上并不可用。但是我们可通过阻塞list操作轻易完成这个任务。
消费者会做的: LOOP forever WHILE SPOP(KEY) returns elements ... process elements ... END BRPOP helper_key END
而在生产者这角度我们可以这样简单地使用: MULTI SADD key element LPUSH help_key x EXEC