• 沒有找到結果。

阻塞因 LPUSH 、RPUSH 、LINSERT 等添加命令而被取消

3.4 列表

3.4.5 阻塞因 LPUSH 、RPUSH 、LINSERT 等添加命令而被取消

keyN

client2

client7

client3

client1 NULL client5

NULL

client4 client6 NULL

在上图展示的 blocking_keys 例子中,client2 、client5 和 client1 三个客户端就正被 key1 阻塞,而其他几个客户端也正在被别的两个 key 阻塞。

当客户端被阻塞之后,脱离阻塞状态有以下三种方法:

1. 被动脱离:有其他客户端为造成阻塞的键推入了新元素。

2. 主动脱离:到达执行阻塞原语时设定的最大阻塞时间。

3. 强制脱离:客户端强制终止和服务器的连接,或者服务器停机。

以下内容将分别介绍被动脱离和主动脱离的实现方式。

3.4.5 阻塞因 LPUSH 、RPUSH 、LINSERT 等添加命令而被取消

通过将新元素推入造成客户端阻塞的某个键中,可以让相应的客户端从阻塞状态中脱离出来

(取消阻塞的客户端数量取决于推入元素的数量)。

LPUSHRPUSHLINSERT 这 三 个 添 加 新 元 素 到 列 表 的 命 令, 在 底 层 都 由 一 个 pushGenericCommand 的函数实现,这个函数的运作流程如下图:

pushGenericCommand

key 非空且不是列表?

返回类型错误 是

key 为空?

如果 key 存在于 server.db[i]->blocking_keys 那么为 key 创建一个 readyList 结构 并将它添加到 server.ready_keys 链表中

将输入值推入列表 否

当向一个空键推入新元素时,pushGenericCommand 函数执行以下两件事:

1. 检查这个键是否存在于前面提到的 server.db[i]->blocking_keys 字典里,如果是的 话,那么说明有至少一个客户端因为这个 key 而被阻塞,程序会为这个键创建一个 redis.h/readyList 结构,并将它添加到 server.ready_keys 链表中。

2. 将给定的值添加到列表键中。

readyList 结构的定义如下:

typedef struct readyList { redisDb *db;

robj *key;

} readyList;

readyList 结构的 key 属性指向造成阻塞的键,而 db 则指向该键所在的数据库。

70 Chapter 3. Redis 数据类型

Redis 设计与实现, 第一版

举个例子,假设某个非阻塞客户端正在使用 0 号数据库,而这个数据库当前的 blocking_keys 属性的值如下:

client1 NULL client5

NULL

client4 client6 NULL

如果这时客户端对该数据库执行 PUSH key3 value ,那么 pushGenericCommand 将创建一个 db 属性指向 0 号数据库、key 属性指向 key3 键对象的 readyList 结构,并将它添加到服务器 server.ready_keys 属性的链表中:

redisServer ...

ready_keys ...

listNode prev next value list (key space of DB)

...

key3 ...

NULL

在我们这个例子中,到目前为止,pushGenericCommand 函数完成了以下两件事:

1. 将 readyList 添加到服务器。

2. 将新元素 value 添加到键 key3 。

虽然 key3 已经不再是空键,但到目前为止,被 key3 阻塞的客户端还没有任何一个被解除阻塞 状态。

为了做到这一点, Redis 的主进程在执行完 pushGenericCommand 函数之后,会继续调用 handleClientsBlockedOnLists 函数,这个函数执行以下操作:

1. 如 果 server.ready_keys 不 为 空, 那 么 弹 出 该 链 表 的 表 头 元 素, 并 取 出 元 素 中 的 readyList 值。

2. 根据 readyList 值所保存的 key 和 db ,在 server.blocking_keys 中查找所有因为 key 而被阻塞的客户端(以链表的形式保存)。

3. 如果 key 不为空,那么从 key 中弹出一个元素,并弹出客户端链表的第一个客户端,然 后将被弹出元素返回给被弹出客户端作为阻塞原语的返回值。

5. 继续执行步骤 3 和 4 ,直到 key 没有元素可弹出,或者所有因为 key 而阻塞的客户端都 取消阻塞为止。

6. 继续执行步骤 1 ,直到 ready_keys 链表里的所有 readyList 结构都被处理完为止。

用一段伪代码描述以上操作可能会更直观一些:

def handleClientsBlockedOnLists():

# 执行直到 ready_keys 为空

while server.ready_keys != NULL:

# 弹出链表中的第一个 readyList

rl = server.ready_keys.pop_first_node()

# 遍历所有因为这个键而被阻塞的客户端

for client in all_client_blocking_by_key(rl.key, rl.db):

# 只要还有客户端被这个键阻塞,就一直从键中弹出元素

# 如果被阻塞客户端执行的是 BLPOP ,那么对键执行 LPOP

# 如果执行的是 BRPOP ,那么对键执行 RPOP element = rl.key.pop_element()

if element == NULL:

# 键为空,跳出 for 循环

# 余下的未解除阻塞的客户端只能等待下次新元素的进入了 break

else:

# 清除客户端的阻塞信息

server.blocking_keys.remove_blocking_info(client)

# 将元素返回给客户端,脱离阻塞状态 client.reply_list_item(element)