赞
踩
Redis
命令十分丰富,包括键(Key
)、字符串(String
)、哈希(Hash
)、列表(List
)、集合(Set
)、有序集合(Sorted Set
)、发布与订阅(Pub/Sub
)等 14 个Redis
命令组,共两百多条Redis
命令。
任务描述
本关任务:使用
Redis
字符串、列表和集合的常用命令完成任务分配的后端处理逻辑。相关知识
为了完成本关任务,你需要掌握:
1
.常用字符串命令,2
.常用列表命令,3
.常用集合命令。常用字符串命令
Redis
的字符串可以存储三种类型的值:
- 整数
- 浮点数
- 字节串
取值范围说明
Redis 中整型数据的长度与系统字长一致(例如:32位系统,整型数据为
32
位有符号整数)Redis 中浮点数的取值范围与精度都与双精度浮点数(
double
)一致数值操作
所以针对存储整型和浮点型的字符串就有自增和自减操作。在需要的时候(例如下表的
INCRBYFLOAT
命令),Redis
还会将整数转换为浮点数。对
Redis
字符串执行自增和自减的命令列表如下:
命令 用法 说明 INCR
INCR key
将 key
存储的值加上1
DECR
DECR key
将 key
存储的值减去1
INCRBY
INCRBY key increment
将 key
存储的值加上increment
DECRBY
DECRBY key decrement
将 key
存储的值减去decrement
INCRBYFLOAT
INCRBYFLOAT key increment
将 key
存储的值加上浮点数increment
注意:
INCRBYFLOAT
只能在Redis
版本>= 2.6
时可用当用户将一个值存储到
Redis
字符串中,Redis
会检测这个值是否可以被解释(interpret
)为十进制整数或者浮点数。如果可以,则允许用户对该字符串进行自增和自减操作。在前面也提到过,如果用户对一个不存在的键或者一个保存了空串的键执行了自增或自减操作,
Redis
都会:
- 先将该键的值置为
0
- 再对该键的值进行自增或自减操作
需要额外提到的是,
Python
的Redis
库在incr(key, increment=1)
方法中同时实现了INCR
和INCRBY
命令,该方法的第二个参数increment
是可选的,如果用户没有设置该值,就会使用其默认值1
。例如:
>>> conn = redis.Redis() >>> conn.set('key', '1') True >>> conn.incr('key', 10) 11 >>> conn.decr('key', 5) 6 >>> conn.incr('key') 7字节串操作
Redis
还可以对字节串的一部分内容进行读取/写入:
命令 用法 说明 APPEND
APPEND key value
将 value
追加到key
键存储的值的末尾GETRANGE
GETRANGE key start end
获取 start
到end
间的子串SETRANGE
SETRANGE key offset value
从 start
偏移量开始,将与value
长度一致的子串设置为value
在使用
GETRANGE
读取字符串时,超出字符串末尾的数据会被视为空串;而在使用SETRANGE
对字符串进行写入时,如果字符串当前长度不能满足写入要求,Redis
则会自动使用空字节将字符串扩展至所需的长度,然后再执行写入/更新操作。值得一提的是,
Redis
现在的GETRANGE
命令式以前的SUBSTR
命令改名而来的,所以,Python
客户端仍然可以使用substr()
方法获取子串,例如:
>>> conn.set('string', 'hello') True >>> conn.append('string', ' educoder') 14L >>> conn.substr('string', 0, 4) 'hello' >>> conn.setrange('string', 0, 'ByeBye') 14 >>> conn.get('string') 'ByeByeeducoder' >>> conn.getrange('string', 6, -1) 'educoder'我们推荐使用
getrange()
方法来获取子串。在上述示例中,我们还将end
下标传入了-1
的值,这时Redis
将会从起始偏移量读取到该字符串的末尾。常用列表命令
Redis
提供了丰富的列表操作命令,从而使得列表的应用场景非常广泛,例如:存储任务队列,记录最近的操作/数据变化,作为日志收集器等。首先我们介绍一些常用的列表命令:
命令 用法 说明 LPUSH
LPUSH key value [value ...]
将一个或多个 value
推入到列表的左侧RPUSH
RPUSH key value [value ...]
将一个或多个 value
推入到列表的右侧LLEN
LLEN key
返回列表 key
的长度LREM
LREM key count value
根据参数 count
的值,移除列表中与参数value
相等的元素加上我们在上一个实训中已经介绍过的弹出、获取元素等命令,就构成了最为常用的列表命令。使用
Python
交互的示例如下:
>>> conn.lpush('list', 'a', 'b', 'c', 'd') 4L >>> conn.llen('list') 4 >>> conn.rpush('list', 'a', 'b', 'c', 'd') 8L >>> conn.lrange('list', 0, -1) ['d', 'c', 'b', 'a', 'a', 'b', 'c', 'd'] >>> conn.lrem('list', 'b', 2) >>> conn.lrange('list', 0, -1) ['d', 'c', 'a', 'a', 'c', 'd']我们发现
lrem()
方法与LREM
命令在参数的顺序上不完全一致,lrem()
方法将count
参数放至最后,在Python
的Redis
客户端中,大多数命令中的数值型参数都被放到了最后,如果弄不清某个方法的参数,你可以到 redis客户端主页 查看。我们还可以在两个列表之间移动元素:
RPOPLPUSH source destination
RPOPLPUSH
命令在一个原子时间内,执行以下两个动作:
- 将列表
source
中的最右侧元素弹出,并返回给客户端。- 将
source
弹出的元素推入到列表destination
的最左侧
>>> conn.lpush('list2', '1', '2', '3') >>> conn.rpoplpush('list', 'list2') 'd' >>> conn.lrange('list', 0, -1) ['d', 'c', 'a', 'a', 'c'] >>> conn.lrange('list2', 0, -1) ['d', '3', '2', '1']原子时间
不可再拆分的时间段
意指该操作执行时,不可被其他操作打断,也就是包含在一个原子时间内的若干操作要么都成功要么都失败
常用集合命令
与列表有序不同,
Redis
中的集合以无序的方式存储多个互不相同的元素,用户可以快速的添加、删除和查找元素。Redis 提供了针对单个集合以及多集合间处理的命令:
命令 用法 说明 SCARD
SCARD key
返回集合 key
中元素的数量SRANDMEMBER
SRANDMEMBER key [count]
返回集合中的 1
或count
个随机元素SPOP
SPOP key
移除并返回集合中的一个随机元素 SMOVE
SMOVE source destination member
将 member
元素从source
集合移动到destination
集合我们通过一些示例来展示上述命令的用法:
>>> conn.sadd('set', 'a', 'b', 'c', 'a') >>> conn.scard('set') 3 >>> conn.srandmember('set') 'a' >>> conn.spop('set') 'b' >>> conn.smembers('set') set(['a', 'c']) >>> conn.smove('set', 'set2', 'a') >>> conn.smembers('set2') set(['a'])
Redis
中的许多命令都有着实际的应用场景,例如SRANDMEMBER
命令从集合中随机选择一个元素并输出,在数据库层面就实现了随机数功能,避免用户将集合的全部成员取出后再随机选择,加快了效率,减少了开发人员的工作量。所以我们一直称Redis
是基于实用主义的。在
SMOVE
命令的示例中你也发现了,如果目的集合是不存在的,我们会先创建目的集合,再将成员从源集合中取出并放入目的集合。但如果指定的成员不存在于源集合中,则该命令不会继续执行。
Redis
集合还有更为强大的功能 —— 组合和关联多个集合:
命令 用法 说明 SDIFF
SDIFF key [key ...]
返回所有给定集合之间的差集 SINTER
SINTER key [key ...]
返回所有给定集合的交集 SUNION
SUNION key [key ...]
返回所有给定集合的并集 上述三个命令是差集,交集,并集运算的“返回结果”版本,同时
Redis
还提供了“存储结果”版本,你可以参考 Redis 命令参考 中的SDIFFSTORE
,SINTERSTORE
和SUNIONSTORE
命令。编程要求
根据提示,在右侧
Begin-End
区域补充代码,完成任务分配的后端处理逻辑:
- 在
task_empty()
方法中:
- 从
Redis
中获取列表task_list
的长度,判断是否为0
- 若为
0
,则返回True
- 若不为
0
,则返回False
- 在
get_task()
方法中:
- 从列表
task_list
的最右侧弹出一个元素,赋值给task
- 将
task
的值设置到Redis
的字符串键current_task
中- 在
get_unallocated_staff()
方法中:
- 从集合
unallocated_staff
中随机返回一个元素,赋值给staff
- 将上面的
staff
从集合unallocated_staff
移动到集合allocated_staff
中- 返回(
return
)staff
的值- 在
allocate_task(staff)
方法中:
- 将参数
staff
的值追加到Redis
字符串键current_task
的尾部,中间以:
间隔- 将追加后的字符串键
current_task
从左侧推入列表task_queue
- 将字符串键
current_task
的值设置为"None"
测试说明
我会对你编写的代码进行测试:
测试输入:
task_1 task_2 task_3 task_4 task_5 staff_1 staff_2 staff_3 staff_4 staff_5预期输出:
Init task list: ['task_1', 'task_2', 'task_3', 'task_4', 'task_5'] Init staff list: set(['staff_4', 'staff_5', 'staff_1', 'staff_2', 'staff_3']) Cur task list is empty: False Get new task: task_5 Current staff is allocated: True Current staff is unallocated: False Current task is: None Allocated all tasks Task queue length: 5 Task list is empty: True Allocated_staff: set(['staff_4', 'staff_5', 'staff_1', 'staff_2', 'staff_3']) Unallocated_staff: set([])
- #!/usr/bin/env python
- #-*- coding:utf-8 -*-
-
- import redis
-
- conn = redis.Redis()
-
- def task_empty():
- # 请在下面完成判断任务列表是否为空
- # ********* Begin *********#
- if conn.llen("task_list") > 0:
- return False
- else:
- return True
- # ********* End *********#
- def get_task():
- # 请在下面完成获取一个任务
- # ********* Begin *********#
- task = conn.rpop("task_list")
- conn.set("current_task", task)
- # ********* End *********#
-
- def get_unallocated_staff():
- # 请在下面完成获取一个未分配的员工
- #********* Begin *********#
- staff = conn.srandmember("unallocated_staff")
- conn.smove("unallocated_staff","allocated_staff", staff)
- return staff;
- #********* End *********#
-
- def allocate_task(staff):
- # 请在下面完成分配任务
- #********* Begin *********#
- conn.append("current_task", ":" + staff)
- conn.lpush("task_queue","current_task")
- conn.set("current_task", "None")
- #********* End *********#
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
任务描述
本关任务:编写带优先级的队列系统的后端处理逻辑。
相关知识
为了完成本关任务,你需要掌握:1.常用哈希命令,2.常用有序集合命令,3.如何实现带优先级的队列系统。
常用哈希命令
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
,取相同成员中最高的分值作为新分值。如何实现带优先级的队列系统
上一关中,我们实现了任务分配的后端处理逻辑,在学习了哈希和有序集合的知识后,我们为每个任务带上优先级,使得高优先级的任务优先分配,更加符合实际情况。
首先我们使用哈希存储任务状态,方便我们后续查询任务状态。任务与任务状态构成域-值对,存放在
task_status
键中:
conn.hset("task_status", task_id, "init")
接下来我们要开始构建任务队列了,由于任务具有优先级,所以可以使用有序集合来存储队列信息,其成员是任务
ID
,分值是优先级。例如:任务1
的优先级为2
时:conn.zadd('task_queue', '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")编程要求
根据提示,在右侧
Begin-End
区域补充代码,完成带优先级的队列系统的后端处理逻辑:
- 在
set_task_info(task_id)
方法中:
- 使用参数
task_id
作为域,初始状态"init"
作为值构成域-值对,存放在task_status
哈希键中。- 在
add_task_to_queue(task_id, priority)
方法中:
- 参数说明:
task_id
为任务ID
priority
为任务优先级。
- 将分值(优先级)为
priority
的成员task_id
存入有序集合task_queue
中。
- 注意将参数
priority
转换为整型- 调用
set_task_info()
方法,传入参数task_id
- 在
get_task()
方法中:
- 新建变量
task_list_by_priority
,值为:
- 使用
ZREVRANGE
命令按照分值(优先级)从大到小顺序返回有序集合task_queue
的全部成员。- 新建变量
current_task
,值为:
task_list_by_priority
中的第一个元素(下标为0
)- 将成员
current_task
从有序集合task_queue
中移除- 修改哈希
task_status
中的current_task
域的值为"processing"
- 返回(
return
)current_task
的值测试说明
我会对你编写的代码进行测试:
测试输入:
1 2 3 4 5 6 7 8 9 10 2 4 9 1 0 5 8 6 7 3预期输出:
Add new task: 1, priority: 2, status: init Add new task: 2, priority: 4, status: init Add new task: 3, priority: 9, status: init Add new task: 4, priority: 1, status: init Add new task: 5, priority: 0, status: init Add new task: 6, priority: 5, status: init Add new task: 7, priority: 8, status: init Add new task: 8, priority: 6, status: init Add new task: 9, priority: 7, status: init Add new task: 10, priority: 3, status: init Before: task list is: ['3', '7', '9', '8', '6', '2', '10', '1', '4', '5'] Get new task: 3 After: task list is: ['7', '9', '8', '6', '2', '10', '1', '4', '5'] Current task status: processing
- #!/usr/bin/env python
- #-*- coding:utf-8 -*-
-
- import redis
-
- conn = redis.Redis()
-
- # 初始化任务信息到 Redis 中
- def set_task_info(task_id):
- # 请在下面完成要求的功能
- #********* Begin *********#
- conn.hset("task_status", task_id, "init")
- #********* End *********#
-
- # 将任务添加至任务队列
- def add_task_to_queue(task_id, priority):
- # 请在下面完成要求的功能
- #********* Begin *********#
- conn.zadd("task_queue", task_id, int(priority))
- set_task_info(task_id)
- #********* End *********#
-
- # 从任务队列中取出优先级最高的任务
- def get_task():
- # 请在下面完成要求的功能
-
- #********* Begin *********#
- 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")
- return current_task
- #********* End *********#
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
任务描述
本关任务:编写一个网络约车的后端处理逻辑。
相关知识
为了完成本关任务,你需要掌握:
1
.Redis的基本事务,2
.排序(SORT
)命令,3
.Redis的键过期时间。Redis的基本事务
Redis
中的事务是一组命令的集合。事务和命令一样,都是Redis
的最小执行单位,一个事务中的命令要么都执行,要么都不执行。例如:在转账过程中,我们需要:
- 将钱从甲的账户中转出
- 将钱向乙的账户中转入
这两个操作要么都执行,要么都不执行,所以这两个操作就属于一个事务内。
Redis
的基本事务要用到MULTI
命令和EXEC
命令,我们需要先执行MULTI
命令,再输入我们要放在事务中的命令,最后再执行EXEC
命令。在事务执行完毕之后,Redis
才会开始处理其他客户端提交的命令。所以我们要是希望一组命令不被打断的依次执行时,也可以使用事务。当
Redis
接收到MULTI
命令时,会将之后接收到的所有命令都放入一个队列中,直到接收到EXEC
命令。然后Redis
再在不被打断的情况下,连续的执行队列中的命令。在
Python
中,Redis
事务是通过pipeline()
方法实现的,我们通过pipeline()
方法创建一个事务,再将所有需要执行的命令都放进这个事务中,最后通过execute()
方法执行这个事务。下面我们通过转账事务作为示例:
pipe = conn.pipeline() pipe.decr('a_account', 500) pipe.incr('b_account', 500) pipe.execute()
pipeline()
方法通过存储事务包含的若干命令,一次性提交所有命令减少了Redis
与客户端之间的通信次数,提升了事务命令执行的效率。值得一提的是,
Redis
的事务没有关系型数据库中事务提供的回滚(rollback
)功能,所以,如果假如事务在执行过程中出错了,你需要手动将数据库恢复到事务执行前的状态。不过,也正是因为不支持回滚功能,Redis
在事务的处理上才能一直保持简洁和快速。排序(
SORT
)命令
SORT
命令可以根据字符串、列表、集合、有序集合、哈希这5
种键中存储的数据,对列表、集合和有序集合进行排序。在某种程度上,你可以把SORT
命令看作是关系型数据库中的order by
子句。SORT
命令的语法如下:
/* * SORT 命令用于对查询结果进行排序,默认按升序排列。 * * 参数说明: * key: 指定要对其元素进行排序的键名,该键必须为列表、集合或有序集合类型。 * * BY pattern (可选): 指定一个模式,根据这个模式从元素中提取出一个子值来进行排序,而不是直接对整个元素进行排序。 * * LIMIT offset count (可选): - offset: 指定排序后跳过的记录数(索引从0开始)。 - count: 指定在排序结果中返回多少条记录。 * GET pattern [GET pattern ...] (可选): - 用于配合 BY 参数使用,指定从每个元素中获取哪些字段值用于排序。 - 如果没有 BY 参数,这些 GET 指令将被忽略。 * ASC | DESC (可选): - ASC 表示按升序排序,默认值。 - DESC 表示按降序排序。 * ALPHA (可选): - 当对字符串进行排序时,指定是否按照字典顺序(字母顺序)进行排序。 * STORE destination (可选): - 将排序后的结果存储到一个新的键 `destination` 中,不返回排序结果,而是返回存储结果的元素个数。 */ SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination]
SORT
命令是Redis
中功能最强大的命令之一,根据SORT
命令提供的选项,可以实现:
- 根据升序(默认)/降序进行排序
- 将元素解释为数值(默认)/二进制字符串进行排序
- 使用元素之外的其他值/指定的外部键值进行排序
例如:
>>> conn.rpush('sort-list', 23, 15, 110, 7) # 根据数值大小进行排序 >>> conn.sort('sort-list') ['7', '15', '23', '110'] # 根据字符顺序进行排序 >>> conn.sort('sort-list', alpha=True) ['110', '15', '23', '7'] >>> conn.hset('d-7', 'f', 2) >>> conn.hset('d-15', 'f', 1) >>> conn.hset('d-23', 'f', 3) >>> conn.hset('d-110', 'f', 4) # 将哈希的域作为权重,对 sort-list 列表进行排序 >>> conn.sort('sort-list', by='d-*->f') ['15', '7', '23', '110'] # 将哈希的值作为排序后的返回值 >>> conn.sort('sort-list', by='d-*->f', get='d-*->f') ['1', '2', '3', '4']上述示例中,有两个特殊的参数,
by
参数和get
参数,他们大大的增强了SORT
命令的功能。
BY
参数很多情况下,列表(或集合、有序集合)中存储的元素值大多是对象
ID
,单纯的对ID
进行排序没有过大的意义,更多的时候,我们是希望根据ID
对应的对象的某个属性来进行排序。例如:
- 任务队列
task_queue
中存储的是若干个任务ID
- 任务的详细信息通过哈希
task_*_info
存储
- 其中包括一个域为
time
- 存储的值为任务的创建时间
此时我们想根据任务的创建时间将任务队列中的所有任务进行排序,以便于调整任务的优先级,那么我们就可以通过
BY
参数来实现。
BY
参数又称为BY
参考键,其中参考键可以是字符串类型键或者是哈希类型键的某个域(写做:键名->域名)。如果提供了BY
参数,SORT
命令就不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个*
并获取其值,然后再依据这个值对元素排序。回到上面的例子,我们就可以这样实现:
>>> conn.lpush('task_queue', '3', '4', '2', '1') >>> conn.hmset('task_1_info', {'time': 1541158465.641236}) >>> conn.hmset('task_2_info', {'time': 1541158497.192748}) >>> conn.hmset('task_3_info', {'time': 1541158525.584697}) >>> conn.hmset('task_4_info', {'time': 1541158547.424744}) >>> conn.sort('task_queue', by='task_*_info->time') ['1', '2', '3', '4']
SORT
命令会读取task_1_info
,task_2_info
,task_3_info
,task_4_info
哈希键中的time
域的值,并根据这个值将task_queue
中的任务ID
排序。当然
BY
参数还可以使用字符串类型作为参考键,你可以参考 SORT 命令 —BY选项 。
GET
参数上面所说的
BY
参数让你能够使用外部的值辅助排序,而接下来要介绍的GET
参数则让你能够更方便的根据排序结果取出外部的值。
GET
参数和BY
参数的规则一致,也支持字符串类型和哈希类型的键,并使用*
作为占位符。例如我们要在带优先级的任务队列(有序集合task_queue
)排序后取出任务ID
对应的任务创建时间时,可以这样做:
>>> conn.zadd('task_queue', '1', 3, '2', 4, '3', 1, '4', 2) >>> conn.sort('task_queue', by='score', desc=True) ['2', '1', '4', '3'] >>> conn.sort('task_queue', by='score', desc=True, get='task_*_info->time') ['1541158497.192748', '1541158465.641236', '1541158547.424744', '1541158525.584697']这里我们还使用了
DESC
参数(desc=True
)来使用倒序排序。在一个
SORT
命令中可以有多个GET
参数(但注意,BY
参数只能有一个),你可以根据需求从不同的键中取出需要的值,以一次性取出所有需要的数据,降低客户端与Redis
间的通信次数。最后需要提醒你的是,
SORT
命令是Redis
中最强大最复杂的命令之一,如果你使用不当很容易成为性能瓶颈之一。所以,在你使用SORT
命令的时候,需要注意:
- 尽可能减少待排序元素的个数
- 使用
SORT
选项限制要获取的数据量- 使用
STORE
参数将结果存储关于
SORT
命令,我们还有很多没有说到的知识,如果你需要使用到更高级的排序功能,那么请参考 SORT 命令 吧!Redis的键过期时间
在使用
Redis
存储数据时,可能某些数据在一段时间后就不再有用了。这时我们可以通过DEL
命令显式地删除这些无用数据,也可以通过 Redis 的过期时间让一个键在指定的时间后自动被删除。在
Redis
中可以使用EXPIRE
命令设置一个键的生存时间,到时间后Redis
则会自动删除该键,该命令的语法为:
EXPIRE key seconds
其中
seconds
表示键的生存时间,单位是秒。假如我们想让task_1_info
键在一天之后被删除,可以这样做:
>>> conn.expire('task_1_info', 24 * 60 * 60) True当返回值:
- 为
True
时表示设置成功- 为
False
时表示键不存在或设置失败如果你想知道一个键还有多久过期,则可以使用
TTL
命令查看键的剩余时间(单位:秒):
>>> conn.ttl('task_1_info') 86257L >>> conn.ttl('task_2_info') >>>当一个键不存在或没有为该键设置过期时间时,
TTL
命令的返回值都是-1
,但Python
客户端对这个返回值做了一些处理,使它变成了None
。需要注意的是,
EXPIRE
命令和TTL
命令的单位都是秒,如果需要更加精确的控制键的生存时间,则应该使用PEXPIRE
命令,该命令可以将生存时间精确到毫秒级,与之对应的也有PTTL
命令来查看键的剩余时间(单位:毫秒)。编程要求
根据提示,在右侧
Begin-End
区域补充代码,完成网络约车的后端处理逻辑:
- 在
request_cab(user_id, priority)
方法中:
- 判断是否存在哈希键
request:info:用户ID
的time
域:
- 提示:可使用
HEXISTS
命令- 若存在,则直接
return
- 若不存在,做如下操作
- 使用事务提交下列命令:
- 将参数
user_id
从最左侧推入列表cab:queue
- 使用
HMSET
命令设置哈希键request:info:用户ID
:
- 域
time
,值为time.time()
- 域
priority
,值为参数priority
- 将上述哈希键的过期时间设置为
10分钟
- 在
allocate()
方法中:
- 使用
SORT
命令对列表cab:queue
排序,并将结果赋值给cab_queue
:
- 使用
BY
参数- 参考键为哈希键
request:info:*
,其中*
为占位符- 使用上述参考键中的
priority
域- 使用
DESC
参数做倒序排序
- 取出
cab_queue
的第一个元素(下标为0
)赋值给current_respond
- 从列表
cab:queue
中移除变量current_respond
中包含的元素- 返回(
return
)current_respond
测试说明
我会对你编写的代码进行测试:
测试输入:
1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1预期输出:
Receive new request: 1, priority: 9, is_expired? True Receive new request: 2, priority: 8, is_expired? True Receive new request: 3, priority: 7, is_expired? True Receive new request: 4, priority: 6, is_expired? True Receive new request: 5, priority: 5, is_expired? True Receive new request: 6, priority: 4, is_expired? True Receive new request: 7, priority: 3, is_expired? True Receive new request: 8, priority: 2, is_expired? True Receive new request: 9, priority: 1, is_expired? True Before: request queue: ['1', '2', '3', '4', '5', '6', '7', '8', '9'] Allocate new request: 1 After: request queue: ['2', '3', '4', '5', '6', '7', '8', '9'] Repeat request in few seconds: Before: request queue length: 8 After: request queue length: 8
- #!/usr/bin/env python
- #-*- coding:utf-8 -*-
-
- import time
- import redis
-
- conn = redis.Redis()
-
- # 用户端发起派车请求
- def request_cab(user_id, priority):
- # 请在下面完成要求的功能
- #********* Begin *********#
- if conn.hexists('request:info:' + str(user_id), 'time'):
- return
- # 开始事务
- pipe = conn.pipeline()
-
- pipe.lpush('cab:queue', user_id)
-
- pipe.execute()
- # 使用 HMSET 命令设置哈希键 request:info:用户ID
- conn.hmset('request:info:' + str(user_id), {'time': time.time(), 'priority': priority})
-
- conn.expire('request:info:' + str(user_id), 600)
-
-
- #********* End *********#
-
- # 平台选择优先级最高的派车请求并派车
- def allocate():
- # 请在下面完成要求的功能
- #********* Begin *********#
-
- # 使用 SORT 命令对列表 cab:queue 排序,并将结果赋值给 cab_queue:
- cab_queue = conn.sort('cab:queue', by='request:info:*->priority', desc=True)
-
- # 取出 cab_queue 的第一个元素(下标为 0)赋值给 current_respond
- current_respond = cab_queue[0] if cab_queue else None
-
- # 从列表 cab:queue 中移除变量 current_respond 中包含的元素
- if current_respond:
- conn.lrem('cab:queue', current_respond,1)
-
- # 返回(return)current_respond
- return current_respond
- #********* End *********#
-
- # 用户端取消派车请求
- def cancel_cab(user_id):
- # 请在下面完成要求的功能
- #********* Begin *********#
- conn.expire('request:info:' + str(user_id), 0)
- conn.lrem('cab:queue', user_id)
- #********* End *********#
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。