• 沒有找到結果。

keyN

client2

client7

client3

client1 NULL client5

NULL

client4 client6 NULL

3.4.7 阻塞因超过最大等待时间而被取消

前面提到过,当客户端被阻塞时,所有造成它阻塞的键,以及阻塞的最长时限会被记录在客户 端里面,并且该客户端的状态会被设置为“正在阻塞” 。

每次 Redis 服务器常规操作函数(server cron job)执行时,程序都会检查所有连接到服务器 的客户端,查看那些处于“正在阻塞”状态的客户端的最大阻塞时限是否已经过期,如果是的话,

就给客户端返回一个空白回复,然后撤销对客户端的阻塞。

可以用一段伪代码来描述这个过程:

def server_cron_job():

# 其他操作 ...

# 遍历所有已连接客户端

for client in server.all_connected_client:

# 如果客户端状态为“正在阻塞”,并且最大阻塞时限已到达 if client.state == BLOCKING and \

client.max_blocking_timestamp < current_timestamp():

# 那么给客户端发送空回复, 脱离阻塞状态 client.send_empty_reply()

# 并清除客户端在服务器上的阻塞信息

server.blocking_keys.remove_blocking_info(client)

# 其他操作 ...

3.5 集合

REDIS_SET (集 合) 是 SADDSRANDMEMBER 等 命 令 的 操 作 对 象, 它 使 用 REDIS_ENCODING_INTSET 和 REDIS_ENCODING_HT 两种方式编码:

集合 REDIS_SET

intset

REDIS_ENCODING_INTSET

字典

REDIS_ENCODING_HT

intset.h/intset dict.h/dict

3.5.1 编码的选择

第一个添加到集合的元素,决定了创建集合时所使用的编码:

• 如果第一个元素可以表示为 long long 类型值(也即是,它是一个整数),那么集合的初 始编码为 REDIS_ENCODING_INTSET 。

• 否则,集合的初始编码为 REDIS_ENCODING_HT 。

3.5.2 编码的切换

如果一个集合使用 REDIS_ENCODING_INTSET 编码,那么当以下任何一个条件被满足时,这个 集合会被转换成 REDIS_ENCODING_HT 编码:

• intset 保存的整数值个数超过 server.set_max_intset_entries (默认值为 512 )。

• 试图往集合里添加一个新元素,并且这个元素不能被表示为 long long 类型(也即是,

它不是一个整数)。

3.5.3 字典编码的集合

当使用 REDIS_ENCODING_HT 编码时,集合将元素保存到字典的键里面,而字典的值则统一设 为 NULL 。

作为例子,以下图片展示了一个以 REDIS_ENCODING_HT 编码表示的集合,集合的成员为 elem1

、elem2 和 elem3 :

74 Chapter 3. Redis 数据类型

Redis 设计与实现, 第一版 SINTER 、SUNION 等命令之下的算法实现,以下三个小节就分别讨论它们所使用的算法。

3.5.5 求交集算法

SINTERSINTERSTORE 两个命令所使用的求并交集算法可以用 Python 表示如下:

# coding: utf-8

def sinter(*multi_set):

# 根据集合的基数进行排序

sorted_multi_set = sorted(multi_set, lambda x, y: len(x) - len(y))

# 使用基数最小的集合作为基础结果集,有助于降低常数项 result = sorted_multi_set[0].copy()

# 剔除所有在 sorted_multi_set[0] 中存在

# 但在其他某个集合中不存在的元素 for elem in sorted_multi_set[0]:

if (not elem in s):

result.remove(elem) break

return result

算法的复杂度为 O(N2) ,执行步数为 S∗ T ,其中 S 为输入集合中基数最小的集合,而 T 则 为输入集合的数量。

3.5.6 求并集算法

SUNIONSUNIONSTORE 两个命令所使用的求并集算法可以用 Python 表示如下:

# coding: utf-8

def sunion(*multi_set):

result = set() for s in multi_set:

for elem in s:

# 重复的元素会被自动忽略 result.add(elem)

return result 算法的复杂度为 O(N ) 。

3.5.7 求差集算法

Redis 为 SDIFFSDIFFSTORE 两个命令准备了两种求集合差的算法。

以 Python 代码表示的算法一定义如下:

# coding: utf-8

def sdiff_1(*multi_set):

result = multi_set[0].copy()

sorted_multi_set = sorted(multi_set[1:], lambda x, y: len(x) - len(y))

# 当 elem 存在于除 multi_set[0] 之外的集合时

# 将 elem 从 result 中删除 for elem in multi_set[0]:

for s in sorted_multi_set:

if elem in s:

result.remove(elem) break

return result

这个算法的复杂度为 O(N2) ,执行步数为 S∗ T ,其中 S 为输入集合中基数最小的集合,而 T 则为除第一个集合之外,其他集合的数量。

76 Chapter 3. Redis 数据类型

Redis 设计与实现, 第一版

以 Python 代码表示的算法二定于如下:

# coding: utf-8

def sdiff_2(*multi_set):

# 用第一个集合作为结果集的起始值 result = multi_set[0].copy() for s in multi_set[1:]:

for elem in s:

# 从结果集中删去其他集合中包含的元素 if elem in result:

result.remove(elem) return result

这个算法的复杂度同样为 O(N2) ,执行步数为 S ,其中 S 为所有集合的基数总和。

Redis 使用一个程序决定该使用那个求差集算法,程序用 Python 表示如下:

# coding: utf-8

from sdiff_1 import sdiff_1 from sdiff_2 import sdiff_2 def sdiff(*multi_set):

# 算法一的常数项较低,给它一点额外的优先级 algo_one_advantage = 2

algo_one_weight = len(multi_set[0]) * len(multi_set[1:]) / algo_one_advantage algo_two_weight = sum(map(len, multi_set))

if algo_one_weight <= algo_two_weight:

return sdiff_1(*multi_set) else:

return sdiff_2(*multi_set)