上一篇文章: Python--Redis实战:第三章:Redis命令:第六节:发布与订阅
下一篇文章: Python--Redis实战:第四章:数据安全与性能保障:第1节:持久化选项
到目前为止,本章介绍了Redis提供的5种结构以及Redis的发布与订阅模式。本节将要介绍的命令则可以用于处理多种类型的数据:首先要介绍的是可以同时处理字符串、集合、列表和散列的sort命令;之后要介绍的是用于实现基本事务特性的multi命令和exec命令,这两个命令可以让用户将多个命令当做一个命令来执行;最后要介绍的是几个不同的自动过期命令,亚麻可以自动删除无用的数据。
阅读本节有助于读者更好的理解如何同时组合和操作多种数据类型。
排序
Redis的排序操作和其他编程语言的排序操作一样,都可以根据某种比较规则对一系列元素进行有序排列。负责执行排序操作的sort命令可以根据字符串、列表、集合、有序集合、散列这5种键Kim存储的数据,对列表、集合以及有序集合进行排序。如果读者之前曾经使用过关系数据库的话,那么可以将soft命令看做是sql语言里的order by。
下表展示了sort命令的定义:
命令 | 用例 | 用例描述 |
---|---|---|
soft | soft source-key [by pattern] [limit offset count] [get pattern get pattern ...]] [asc/desc] [alph] [store dest-key] | 根据给定的选项,对输入列表、集合或者有序集合进行排序,然后返回或者存储排序的结果。 |
使用sort命令提供的选项可以实现以下功能:
- 根据降序而不是默认的升序来排列元素;
- 将元素看作是数字来进行排序,或者将元素看作是二进制字符串来进行排序(比如排序字符串'110'和'12'的排序结果就跟排序数字110和12的结果不一样);
- 使用被排序元素之外的其他值作为权重进行排序,甚至还可以从输入的列表、集合、有序集合以外的其他地方进行取值。
实例
import redis # 导入redis包包
# 与本地redis进行链接,地址为:localhost,端口号为6379
r = redis.StrictRedis(host='localhost', port=6379)
r.delete('sort-input')
#首先将一些元素添加到列表里面
print(r.rpush('sort-input',23,15,110,7))
#根据数字大小对元素进行排序
print(r.sort('sort-input'))
#根据字母顺序对元素进行排序
print(r.sort('sort-input',alpha=True))
#添加一些用于执行排序操作和获取操作的附加数据
print(r.hset('d-7','field',5))
print(r.hset('d-15','field',1))
print(r.hset('d-23','field',9))
print(r.hset('d-110','field',3))
#将散列的域(field)用作权重,对sort-input列表进行排序
print(r.sort('sort-input',by='d-*->field'))
#获取外部数据,并将它们用作命令的返回值,而不是返回被排序的数据
print(r.sort('sort-input',by='d-*->field',get='d-*->field'))
运行结果:
4
[b'7', b'15', b'23', b'110']
[b'110', b'15', b'23', b'7']
0
0
0
0
[b'15', b'110', b'7', b'23']
[b'1', b'3', b'5', b'9']
最开头的几行代码设置了一些初始数据,然后对这些数据进行了数值排序和字符串排序,最后的代码演示了如果通过sort命令的特殊语法来将散列存储的数据作为权重进行排序,以及怎样获取并返回散列存储的数据。
sort命令不仅可以对列表进行排序,还可以对集合进行排序,然后返回一个列表形式的排序结果。上述实例除了展示了如果使用alpha关键字参数对元素进行字符串排序之外,还展示了如果基于外部数据对元素进行排序,以及如何获取并返回外部数据。
后面讲介绍如何组合使用集合操作和sort命令:当集合结构计算交集、并集和差集的能力,与sort命令获取散列存储的外部数据的能力相结合时,sort命令将变得非常强大。
尽管sort是Redis中唯一一个可以同时处理3种不同类型的数据的命令,但基本的Redis事务同样可以让我们在一连串不断执行的命令里面操作多种不同类型的数据。
基本的Redis事务
有时候为了同时处理多个结构,我们需要向Redis发送多个命令。尽管Redis有几个可以在两个键之间复制或者移动元素,但却没有那种可以在两个不同类型之间移动元素的命令(虽然可以使用zunionstore命令将元素从一个集合复制到一个有序集合)。为了对相同或者不同类型的多个键执行操作,Redis有5个命令可以让用户在不被打断(interruption)的情况下对多个键执行操作,它们分别是watch、multi、exec、unwatch、discard。
这一节中介绍最基本的Redis事务用法,其中只会用到multi命令和exec命令。
什么是Redis的基本事务
Redis的基本事务需要用到multi命令和exec命令,这种事务可以让一个客户端在不被其他客户端打断的情况下执行多个命令。和关系数据库那种可以在执行的过程中进行回滚(rollback)的事务不同,在Redis里面,被multi命令和exec命令包围的所有命令会一个接一个的执行,直到所有命令都执行完毕。当一个事务执行完毕之后,Redis才会处理其他客户端的命令。
要在Redis里面执行事务,我们首先需要执行multi命令,然后输入那些我们想要在事务里面执行的命令,最后再执行exec命令。当Redis从一个客户端那里接受到multi命令时,Redis会将这个客户端之后发送的所有命令都放入到一个队列里面,直到这个客户端发送exec命令为止,然后Redis就会在不被打断的情况下,一个接一个地执行存储在队列里面的命令。从语义上来说,Redis事务在Python客户端上面是由流水线(pipelien)实现:对连接对象用pipeline()方法将创建一个事务,在一切正常的情况下,客户端会自动地使用multi和exec包裹起用户输入的多个命令。此处,为了减少Redis与客户端之间的通信往返次数,提升执行多个命令时的性能,Python的Redis客户端会存储起事务包含的多个命令,然后在事务执行时一次性地将所有命令都发送给Redis。
跟介绍publish命令和subscribe命令时的情况一样,要展示事务执行结果,最简单的方法就是将事务放到线程里执行。
下面代码展示了在没有使用事务的情况下,执行并行(parallel)自增操作的结果:
import redis # 导入redis包包
import time,threading
# 与本地redis进行链接,地址为:localhost,端口号为6379
r = redis.StrictRedis(host='localhost', port=6379)
r.delete('notrans:')
def notrans():
#对'notrans:'计数器执行自增操作并打印操作的执行结果
print(r.incr('notrans:'))
#等待100毫秒
time.sleep(.1)
#对'notrans:'计数器执行自减操作。
r.incr('notrans:',-1)
if __name__ == '__main__':
# 启动3个线程来执行没有被事务包裹的自增、休眠和自减操作
for i in range(3):
threading.Thread(target=notrans).start()
# 等待500毫秒,让操作有足够的时间完成
time.sleep(.5)
结果:
1
2
3
因为没有使用事务,所以三个线程都可以在执行自减操作之前,对notrans:计数器执行自增操作。虽然代码里面通过休眠100毫秒放大了潜在问题,但如果我们确实需要在不受其它命令干扰的情况下,对计数器执行自增操作和自减操作,那么我们就不得不解决这个潜在问题。
下面代码使用事务来执行相同的操作:
import redis # 导入redis包包
import time,threading
# 与本地redis进行链接,地址为:localhost,端口号为6379
r = redis.StrictRedis(host='localhost', port=6379)
r.delete('trans:')
def notrans():
#创建一个事务型(transactional)流水线对象
pipeline=r.pipeline()
#把针对'trans:'计数器的自增操作放入队列
pipeline.incr('trans:')
#等待100毫秒
time.sleep(.1)
#把针对'trans:'计数器的自减操作放入队列
pipeline.incr('trans:',-1)
#执行被事务包裹的命令,并打印自增操作的执行结果
print(pipeline.execute()[0])
if __name__ == '__main__':
# 启动3个线程来执行没有被事务包裹的自增、休眠和自减操作
for i in range(3):
threading.Thread(target=notrans).start()
# 等待500毫秒,让操作有足够的时间完成
time.sleep(.5)
结果:
1
1
1
可以看出,尽管自增操作和自减操作直接有一段延迟时间,但通过使用事务,各个线程都可以在不被其它线程打断的情况下,执行各自队列里面的命令。记住,Redis要在接收到Exec命令之后,才会执行那些位于multi和exec之间的入队命令。
在使用Redis存储数据的时候,有些数据仅在一段很短的时间内有用,虽然我们可以在数据的有效期过了之后删除无用的数据,但更好的办法是使用Redis提供的键过期操作来自动删除无用数据。
键的过期时间
在使用Redis存储数据的时候,有些数据可能在某个时间点之后就不再有用了,用户可以使用DEL命令显示删除这些无用数据,也可以通过Redis的过期时间(expiration)特征来让一个键再给定的时限(timeout)之后自动被删除。当我们说一个键【带有生存时间】(time to live)或者一个键【会在特定时间之后过期】时,我们指的是Redis会在这个键的过期时间到达时自动删除该键。
虽然过期时间特性对于清理缓存数据非常有用,不过通常只有少数几个命令可以原子地为键设置过期时间,并且对于列表、集合、散列和有序集合这样的容器来说,键过期命令只能为整个键设置过期时间,而没办法为键里面的单个元素设置过期时间(为了解决这个问题,可以使用存储时间戳的有序集合来实现针对的那个元素的过期操作)。
本节将对那些可以在给定时限或者给定时间之后,自动删除过期键的Redis命令进行介绍。通过阅读本节,读者可以学会如何使用过期操作来自动删除过期数据并降低Redis的内存占用。
下表列出了Redis提供的用于为键设置过期时间的命令,已经查看键的过期时间的命令:
命令 | 示例 | 描述 |
---|---|---|
persist | persist key-name | 移除键的过期时间 |
ttl | ttl key-name | 查看给定键距离过期还有多少秒 |
expire | expire key-name seconds | 让给定键再指定的秒数之后过期 |
expireat | expireat key-name timestamp | 将给定键的过期时间设置为给定的UNIX时间戳。 |
pttl | pttl key-name | 查看给定键距离过期时间还有多少毫秒,这个命令在Redis2.6或以上版本可用, |
pexpire | pexpire key-name milliseconds | 让给定键再指定的毫秒之后过期。这个命令在Redis2.6或以上版本可用。 |
pexpireat | pexpireat key-name timestamp-milliseconds | 将一个毫秒级精确的UNIX时间戳设置为给定键的过期时间,这个命令在Redis2.6或以上版本可用。 |
下面代码展示了几个对键执行过期时间操作的例子:
import redis # 导入redis包包
import time
# 与本地redis进行链接,地址为:localhost,端口号为6379
r = redis.StrictRedis(host='localhost', port=6379)
r.delete('trans:')
#设置一个简单的字符串值作为过期时间的设置对象
print(r.set('key','value'))
print(r.get('key'))
print(r.expire('key',2))
time.sleep(1)
#查看键距离过期还有多长时间
print(r.ttl('key'))
time.sleep(1)
#此时键已经过期,并被删除
print(r.get('key'))
运行结果:
True
b'value'
True
1
None
上一篇文章: Python--Redis实战:第三章:Redis命令:第六节:发布与订阅
下一篇文章: Python--Redis实战:第四章:数据安全与性能保障:第1节:持久化选项