技术分享 Redis 阻塞、安全队列 BLPOP/BRPOPLPUSH

swede · 2019-10-22 10:42:47 · 热度: 193

队列优先级怎么做?

  • 方法1. 分N个redis list key,一个高级,一个普通级别
  • 方法2. 如果队列是 rpush,高级的则插队用 lpush(缺点是高级的顺序是反的)
  • 方法3. 用 blpop 的轮训弹出,例如:brpop([‘high_task_queue’, ‘low_task_queue’], 0)
  • 方法4. 有序集合(非阻塞)
  • 方法5. 二分查找在 list 中间找个位置插入

阻塞的优点?

节约性能,cli可以休息,不用一直连着,可以处于 WAIT 状态 非阻塞的意思是如果当前队列为空,要求调用方不断轮循(polling+sleep),这对使用者来说是非常不方便的。

==================================================

BLPOP BLPOP key [key …] timeout BLPOP是列表的阻塞式(blocking)弹出原语。

它是LPOP命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被BLPOP命令阻塞,直到等待超时或发现可弹出元素为止。

当给定多个key参数时,按参数key的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。

非阻塞行为

当BLPOP被调用时,如果给定key内至少有一个非空列表,那么弹出遇到的第一个非空列表的头元素,并和被弹出元素所属的列表的名字一起,组成结果返回给调用者。

当存在多个给定key时,BLPOP按给定key参数排列的先后顺序,依次检查各个列表。

假设现在有job、 command和request三个列表,其中job不存在,command和request都持有非空列表。考虑以下命令:

BLPOP job command request 0

BLPOP保证返回的元素来自command,因为它是按”查找job -> 查找command -> 查找request“这样的顺序,第一个找到的非空列表。

redis> DEL job command request  # 确保key都被删除
(integer) 0
redis> LPUSH command "update system..."  # 为command列表增加一个值
(integer) 1
redis> LPUSH request "visit page"  # 为request列表增加一个值
(integer) 1

redis> BLPOP job command request 0  # job列表为空,被跳过,紧接着command列表的第一个元素被弹出。
1) "command"    # 弹出元素所属的列表
2) "update system..."   # 弹出元素所属的值

阻塞行为

如果所有给定key都不存在或包含空列表,那么BLPOP命令将阻塞连接,直到等待超时,或有另一个客户端对给定key的任意一个执行LPUSH或RPUSH命令为止。

超时参数timeout接受一个以秒为单位的数字作为值。超时参数设为0表示阻塞时间可以无限期延长(block indefinitely) 。

redis> EXISTS job  # 确保两个key都不存在
(integer) 0
redis> EXISTS command
(integer) 0

redis> BLPOP job command 300  #因为key一开始不存在,所以操作会被阻塞,直到另一客户端对job或者command列表进行PUSH操作。
1) "job"  # 这里被push的是job
2) "do my home work"  # 被弹出的值
(26.26s)  # 等待的秒数

redis> BLPOP job command 5  # 等待超时的情况
(nil)
(5.66s) # 等待的秒数

相同的key被多个客户端同时阻塞

相同的key可以被多个客户端同时阻塞。 不同的客户端被放进一个队列中,按”先阻塞先服务”(first-BLPOP,first-served)的顺序为key执行BLPOP命令。 在MULTI/EXEC事务中的BLPOP

BLPOP可以用于流水线(pipline,批量地发送多个命令并读入多个回复),但把它用在MULTI/EXEC块当中没有意义。因为这要求整个服务器被阻塞以保证块执行时的原子性,该行为阻止了其他客户端执行LPUSH或RPUSH命令。

因此,一个被包裹在MULTI/EXEC块内的BLPOP命令,行为表现得就像LPOP一样,对空列表返回nil,对非空列表弹出列表元素,不进行任何阻塞操作。

情况1:对非空列表进行操作

redis> RPUSH job programming
(integer) 1

redis> MULTI
OK

redis> BLPOP job 30
QUEUED

redis> EXEC  # 不阻塞,立即返回
1) 1) "job"
   2) "programming"

情况2:对空列表进行操作

redis> LLEN job  # 空列表
(integer) 0

redis> MULTI
OK

redis> BLPOP job 30
QUEUED

redis> EXEC  # 不阻塞,立即返回
1) (nil)

时间复杂度:O(1) 返回值:如果列表为空,返回一个nil。 反之,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的key,第二个元素是被弹出元素的值。

用 RPOPLPUSH 实现安全的队列

Redis的列表经常被用作队列(queue),用于在不同程序之间有序地交换消息(message)。一个客户端通过 LPUSH 命令将消息放入队列中,而另一个客户端通过 RPOP 或者 BRPOP 命令取出队列中等待时间最长的消息。

不幸的是,上面的队列方法是『不安全』的,因为在这个过程中,一个客户端可能在取出一个消息之后崩溃,而未处理完的消息也就因此丢失。

使用 RPOPLPUSH 命令(或者它的阻塞版本 BRPOPLPUSH )可以解决这个问题:因为它不仅返回一个消息,同时还将这个消息添加到另一个备份列表当中,如果一切正常的话,当一个客户端完成某个消息的处理之后,可以用 LREM 命令将这个消息从备份表删除。

最后,还可以添加一个客户端专门用于监视备份表,它自动地将超过一定处理时限的消息重新放入队列中去(负责处理该消息的客户端可能已经崩溃),这样就不会丢失任何消息了。

猜你喜欢:
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册