# 發布與訂閱(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
- 關于