# 通信協議(protocol)
Note
本文檔翻譯自: [http://redis.io/topics/protocol](http://redis.io/topics/protocol) 。
Redis 協議在以下三個目標之間進行折中:
* 易于實現
* 可以高效地被計算機分析(parse)
* 可以很容易地被人類讀懂
## 網絡層
客戶端和服務器通過 TCP 連接來進行數據交互, 服務器默認的端口號為 6379 。
客戶端和服務器發送的命令或數據一律以 `\r\n` (CRLF)結尾。
## 請求
Redis 服務器接受命令以及命令的參數。
服務器會在接到命令之后,對命令進行處理,并將命令的回復傳送回客戶端。
## 新版統一請求協議
新版統一請求協議在 Redis 1.2 版本中引入, 并最終在 Redis 2.0 版本成為 Redis 服務器通信的標準方式。
你的 Redis 客戶端應該按照這個新版協議來進行實現。
在這個協議中, 所有發送至 Redis 服務器的參數都是二進制安全(binary safe)的。
以下是這個協議的一般形式:
```
*<參數數量> CR LF
$<參數 1 的字節數量> CR LF
<參數 1 的數據> CR LF
...
$<參數 N 的字節數量> CR LF
<參數 N 的數據> CR LF
```
Note
譯注:命令本身也作為協議的其中一個參數來發送。
舉個例子, 以下是一個命令協議的打印版本:
```
*3
$3
SET
$5
mykey
$7
myvalue
```
這個命令的實際協議值如下:
```
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
```
稍后我們會看到, 這種格式除了用作命令請求協議之外, 也用在命令的回復協議中: 這種只有一個參數的回復格式被稱為**批量回復(Bulk Reply)**。
統一協議請求原本是用在回復協議中, 用于將列表的多個項返回給客戶端的, 這種回復格式被稱為**多條批量回復(Multi Bulk Reply)**。
一個多條批量回復以 `*<argc>\r\n` 為前綴, 后跟多條不同的批量回復, 其中 `argc` 為這些批量回復的數量。
## 回復
Redis 命令會返回多種不同類型的回復。
通過檢查服務器發回數據的第一個字節, 可以確定這個回復是什么類型:
* 狀態回復(status reply)的第一個字節是 `"+"`
* 錯誤回復(error reply)的第一個字節是 `"-"`
* 整數回復(integer reply)的第一個字節是 `":"`
* 批量回復(bulk reply)的第一個字節是 `"$"`
* 多條批量回復(multi bulk reply)的第一個字節是 `"*"`
## 狀態回復
一個狀態回復(或者單行回復,single line reply)是一段以 `"+"` 開始、 `"\r\n"` 結尾的單行字符串。
以下是一個狀態回復的例子:
```
+OK
```
客戶端庫應該返回 `"+"` 號之后的所有內容。 比如在在上面的這個例子中, 客戶端就應該返回字符串 `"OK"` 。
狀態回復通常由那些不需要返回數據的命令返回,這種回復不是二進制安全的,它也不能包含新行。
狀態回復的額外開銷非常少,只需要三個字節(開頭的 `"+"` 和結尾的 CRLF)。
## 錯誤回復
錯誤回復和狀態回復非常相似, 它們之間的唯一區別是, 錯誤回復的第一個字節是 `"-"` , 而狀態回復的第一個字節是 `"+"` 。
錯誤回復只在某些地方出現問題時發送: 比如說, 當用戶對不正確的數據類型執行命令, 或者執行一個不存在的命令, 等等。
一個客戶端庫應該在收到錯誤回復時產生一個異常。
以下是兩個錯誤回復的例子:
```
-ERR unknown command 'foobar'
-WRONGTYPE Operation against a key holding the wrong kind of value
```
在 `"-"` 之后,直到遇到第一個空格或新行為止,這中間的內容表示所返回錯誤的類型。
`ERR` 是一個通用錯誤,而 `WRONGTYPE` 則是一個更特定的錯誤。 一個客戶端實現可以為不同類型的錯誤產生不同類型的異常, 或者提供一種通用的方式, 讓調用者可以通過提供字符串形式的錯誤名來捕捉(trap)不同的錯誤。
不過這些特性用得并不多, 所以并不是特別重要, 一個受限的(limited)客戶端可以通過簡單地返回一個邏輯假(false)來表示一個通用的錯誤條件。
## 整數回復
整數回復就是一個以 `":"` 開頭, CRLF 結尾的字符串表示的整數。
比如說, `":0\r\n"` 和 `":1000\r\n"` 都是整數回復。
返回整數回復的其中兩個命令是 [INCR](../string/incr.html#incr) 和 [LASTSAVE](../server/lastsave.html#lastsave) 。 被返回的整數沒有什么特殊的含義, [INCR](../string/incr.html#incr) 返回鍵的一個自增后的整數值, 而 [LASTSAVE](../server/lastsave.html#lastsave) 則返回一個 UNIX 時間戳, 返回值的唯一限制是這些數必須能夠用 64 位有符號整數表示。
整數回復也被廣泛地用于表示邏輯真和邏輯假: 比如 [EXISTS](../key/exists.html#exists) 和 [SISMEMBER](../set/sismember.html#sismember) 都用返回值 `1` 表示真, `0` 表示假。
其他一些命令, 比如 [SADD](../set/sadd.html#sadd) 、 [SREM](../set/srem.html#srem) 和 [SETNX](../string/setnx.html#setnx) , 只在操作真正被執行了的時候, 才返回 `1` , 否則返回 `0` 。
以下命令都返回整數回復: [SETNX](../string/setnx.html#setnx) 、 [DEL](../key/del.html#del) 、 [EXISTS](../key/exists.html#exists) 、 [INCR](../string/incr.html#incr) 、 [INCRBY](../string/incrby.html#incrby) 、 [DECR](../string/decr.html#decr) 、 [DECRBY](../string/decrby.html#decrby) 、 [DBSIZE](../server/dbsize.html#dbsize) 、 [LASTSAVE](../server/lastsave.html#lastsave) 、 [RENAMENX](../key/renamenx.html#renamenx) 、 [MOVE](../key/move.html#move) 、 [LLEN](../list/llen.html#llen) 、 [SADD](../set/sadd.html#sadd) 、 [SREM](../set/srem.html#srem) 、 [SISMEMBER](../set/sismember.html#sismember) 、 [SCARD](../set/scard.html#scard) 。
## 批量回復
服務器使用批量回復來返回二進制安全的字符串,字符串的最大長度為 512 MB 。
```
客戶端:GET mykey
服務器:foobar
```
服務器發送的內容中:
* 第一字節為 `"$"` 符號
* 接下來跟著的是表示實際回復長度的數字值
* 之后跟著一個 CRLF
* 再后面跟著的是實際回復數據
* 最末尾是另一個 CRLF
對于前面的 [GET](../string/get.html#get) 命令,服務器實際發送的內容為:
```
"$6\r\nfoobar\r\n"
```
如果被請求的值不存在, 那么批量回復會將特殊值 `-1` 用作回復的長度值, 就像這樣:
```
客戶端:GET non-existing-key
服務器:$-1
```
這種回復稱為空批量回復(NULL Bulk Reply)。
當請求對象不存在時,客戶端應該返回空對象,而不是空字符串: 比如 Ruby 庫應該返回 `nil` , 而 C 庫應該返回 `NULL` (或者在回復對象中設置一個特殊標志), 諸如此類。
## 多條批量回復
像 [LRANGE](../list/lrange.html#lrange) 這樣的命令需要返回多個值, 這一目標可以通過多條批量回復來完成。
多條批量回復是由多個回復組成的數組, 數組中的每個元素都可以是任意類型的回復, 包括多條批量回復本身。
多條批量回復的第一個字節為 `"*"` , 后跟一個字符串表示的整數值, 這個值記錄了多條批量回復所包含的回復數量, 再后面是一個 CRLF 。
```
客戶端: LRANGE mylist 0 3
服務器: *4
服務器: $3
服務器: foo
服務器: $3
服務器: bar
服務器: $5
服務器: Hello
服務器: $5
服務器: World
```
在上面的示例中,服務器發送的所有字符串都由 CRLF 結尾。
正如你所見到的那樣, 多條批量回復所使用的格式, 和客戶端發送命令時使用的統一請求協議的格式一模一樣。 它們之間的唯一區別是:
* 統一請求協議只發送批量回復。
* 而服務器應答命令時所發送的多條批量回復,則可以包含任意類型的回復。
以下例子展示了一個多條批量回復, 回復中包含四個整數值, 以及一個二進制安全字符串:
```
*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$6\r\n
foobar\r\n
```
在回復的第一行, 服務器發送 `*5\r\n` , 表示這個多條批量回復包含 5 條回復, 再后面跟著的則是 5 條回復的正文。
多條批量回復也可以是空白的(empty), 就像這樣:
```
客戶端: LRANGE nokey 0 1
服務器: *0\r\n
```
無內容的多條批量回復(null multi bulk reply)也是存在的, 比如當 [BLPOP](../list/blpop.html#blpop) 命令的阻塞時間超過最大時限時, 它就返回一個無內容的多條批量回復, 這個回復的計數值為 `-1` :
```
客戶端: BLPOP key 1
服務器: *-1\r\n
```
客戶端庫應該區別對待空白多條回復和無內容多條回復: 當 Redis 返回一個無內容多條回復時, 客戶端庫應該返回一個 null 對象, 而不是一個空數組。
## 多條批量回復中的空元素
多條批量回復中的元素可以將自身的長度設置為 `-1` , 從而表示該元素不存在, 并且也不是一個空白字符串(empty string)。
當 [SORT](../key/sort.html#sort) 命令使用 `GET pattern` 選項對一個不存在的鍵進行操作時, 就會發生多條批量回復中帶有空白元素的情況。
以下例子展示了一個包含空元素的多重批量回復:
```
服務器: *3
服務器: $3
服務器: foo
服務器: $-1
服務器: $3
服務器: bar
```
其中, 回復中的第二個元素為空。
對于這個回復, 客戶端庫應該返回類似于這樣的回復:
```
["foo", nil, "bar"]
```
## 多命令和流水線
客戶端可以通過流水線, 在一次寫入操作中發送多個命令:
* 在發送新命令之前, 無須閱讀前一個命令的回復。
* 多個命令的回復會在最后一并返回。
## 內聯命令
當你需要和 Redis 服務器進行溝通, 但又找不到 `redis-cli` , 而手上只有 `telnet` 的時候, 你可以通過 Redis 特別為這種情形而設的內聯命令格式來發送命令。
以下是一個客戶端和服務器使用內聯命令來進行交互的例子:
```
客戶端: PING
服務器: +PONG
```
以下另一個返回整數值的內聯命令的例子:
```
客戶端: EXISTS somekey
服務器: :0
```
因為沒有了統一請求協議中的 `"*"` 項來聲明參數的數量, 所以在 `telnet` 會話輸入命令的時候, 必須使用空格來分割各個參數, 服務器在接收到數據之后, 會按空格對用戶的輸入進行分析(parse), 并獲取其中的命令參數。
## 高性能 Redis 協議分析器
盡管 Redis 的協議非常利于人類閱讀, 定義也很簡單, 但這個協議的實現性能仍然可以和二進制協議一樣快。
因為 Redis 協議將數據的長度放在數據正文之前, 所以程序無須像 JSON 那樣, 為了尋找某個特殊字符而掃描整個 payload , 也無須對發送至服務器的 payload 進行轉義(quote)。
程序可以在對協議文本中的各個字符進行處理的同時, 查找 CR 字符, 并計算出批量回復或多條批量回復的長度, 就像這樣:
```
#include <stdio.h>
int main(void) {
unsigned char *p = "$123\r\n";
int len = 0;
p++;
while(*p != '\r') {
len = (len*10)+(*p - '0');
p++;
}
/* Now p points at '\r', and the len is in bulk_len. */
printf("%d\n", len);
return 0;
}
```
得到了批量回復或多條批量回復的長度之后, 程序只需調用一次 `read` 函數, 就可以將回復的正文數據全部讀入到內存中, 而無須對這些數據做任何的處理。
在回復最末尾的 CR 和 LF 不作處理,丟棄它們。
Redis 協議的實現性能可以和二進制協議的實現性能相媲美, 并且由于 Redis 協議的簡單性, 大部分高級語言都可以輕易地實現這個協議, 這使得客戶端軟件的 bug 數量大大減少。
- 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
- 關于