Redis 学习笔记 (三):Redis 命令补充

730 阅读10分钟
原文链接: lawtech0902.com

在之前的学习笔记中,还有许多Redis的命令没有涉及,这一篇主要用来简要地补充,当然,详细的命令还得参考Redis的官方命令文档

键值相关命令

下表展示了Redis提供的一些键值(KEY-VALUE)相关的常用命令及其redis-py API

命令 用例 描述 redis-py API
KEYS KEYS pattern 查找所有符合给定模式pattern(正则表达式)的key keys(pattern=’*’)
EXISTS EXISTS key 检查给定key是否存在 exists(name)
EXPIRE EXPIRE key seconds 为给定key设置生存时间,当key过期时(生存时间为0),它会被自动删除 expire(name, time)
MOVE MOVE key db 将当前数据库的key移动到给定的数据库db当中 move(name, db)
PERSIST PERSIST key 移除给定key的生存时间,将这个key从『易失的』(带生存时间key)转换成『持久的』(一个不带生存时间、永不过期的key) persist(name)
RANDOMKEY RANDOMKEY 从当前数据库返回一个随机的key randomkey()
RENAME RENAME key newkey 将key重命名为newkey,如果key与newkey相同,将返回一个错误。如果newkey已经存在,则值将被覆盖 rename(src, dst)
TYPE TYPE key 返回key所存储的value的数据结构类型,它可以返回string, list, set, zset和hash等不同的类型 type(name)
TTL TTL key 返回key剩余的过期时间(单位:秒) ttl(name)

下面这个交互示例展示了Redis中关于键的过期时间相关的命令的使用方法

>>> r.set('key', 'value')
True
>>> r.get('key')
b'value'
>>> r.expire('key', 2)
True
>>> time.sleep(2)
>>> r.get('key')
>>> r.set('key', 'value2')
True
>>> r.expire('key', 100); r.ttl('key')
True
100

发布与订阅

发布订阅(pub/sub)是一种消息通信模式,主要的目的是解耦消息发布者和消息订阅者之间的耦合,这点和设计模式中的观察者模式比较相似。pub/sub不仅仅解决发布者和订阅者之间代码级别耦合也解决两者在物理部署上的耦合。Redis作为一个pub/sub的server,在订阅者和发布者之间起到了消息路由的功能。订阅者可以通过subscribe和psubscribe命令向 redis server订阅自己感兴趣的消息类型,redis将消息类型称为通道(channel)。当发布者通过publish命令向 redis server发送二进制字符串消息(binary string message)时,订阅该消息类型的全部client都会收到此消息。这里消息的传递是多对多的,一个client可以订阅多个channel,也可以向多个channel发送消息。

下表展示了Redis提供的发布与订阅命令及其redis-py API

命令 用例 描述 redis-py API
SUBSCRIBE SUBSCRIBE channel [channel …] 订阅给定的频道 subscribe(args, *kwargs)
UNSUBSCRIBE UNSUBSCRIBE [channel [channel …]] 退订给定的频道,如果没有给定任何频道,则退订所有频道 unsubscribe(*args)
PUBLISH PUBLISH channel message 将信息message发送到指定的频道channel publish(channel, message)
PSUBSCRIBE PSUBSCRIBE pattern [pattern …] 订阅与给定模式相关的频道 psubscribe(args, *kwargs)
PUNSUBSCRIBE PUNSUBSCRIBE [pattern [pattern …]] 退订给定的模式,如果没有给定,则退订所有模式 PUNSUBSCRIBE [pattern [pattern …]]
PUBSUB PUBSUB subcommand [argument [argument …]] PUBSUB命令是一个introspection命令,允许检查Pub/Sub子系统的状态,它由单独记录的子命令组成 pubsub(**kwargs)

考虑到PUBLISH命令和SUBSCRIBE命令在Python客户端的实现方式,一个比较简单的延时发布与订阅的方法,就是像如下代码那样用辅助线程(helper thread)来执行PUBLISH命令

import redis
import time
import threading
pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
r = redis.StrictRedis(connection_pool=pool)
def publisher(n):
    time.sleep(1)
    for i in range(n):
        r.publish('channel', i)
def run_pubsub():
    threading.Thread(target=publisher, args=(3,)).start()
    pubsub = r.pubsub()
    pubsub.subscribe(['channel'])
    count=0
    for item in pubsub.listen():
        print(item)
        count += 1
        if count == 4:
            pubsub.unsubscribe()
        if count == 5:
            break

publisher函数在刚开始执行时会先休眠,让订阅者有足够的时间来连接服务器并监听消息。在发布消息之后进行短暂的休眠,让消息可以一条接一条地出现。

run_pubsub函数启动发送者线程,让它发送三条消息。随后创建发布与订阅对象,并让它订阅给定的频道。通过遍历函数pubsub.listen()的执行结果来监听订阅消息。在接收到一条订阅反馈消息和三条发布者发送的消息之后,执行退订操作,停止监听新消息。客户端在接收到退订反馈消息之后,就不再接收消息。

实际运行函数并观察它们的行为

>>> run_pubsub()
{'type': 'subscribe', 'channel': b'channel', 'data': 1, 'pattern': None}
{'type': 'message', 'channel': b'channel', 'data': b'0', 'pattern': None}
{'type': 'message', 'channel': b'channel', 'data': b'1', 'pattern': None}
{'type': 'message', 'channel': b'channel', 'data': b'2', 'pattern': None}
{'type': 'unsubscribe', 'channel': b'channel', 'data': 0, 'pattern': None}

以上这些结构就是我们在遍历pubsub.listen()函数时得到的元素。

在刚开始订阅一个频道的时候,客户端会接收到一条关于被订阅频道的反馈消息。在退订频道时,客户端会接收到一条反馈消息,告知被退订的是哪一个频道,以及客户端目前仍在订阅的频道数量。

其他命令

排序

Redis中负责执行排序操作的SORT命令可以根据字符串、列表、集合、有序集合、散列这5中键里面存储的数据,对列表、集合以及有序集合进行排序,可以将SORT命令看作是SQL语言中的order by子句。

下表展示了SORT命令的定义及其redis-py API

命令 用例 描述 redis-py API
SORT SORT key [BY pattern] [LIMIT offset count] [GET pattern] [ASC|DESC] [ALPHA] destination 返回或存储key的list、set或sorted set中的元素。默认是按照数值类型排序的,并且按照两个元素的双精度浮点数类型值进行比较 sort(name, start=None, num=None, by=None, get=None, desc=False, alpha=False, store=None, groups=False)

下面展示了SORT命令的一些简单的用法

>>> r.rpush('sort-input', 23, 15, 110, 7)
4
>>> r.sort('sort-input')
[b'7', b'15', b'23', b'110']
>>> r.sort('sort-input', alpha=True)
[b'110', b'15', b'23', b'7']
>>> r.hset('d-7', 'field', 5)
1
>>> r.hset('d-15', 'field', 1)
1
>>> r.hset('d-23', 'field', 9)
1
>>> r.hset('d-110', 'field', 3)
1
>>> r.sort('sort-input', by='d-*->field')
[b'15', b'110', b'7', b'23']
>>> r.sort('sort-input', by='d-*->field', get='d-*->field')
[b'1', b'3', b'5', b'9']

SORT命令不仅可以对列表进行排序,还可以对集合进行排序,然后返回一个列表形式的排序结果。上述代码除了展示如何使用alpha关键字(根据元素字母表顺序,默认根据大小)参数对元素进行字符串排序之外,还展示了如何基于外部数据对元素进行排序,以及如何获取并返回外部数据。

尽管SORT是Redis中唯一一个可以同时处理3种不同类型的数据的命令,但是事务同样可以让我们在一连串不间断执行的命令里面操作不同类型的数据。

基本的Redis事务

Redis中的事务(transaction)是一组命令的集合。MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务的基础。

事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 事务是一个原子操作:事务中的命令要么全部执行,要么全不执行。

事务的应用非常普遍,比如银行汇款过程中A向B汇款,系统先从A的账户中将钱划走,然后向B的账户中增加相应的金额。这两个步骤必须属于同一个事务,要么全部执行,要么全不执行。

Redis的基本事务(basic transaction)需要用到MULTI和EXEC命令。在Redis中,被MULTI和EXEC命令包围的所有命令会一个接一个地执行,直到所有命令都执行完毕为止。当一个事务执行完毕后,才会处理其他客户端的命令。

Redis中执行事务的步骤:首先需要执行MULTI命令,然后输入我们想要在事务里面执行的命令,最后再执行EXEC命令。MULTI命令用于开启一个事务,它总是返回OK 。MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。另一方面,通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务。EXEC命令的回复是一个数组,数组中的每个元素都是执行事务中的命令所产生的回复。其中,回复元素的先后顺序和命令发送的先后顺序一致。当客户端处于事务状态时,所有传入的命令都会返回一个内容为QUEUED的状态回复status reply,这些被入队的命令将在EXEC命令被调用时执行。

从语义上来说,Redis事务在Python客户端中是由管道(pipeline)实现的:对连接对象调用pipeline()方法将创建一个事务,在一切正常的情况下,客户端会自动地调用MULTI命令包裹用户输入的多个命令。此外,为了减少Redis与客户端之间的通信往返次数,提升执行多个命令的性能,Python的Redis客户端会存储起事务包含的多个命令,然后在事务执行时一次性将所有命令都发送给Redis。

要展示事务执行的结果,最简单的方法就是将事务放到线程里面执行,下面这个交互示例展示了在没有使用事务的情况下,执行并行(parallel)自增操作的结果

>>> import redis
>>> import threading
>>> import time
>>> r = redis.StrictRedis(host='localhost', port=6379, db=0)
>>> def notrans():
...     print(r.incr('notrans:'))
...     time.sleep(.1)
...     r.incr('notrans:', -1)
...
>>> if 1:
...     for i in range(3):
...             threading.Thread(target=notrans).start()
...     time.sleep(.5)
...
2
1
3

上述代码启动了3个线程来执行没有被事务包裹的自增、休眠和自减操作,正因为没有使用事务,所以三个线程都可以在执行自减操作前,对notrans:计数器执行自增操作。

下面这个交互示例就展示了如何使用事务处理命令的并行执行问题

>>> def trans():
...     pipeline = r.pipeline()
...     pipeline.incr('trans:')
...     time.sleep(.1)
...     pipeline.incr('trans:', -1)
...     print(pipeline.execute()[0])
...
>>> if 1:
...     for i in range(3):
...             threading.Thread(target=trans).start()
...     time.sleep(.5)
...
1
1
1

首先在trans函数中创建一个事务型(transactional)管道对象,然后先把针对’tans:’计数器的自增操作放入队列,等待100ms后再将针对’tans:’计数器的自减操作放入队列,最后执行被事务包裹的命令,并打印自增操作的执行结果。最终在执行结果中可以看到,尽管自增和自减操作之间有一段延迟时间,但通过使用事务,各个线程都可以在不被其他线程打断的情况下,执行各自队列里面的命令。

# Redis, Python 🐶 怕是要给老板下跪了哦~ 🐶 赞赏 LawTech. WeChat Pay

微信打赏

LawTech. Alipay

支付宝打赏