Redis 发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
实例
以下实例演示了发布订阅是如何工作的。在实例中创建了订阅频道名为 redisChat:
redis 127.0.0.1:6379> SUBSCRIBE redisChat Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "redisChat" 3) (integer) 1
现在,重新开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收到消息。
redis 127.0.0.1:6379> PUBLISH redisChat "Redis is a great caching technique" (integer) 1 redis 127.0.0.1:6379> PUBLISH redisChat "Learn redis by runoob.com" (integer) 1 # 订阅者的客户端会显示如下消息 1) "message" 2) "redisChat" 3) "Redis is a great caching technique" 1) "message" 2) "redisChat" 3) "Learn redis by runoob.com"
# 1. 订阅一个或多个符合给定模式的频道。 PSUBSCRIBE pattern [pattern ...] ''' 返回值:接收到的信息。 redis 127.0.0.1:6379> PSUBSCRIBE mychannel Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "mychannel" 3) (integer) 1 ''' # 2.查看订阅与发布系统状态。 PUBSUB subcommand [argument [argument ...]] ''' 返回值:由活跃频道组成的列表。 redis 127.0.0.1:6379> PUBSUB CHANNELS (empty list or set) ''' # 3.将信息发送到指定的频道。 PUBLISH channel message ''' 返回值:接收到信息的订阅者数量。 redis 127.0.0.1:6379> PUBLISH mychannel "hello, i m here" (integer) 1 ''' # 4.退订所有给定模式的频道。 PUNSUBSCRIBE [pattern [pattern ...]] ''' 返回值:这个命令在不同的客户端中有不同的表现。 redis 127.0.0.1:6379> PUNSUBSCRIBE mychannel 1) "punsubscribe" 2) "a" 3) (integer) 1 ''' # 5.订阅给定的一个或多个频道的信息。 SUBSCRIBE channel [channel ...] ''' 返回值:接收到的信息 redis 127.0.0.1:6379> SUBSCRIBE mychannel Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "mychannel" 3) (integer) 1 1) "message" 2) "mychannel" 3) "a" ''' # 6.指退订给定的频道。 UNSUBSCRIBE [channel [channel ...]] ''' 返回值:这个命令在不同的客户端中有不同的表现。 redis 127.0.0.1:6379> UNSUBSCRIBE mychannel 1) "unsubscribe" 2) "a" 3) (integer) 0 '''
Redis 事务
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
实例
以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:
redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days" QUEUED redis 127.0.0.1:6379> GET book-name QUEUED redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series" QUEUED redis 127.0.0.1:6379> SMEMBERS tag QUEUED redis 127.0.0.1:6379> EXEC 1) OK 2) "Mastering C++ in 21 days" 3) (integer) 3 4) 1) "Mastering Series" 2) "C++" 3) "Programming"
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
这是官网上的说明 From redis docs on transactions:
It's important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands.
比如:
redis 127.0.0.1:7000> multi OK redis 127.0.0.1:7000> set a aaa QUEUED redis 127.0.0.1:7000> set b bbb QUEUED redis 127.0.0.1:7000> set c ccc QUEUED redis 127.0.0.1:7000> exec 1) OK 2) OK 3) OK
如果在 set b bbb 处失败,set a 已成功不会回滚,set c 还会继续执行。
# 1.取消事务,放弃执行事务块内的所有命令。 DISCARD ''' 返回值 总是返回 OK 。 redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> PING QUEUED redis 127.0.0.1:6379> SET greeting "hello" QUEUED redis 127.0.0.1:6379> DISCARD OK ''' # 2.执行所有事务块内的命令。 EXEC ''' 返回值:事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。 # 事务被成功执行 redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> INCR user_id QUEUED redis 127.0.0.1:6379> INCR user_id QUEUED redis 127.0.0.1:6379> INCR user_id QUEUED redis 127.0.0.1:6379> PING QUEUED redis 127.0.0.1:6379> EXEC 1) (integer) 1 2) (integer) 2 3) (integer) 3 4) PONG # 监视 key ,且事务成功执行 redis 127.0.0.1:6379> WATCH lock lock_times OK redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> SET lock "huangz" QUEUED redis 127.0.0.1:6379> INCR lock_times QUEUED redis 127.0.0.1:6379> EXEC 1) OK 2) (integer) 1 # 监视 key ,且事务被打断 redis 127.0.0.1:6379> WATCH lock lock_times OK redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> SET lock "joe" # 就在这时,另一个客户端修改了 lock_times 的值 QUEUED redis 127.0.0.1:6379> INCR lock_times QUEUED redis 127.0.0.1:6379> EXEC # 因为 lock_times 被修改, joe 的事务执行失败 (nil) ''' # 3.标记一个事务块的开始。 MULTI ''' 返回值:总是返回 OK 。 redis 127.0.0.1:6379> MULTI # 标记事务开始 OK redis 127.0.0.1:6379> INCR user_id # 多条命令按顺序入队 QUEUED redis 127.0.0.1:6379> INCR user_id QUEUED redis 127.0.0.1:6379> INCR user_id QUEUED redis 127.0.0.1:6379> PING QUEUED redis 127.0.0.1:6379> EXEC # 执行 1) (integer) 1 2) (integer) 2 3) (integer) 3 4) PONG ''' # 4.取消 WATCH 命令对所有 key 的监视。 UNWATCH ''' 返回值: 总是返回 OK 。 redis 127.0.0.1:6379> WATCH lock lock_times OK redis 127.0.0.1:6379> UNWATCH OK ''' # 5.监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 WATCH key [key ...] ''' 返回值:总是返回 OK 。 redis> WATCH lock lock_times OK '''
Redis 脚本
Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。
Eval 命令的基本语法如下:
redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...]
实例
以下实例演示了 redis 脚本工作过程
redis 127.0.0.1:6379> EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second 1) "key1" 2) "key2" 3) "first" 4) "second"
# 1.执行 Lua 脚本。 EVAL script numkeys key [key ...] arg [arg ...] ''' 参数说明: script: 参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数。 numkeys: 用于指定键名参数的个数。 key [key ...]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。 arg [arg ...]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。 redis 127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second 1) "key1" 2) "key2" 3) "first" 4) "second" ''' # 2.执行 Lua 脚本。 EVALSHA sha1 numkeys key [key ...] arg [arg ...] ''' 参数说明: sha1 : 通过 SCRIPT LOAD 生成的 sha1 校验码。 numkeys: 用于指定键名参数的个数。 key [key ...]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。 arg [arg ...]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。 redis 127.0.0.1:6379> SCRIPT LOAD "return 'hello moto'" "232fd51614574cf0867b83d384a5e898cfd24e5a" redis 127.0.0.1:6379> EVALSHA "232fd51614574cf0867b83d384a5e898cfd24e5a" 0 "hello moto" ''' # 3.查看指定的脚本是否已经被保存在缓存当中。 SCRIPT EXISTS script [script ...] ''' 返回值:一个列表,包含 0 和 1 ,前者表示脚本不存在于缓存,后者表示脚本已经在缓存里面了。 列表中的元素和给定的 SHA1 校验和保持对应关系,比如列表的第三个元素的值就表示第三个 SHA1 校验和所指定的脚本在缓存中的状态。 redis 127.0.0.1:6379> SCRIPT LOAD "return 'hello moto'" # 载入一个脚本 "232fd51614574cf0867b83d384a5e898cfd24e5a" redis 127.0.0.1:6379> SCRIPT EXISTS 232fd51614574cf0867b83d384a5e898cfd24e5a 1) (integer) 1 redis 127.0.0.1:6379> SCRIPT FLUSH # 清空缓存 OK redis 127.0.0.1:6379> SCRIPT EXISTS 232fd51614574cf0867b83d384a5e898cfd24e5a 1) (integer) 0 ''' # 4.从脚本缓存中移除所有脚本。 SCRIPT FLUSH ''' 返回值:OK redis 127.0.0.1:6379> SCRIPT FLUSH OK ''' # 5.杀死当前正在运行的 Lua 脚本。 SCRIPT KILL ''' 返回值:OK redis 127.0.0.1:6379> SCRIPT KILL OK ''' # 6.将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。 SCRIPT LOAD script ''' 返回值:给定脚本的 SHA1 校验和 redis 127.0.0.1:6379> SCRIPT LOAD "return 1" "e0e1f9fabfc9d4800c877a703b823ac0578ff8db" '''