常用哈希命令
Redis
的哈希允许用户将多个键值存储到一个 Redis
键中,使得哈希十分适合将一些相关的数据存储在一起。我们可以把这种数据看作是关系型数据库中的行。
常用的哈希命令包括之前介绍过的添加和删除域-值对命令、获取所有域-值对命令以及对域-值对的值进行自增/自减操作的命令:
命令 | 用法 | 说明 |
---|---|---|
HMSET | HMSET key field value [field value ...] | 同时将多个 field-value (域-值)对设置到哈希表 key 中 |
HMGET | HMGET key field [field ...] | 返回哈希表 key 中,一或多个给定域的值 |
HDEL | HDEL key field [field ...] | 删除哈希表 key 中的一或多个指定域 |
HLEN | HLEN key | 返回哈希表 key 中域的数量 |
在上一个实训中,我们使用过 HMSET
命令来批量的存储域-值对信息,实际上 HMSET
和 HMGET
命令既可以通过批量处理给用户带来便利,又减少了命令的调用次数,提升了客户端与 Redis
之间的通信次数,提高了 Redis
的性能:
>>> conn.hmset('hash', {'a': '1', 'b': '2', 'c': '3'})
True
>>> conn.hmget('hash', ['a', 'b'])
['1', '2']
>>> conn.hdel('hash', 'b', 'c')
2
>>> conn.hlen('hash')
1
在使用 HMGET
命令时,我们可以使用类似于上面数组形式传入参数,也可以类似于 HDEL
命令的多参数形式传入参数。而之前介绍的 HGET
和 HSET
命令则分别是 HMGET
和 HMSET
命令的单参数版本,每次执行时只能处理一个键值对。
Redis
哈希还支持一些更高级的批量操作:
命令 | 用法 | 说明 |
---|---|---|
HEXISTS | HEXISTS key field | 查看哈希表 key 中,给定域 field 是否存在 |
HKEYS | HKEYS key | 返回哈希表 key 中所有域 |
HVALS | HVALS key | 返回哈希表 key 中所有域的值 |
HINCRBY | HINCRBY key field increment | 为哈希表 key 中的域 field 的值加上 increment |
在哈希包含的值的体积都十分大时,我们应该使用 HKEYS
命令获取所有的域,再使用 HGET
一个个的从哈希中取出域的值,从而避免 Redis
因为一次性获取多个大体积的值而导致服务器阻塞。甚至,我们可以只获取必要的值来减少传输的数据量。
常用有序集合命令
有序集合与哈希类似,也存储着成员(member
)和分值(score
)之间的映射关系。Redis
为有序集合提供了分值处理命令,并能根据分值大小有序的排列成员:
命令 | 用法 | 说明 |
---|---|---|
ZCARD | ZCARD key | 返回有序集合 key 的成员总数 |
ZCOUNT | ZCOUNT key min max | 返回有序集合 key 中, score 值在 min 和 max 之间的成员数量 |
ZRANK | ZRANK key member | 返回有序集合 key 中成员 member 的排名 |
ZSCORE | ZSCORE key member | 返回有序集合 key 中,成员 member 的分值 |
值得一提的是,之前提过的 ZADD
命令在 Redis
中的语法是:
- 先输入分值,后输入成员。
- 例如:
ZADD sorted-set 100 member
而在 Python
客户端中执行 ZADD
命令组需要:
- 先输入成员,后输入分值
- 例如:
conn.zadd('sorted-set', 'member', 100)
类似于集合,有序集合也有交集(ZINTERSTORE
)和并集(ZUNIONSTORE
)命令。我们通过一个示例来理解有序集合的交集和并集命令:
>>> conn.zadd('zset-1', 'a', 1, 'b', 2, 'c', 3)
>>> conn.zadd('zset-2', 'b', 4, 'c', 1, 'd', 0)
>>> conn.zinterstore('zset-i', ['zset-1', 'zset-2'])
2L
>>> conn.zrange('zset-i', 0, -1, withscores=True)
[('c', 4.0), ('b', 6.0)]
>>> conn.zunionstore('zset-u', ['zset-1', 'zset-2'], aggregate='min')
4L
>>> conn.zrange('zset-u', 0, -1, withscores=True)
[('d', 0.0), ('a', 1.0), ('c', 1.0), ('b', 2.0)]
>>> conn.sadd('set-1', 'a', 'd')
2
>>> conn.zunionstore('zset-u2', ['zset-1', 'zset-2', 'set-1'])
4L
>>> conn.zrange('zset-u2', 0, -1, withscores=True)
[('d', 1.0), ('a', 2.0), ('c', 4.0), ('b', 6.0)]
在执行交集和并集运算时,可以传入不同的聚合函数:
sum
,对相同成员的分值求和作为新分值。min
,取相同成员中最低的分值作为新分值。max
,取相同成员中最高的分值作为新分值。
在交集运算时,使用了默认的聚合函数sum
,所以其交集运算过程如下:
而并集运算则不同,只要某个成员存在于一个输入有序集合中,那么这个成员就会包括在输出有序集合中。在执行并集运算时,我们使用 aggregate
参数指定了聚合函数是 min
,所以其并集运算过程如下:
并集运算还可以在有序集合和集合之间进行,上面的示例中,我们将两个有序集合和一个集合组合成了一个新的有序集合:
在上述过程中,集合中的每个成员的分值都先被当作 0
,然后再进行并集运算。
如何实现带优先级的队列系统
上一关中,我们实现了任务分配的后端处理逻辑,在学习了哈希和有序集合的知识后,我们为每个任务带上优先级,使得高优先级的任务优先分配,更加符合实际情况。
首先我们使用哈希存储任务状态,方便我们后续查询任务状态。任务与任务状态构成域-值对,存放在 task_status
键中:
conn.hset("task_status", task_id, "init")
接下来我们要开始构建任务队列了,由于任务具有优先级,所以可以使用有序集合来存储队列信息,其成员是任务 ID
,分值是优先级。例如:任务 1
的优先级为 2
时:
conn.zadd('task_queue', '1', 2)
通过上述方法将任务放进任务队列,而在取任务时,则需要使用到有序集合的排序功能,找出优先级(分值)最高的成员:
task_list_by_priority = conn.zrevrange('task_queue', 0, -1)
current_task = task_list_by_priority[0]
conn.zrem('task_queue', current_task)
ZREVRANGE
命令有三个参数,依次为 key
,start
,stop
,其返回有序集合根据排名范围 start
到 stop
中的成员,并按分值从大到小排列。
所以我们可以使用这个命令获取到整个有序集合按照分值从大到小顺序排列的结果,从当中取出第一个成员,就是我们所需要的优先级(分值)最高的成员(current_task
)了。最后,别忘了将这个成员从有序集合中移除(使用ZREM
命令)。
因为我们使用了 task_status
哈希存储了任务状态,所以需要在任务从队列中取出,开始处理时更新这个状态:
conn.hset("task_status", current_task, "processing")
将上述步骤使用三个方法分别实现,代码如下:
# 初始化任务信息到 Redis 中
def set_task_info(task_id):
conn.hset("task_status", task_id, "init")
# 将任务添加至任务队列
def add_task_to_queue(task_id, priority):
conn.zadd("task_queue", task_id, int(priority))
set_task_info(task_id)
# 从任务队列中取出优先级最高的任务
def get_task():
task_list_by_priority = conn.zrevrange("task_queue", 0, -1)
current_task = task_list_by_priority[0]
conn.zrem('task_queue', current_task)
conn.hset("task_status", current_task, "processing")