# 发布与订阅(pub/sub)
Note
本文档翻译自: [http://redis.io/topics/pubsub](http://redis.io/topics/pubsub) 。
[SUBSCRIBE](../pub_sub/subscribe.html#subscribe) 、 [UNSUBSCRIBE](../pub_sub/unsubscribe.html#unsubscribe) 和 [PUBLISH](../pub_sub/publish.html#publish) 三个命令实现了[发布与订阅信息泛型](http://en.wikipedia.org/wiki/Publish/subscribe)(Publish/Subscribe messaging paradigm), 在这个实现中, 发送者(发送信息的客户端)不是将信息直接发送给特定的接收者(接收信息的客户端), 而是将信息发送给频道(channel), 然后由频道将信息转发给所有对这个频道感兴趣的订阅者。
发送者无须知道任何关于订阅者的信息, 而订阅者也无须知道是那个客户端给它发送信息, 它只要关注自己感兴趣的频道即可。
对发布者和订阅者进行解构(decoupling), 可以极大地提高系统的扩展性(scalability), 并得到一个更动态的网络拓扑(network topology)。
比如说, 要订阅频道 `foo` 和 `bar` , 客户端可以使用频道名字作为参数来调用 [SUBSCRIBE](../pub_sub/subscribe.html#subscribe) 命令:
```
redis> SUBSCRIBE foo bar
```
当有客户端发送信息到这些频道时, Redis 会将传入的信息推送到所有订阅这些频道的客户端里面。
正在订阅频道的客户端不应该发送除 [SUBSCRIBE](../pub_sub/subscribe.html#subscribe) 和 [UNSUBSCRIBE](../pub_sub/unsubscribe.html#unsubscribe) 之外的其他命令。 其中, [SUBSCRIBE](../pub_sub/subscribe.html#subscribe) 可以用于订阅更多频道, 而 [UNSUBSCRIBE](../pub_sub/unsubscribe.html#unsubscribe) 则可以用于退订已订阅的一个或多个频道。
[SUBSCRIBE](../pub_sub/subscribe.html#subscribe) 和 [UNSUBSCRIBE](../pub_sub/unsubscribe.html#unsubscribe) 的执行结果会以信息的形式返回, 客户端可以通过分析所接收信息的第一个元素, 从而判断所收到的内容是一条真正的信息, 还是 [SUBSCRIBE](../pub_sub/subscribe.html#subscribe) 或 [UNSUBSCRIBE](../pub_sub/unsubscribe.html#unsubscribe) 命令的操作结果。
## 信息的格式
频道转发的每条信息都是一条带有三个元素的多条批量回复(multi-bulk reply)。
信息的第一个元素标识了信息的类型:
* `subscribe` : 表示当前客户端成功地订阅了信息第二个元素所指示的频道。 而信息的第三个元素则记录了目前客户端已订阅频道的总数。
* `unsubscribe` : 表示当前客户端成功地退订了信息第二个元素所指示的频道。 信息的第三个元素记录了客户端目前仍在订阅的频道数量。 当客户端订阅的频道数量降为 `0` 时, 客户端不再订阅任何频道, 它可以像往常一样, 执行任何 Redis 命令。
* `message` : 表示这条信息是由某个客户端执行 [PUBLISH](../pub_sub/publish.html#publish) 命令所发送的, 真正的信息。 信息的第二个元素是信息来源的频道, 而第三个元素则是信息的内容。
举个例子, 如果客户端执行以下命令:
```
redis> SUBSCRIBE first second
```
那么它将收到以下回复:
```
1) "subscribe"
2) "first"
3) (integer) 1
1) "subscribe"
2) "second"
3) (integer) 2
```
如果在这时, 另一个客户端执行以下 [PUBLISH](../pub_sub/publish.html#publish) 命令:
```
redis> PUBLISH second Hello
```
那么之前订阅了 `second` 频道的客户端将收到以下信息:
```
1) "message"
2) "second"
3) "hello"
```
当订阅者决定退订所有频道时, 它可以执行一个无参数的 [UNSUBSCRIBE](../pub_sub/unsubscribe.html#unsubscribe) 命令:
```
redis> UNSUBSCRIBE
```
这个命令将接到以下回复:
```
1) "unsubscribe"
2) "second"
3) (integer) 1
1) "unsubscribe"
2) "first"
3) (integer) 0
```
## 订阅模式
Redis 的发布与订阅实现支持模式匹配(pattern matching): 客户端可以订阅一个带 `*` 号的模式, 如果某个/某些频道的名字和这个模式匹配, 那么当有信息发送给这个/这些频道的时候, 客户端也会收到这个/这些频道的信息。
比如说,执行命令
```
redis> PSUBSCRIBE news.*
```
的客户端将收到来自 `news.art.figurative` 、 `news.music.jazz` 等频道的信息。
客户端订阅的模式里面可以包含多个 glob 风格的通配符, 比如 `*` 、 `?` 和 `[...]` , 等等。
执行命令
```
redis> PUNSUBSCRIBE news.*
```
将退订 `news.*` 模式, 其他已订阅的模式不会被影响。
通过订阅模式接收到的信息, 和通过订阅频道接收到的信息, 这两者的格式不太一样:
* 通过订阅模式而接收到的信息的类型为 `pmessage` : 这代表有某个客户端通过 [PUBLISH](../pub_sub/publish.html#publish) 向某个频道发送了信息, 而这个频道刚好匹配了当前客户端所订阅的某个模式。 信息的第二个元素记录了被匹配的模式, 第三个元素记录了被匹配的频道的名字, 最后一个元素则记录了信息的实际内容。
客户端处理 [PSUBSCRIBE](../pub_sub/psubscribe.html#psubscribe) 和 [PUNSUBSCRIBE](../pub_sub/punsubscribe.html#punsubscribe) 返回值的方式, 和客户端处理 [SUBSCRIBE](../pub_sub/subscribe.html#subscribe) 和 [UNSUBSCRIBE](../pub_sub/unsubscribe.html#unsubscribe) 的方式类似: 通过对信息的第一个元素进行分析, 客户端可以判断接收到的信息是一个真正的信息, 还是 [PSUBSCRIBE](../pub_sub/psubscribe.html#psubscribe) 或 [PUNSUBSCRIBE](../pub_sub/punsubscribe.html#punsubscribe) 命令的返回值。
## 通过频道和模式接收同一条信息
如果客户端订阅的多个模式匹配了同一个频道, 或者客户端同时订阅了某个频道、以及匹配这个频道的某个模式, 那么它可能会多次接收到同一条信息。
举个例子, 如果客户端执行了以下命令:
```
SUBSCRIBE foo
PSUBSCRIBE f*
```
那么当有信息发送到频道 `foo` 时, 客户端将收到两条信息: 一条来自频道 `foo` ,信息类型为 `message` ; 另一条来自模式 `f*` ,信息类型为 `pmessage` 。
## 订阅总数
在执行 [SUBSCRIBE](../pub_sub/subscribe.html#subscribe) 、 [UNSUBSCRIBE](../pub_sub/unsubscribe.html#unsubscribe) 、 [PSUBSCRIBE](../pub_sub/psubscribe.html#psubscribe) 和 [PUNSUBSCRIBE](../pub_sub/punsubscribe.html#punsubscribe) 命令时, 返回结果的最后一个元素是客户端目前仍在订阅的频道和模式总数。
当客户端退订所有频道和模式, 也即是这个总数值下降为 `0` 的时候, 客户端将退出订阅与发布状态。
## 编程示例
Pieter Noordhuis 提供了一个使用 EventMachine 和 Redis 编写的 [高性能多用户网页聊天软件](https://gist.github.com/348262) , 这个软件很好地展示了发布与订阅功能的用法。
## 客户端库实现提示
因为所有接收到的信息都会包含一个信息来源:
* 当信息来自频道时,来源是某个频道;
* 当信息来自模式时,来源是某个模式。
因此, 客户端可以用一个哈希表, 将特定来源和处理该来源的回调函数关联起来。 当有新信息到达时, 程序就可以根据信息的来源, 在 O(1) 复杂度内, 将信息交给正确的回调函数来处理。
- Redis 文档
- 键空间通知(keyspace notification)
- 事务(transaction)
- 发布与订阅(pub/sub)
- 复制(Replication)
- 通信协议(protocol)
- 持久化(persistence)
- Sentinel
- 集群教程
- Redis 集群规范
- Redis 命令参考
- Key(键)
- DEL
- DUMP
- EXISTS
- EXPIRE
- EXPIREAT
- KEYS
- MIGRATE
- MOVE
- OBJECT
- PERSIST
- PEXPIRE
- PEXPIREAT
- PTTL
- RANDOMKEY
- RENAME
- RENAMENX
- RESTORE
- SORT
- TYPE
- SCAN
- String(字符串)
- APPEND
- BITCOUNT
- BITOP
- DECR
- DECRBY
- GET
- GETBIT
- GETRANGE
- GETSET
- INCR
- INCRBY
- INCRBYFLOAT
- MGET
- MSET
- MSETNX
- PSETEX
- SET
- SETBIT
- SETEX
- SETNX
- SETRANGE
- STRLEN
- Hash(哈希表)
- HDEL
- HEXISTS
- HGET
- HGETALL
- HINCRBY
- HINCRBYFLOAT
- HKEYS
- HLEN
- HMGET
- HMSET
- HSET
- HSETNX
- HVALS
- HSCAN
- List(列表)
- BLPOP
- BRPOP
- BRPOPLPUSH
- LINDEX
- LINSERT
- LLEN
- LPOP
- LPUSH
- LRANGE
- LREM
- LSET
- LTRIM
- RPOP
- RPOPLPUSH
- RPUSH
- RPUSHX
- Set(集合)
- SADD
- SCARD
- SDIFF
- SDIFFSTORE
- SINTER
- SINTER
- SINTERSTORE
- SISMEMBER
- SMEMBERS
- SMOVE
- SPOP
- SRANDMEMBER
- SREM
- SUNION
- SUNIONSTORE
- SSCAN
- SortedSet(有序集合)
- ZADD
- ZCARD
- ZCOUNT
- ZINCRBY
- ZRANGE
- ZRANGEBYSCORE
- ZRANK
- ZREM
- ZREMRANGEBYRANK
- ZREMRANGEBYSCORE
- ZREVRANGE
- ZREVRANGEBYSCORE
- ZREVRANK
- ZSCORE
- ZUNIONSTORE
- ZINTERSTORE
- ZSCAN
- Pub/Sub(发布/订阅)
- PSUBSCRIBE
- PUBLISH
- PUBSUB
- PUNSUBSCRIBE
- SUBSCRIBE
- UNSUBSCRIBE
- Transaction(事务)
- DISCARD
- EXEC
- MULTI
- UNWATCH
- WATCH
- Script(脚本)
- EVAL
- EVALSHA
- SCRIPT EXISTS
- SCRIPT FLUSH
- SCRIPT KILL
- SCRIPT LOAD
- Connection(连接)
- AUTH
- ECHO
- PING
- QUIT
- SELECT
- Server(服务器)
- BGREWRITEAOF
- BGSAVE
- CLIENT GETNAME
- CLIENT KILL
- CLIENT LIST
- CLIENT SETNAME
- CONFIG GET
- CONFIG RESETSTAT
- CONFIG REWRITE
- CONFIG SET
- DBSIZE
- DEBUG OBJECT
- DEBUG SEGFAULT
- FLUSHALL
- FLUSHDB
- INFO
- LASTSAVE
- MONITOR
- PSYNC
- SAVE
- SHUTDOWN
- SLAVEOF
- SLOWLOG
- SYNC
- TIME
- 关于