所謂 協(xié)議?,本質(zhì)是一種約定,需要使用者雙方來準(zhǔn)守,常見于 C/S 通信模式?中,比如在瀏覽器中最常用的 HTTP 應(yīng)用層通信協(xié)議。
(資料圖)
通信兩端需要某種約定,才能保持正常通信。一端通過約定的格式發(fā)送數(shù)據(jù),另一端通過約定的格式解析數(shù)據(jù),這種約定,取了一個好聽的名字 ---- 協(xié)議。
典型的 HTTP 協(xié)議,最本質(zhì)的原理也是如此。redis 作為一款高性能內(nèi)存組件,要盡可能將精力花在數(shù)據(jù)的組織形式上,因此,沒有采用開源的一些復(fù)雜協(xié)議,比如 HTTP,而是簡單的自定義一套應(yīng)用層通信協(xié)議。
Redis 客戶端 - 服務(wù)端通信協(xié)議稱之為 RESP 協(xié)議,全稱叫 Redis Serialization Protocol,即 redis 序列化協(xié)議。人類易讀,相當(dāng)精巧!
RESP 協(xié)議特點人類易讀簡單實現(xiàn)快速解析RESP 是一種二進(jìn)制安全協(xié)議,因為編碼后的每一個字符串都有前綴來表明其長度,通過長度就能知道數(shù)據(jù)邊界,從而避免越界訪問的問題。
值得注意的是,RESP 協(xié)議只用于 客戶端 - 服務(wù)端? 之間的交流,redis cluster? 各節(jié)點之間采用不同的二進(jìn)制協(xié)議(采用 Gossip 協(xié)議)進(jìn)行交流。
網(wǎng)絡(luò)通信我們知道,在傳統(tǒng)計算機(jī)網(wǎng)絡(luò)模型中,傳輸層?(TCP / UDP)的上一層便是應(yīng)用層。應(yīng)用層協(xié)議一般專注于數(shù)據(jù)的編解碼等約定,比如經(jīng)典的 HTTP 協(xié)議。
RESP 協(xié)議本質(zhì)和 HTTP 是一個級別,都屬于應(yīng)用層協(xié)議。
在 redis 中,傳輸層協(xié)議使用的是 TCP,服務(wù)端從 TCP socket 緩沖區(qū)中讀取數(shù)據(jù),然后經(jīng)過 RESP 協(xié)議解碼得到我們的指令。
而寫入數(shù)據(jù)則是相反,服務(wù)器先將響應(yīng)數(shù)據(jù)使用 RESP 編碼,然后將編碼后的數(shù)據(jù)寫入 TCP Socket 緩沖區(qū)發(fā)送給客戶端。
協(xié)議格式在 RESP 協(xié)議中,第一個字節(jié)決定了具體數(shù)據(jù)類型:
簡單字符串?:Simple Strings,第一個字節(jié)響應(yīng)+錯誤?:Errors,第一個字節(jié)響應(yīng)-整型?:Integers,第一個字節(jié)響應(yīng):批量字符串?:Bulk Strings,第一個字節(jié)響應(yīng)$數(shù)組?:Arrays,第一個字節(jié)響應(yīng)*我們來看看一具體的例子,我們一條正常指令 PSETEX test_redisson_batch_key8 120000 test_redisson_batch_key=>value:8,經(jīng) RESP 協(xié)議編碼后長這樣:
*4$6PSETEX$24test_redisson_batch_key8$6120000$32test_redisson_batch_key=>value:8
值得注意的是,在 RESP 協(xié)議中的每一部分都是以 \R\N 結(jié)尾。
簡單字符串:Simple Strings?。以 + 為前綴的響應(yīng)數(shù)據(jù),例如:
"+OK\r\n"
以上是字符串 OK,被編碼后的格式,總共 5 字節(jié)。
這是一種非二進(jìn)制安全的編碼方式,因為, 我們無法確切的知道字符串的長度,只能以 \r\n? 來判斷,所以編碼的字符串中,不能包含 \r? 或者 \n 字符。
當(dāng)然,如果你想要二進(jìn)制安全字符串,可以選擇 Bulk Strings 方式,我們后面會介紹。
錯誤Errors?。RESP 提供了錯誤類型,和簡單字符串非常類似,不過是以 - 開頭,基本格式如下:
"-Error message\r\n"
與簡單字符串真正不同的之處在于客戶端的處理上,對 - 開頭的響應(yīng),客戶端直接以異常情況處理。
我們來看一個是實際例子,當(dāng)我們的指令或者參數(shù)錯誤,redis 服務(wù)端會直接返回異常,如下:
-ERR unknown command "helloworld"-WRONGTYPE Operation against a key holding the wrong kind of value
- 后面的第一個單詞,直到第一個空格或換行符,表示返回的錯誤類型。這只是 Redis 使用的一個慣例,并不是 RESP 錯誤格式的一部分。
ERR? 是通用錯誤,而 WRONGTYPE 是一個更具體的錯誤,表示客戶端嘗試執(zhí)行錯誤的數(shù)據(jù)類型,通常作為一個錯誤的前綴,它允許客戶端在不檢查確切錯誤消息的情況下理解服務(wù)器返回的錯誤類型。
我們在客戶端實現(xiàn)的時候,可以針對不同的錯誤返回不同類型的異常,或者提供一種捕獲錯誤的通用方法,比如,直接將錯誤名稱作為字符串提供給調(diào)用者。
然而,這樣的特性不應(yīng)該被認(rèn)為是至關(guān)重要的,因為它很少有用,而且有限的客戶端實現(xiàn)可能只是返回一個通用的錯誤條件,比如 false。
整型RESP Integers?。表示響應(yīng)的是整數(shù),以 :? 開頭,比如 :0\r\n? 和 :1000\r\n。
redis 中很多命令的響應(yīng)都是整數(shù),比如 ==INCR==, ==LLEN==, 及 ==LASTSAVE==。另外,響應(yīng)值是一個 64 位的整數(shù)。
當(dāng)然,整形也可以表示 true? 或者 false? 語義,比如 EXISTS? 或者 SISMEMBER 返回 1 表示 true,0 表示 false。
還有其他命令,比如 SADD?, SREM?, 和 SETNX 返回 1 表示實際執(zhí)行,反之為 0。
以下命令會響應(yīng)結(jié)果為整數(shù):
SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD.批量字符串
RESP Bulk Strings。批量回復(fù),是一個大小在 512 Mb 的二進(jìn)制安全字符串,被編碼成:
以$? 開頭,緊跟一個整數(shù)代表回復(fù)字符串的大小,以\r\n 結(jié)束隨后是 實際的字符串?dāng)?shù)據(jù)最后以 "\r\n" 結(jié)尾比如 hello 被編碼為:
"$5\r\nhello\r\n"
一個空字符串被編碼為:
"$0\r\n\r\n"
另外,對于一些不存在的 value 可以返回 -1 表示 null,也被稱為 NULL 批量回復(fù)。
客戶端庫進(jìn)行實現(xiàn)時,可以將此 -1 處理成空對象,比如 Ruby 將返回 nil?,而 C 則返回 NULL
數(shù)組RESP Arrays?。數(shù)組,對于響應(yīng)的集合元素,比如 LRANGE 命令,返回的是元素列表,也就是數(shù)組形式。
編碼格式:
以*? 開頭表示,緊接著是一個整數(shù),表示數(shù)組元素個數(shù),并以\r\n 結(jié)尾。數(shù)組的每個元素的都是 RESP 提供的類型。例如,空數(shù)組:
"*0\r\n"
包含 "hello" 和 "world" 的響應(yīng)數(shù)組(也叫多批量字符串,每一個元素是批量字符串):
"*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n"
3個整數(shù)的數(shù)組是這樣的:
"*3\r\n:1\r\n:2\r\n:3\r\n"
另外,數(shù)組也可以混合類型的。
比如以下5個元素中,有4個是整形,一個是 批量字符串:
*5\r\n:1\r\n:2\r\n:3\r\n:4\r\n$5\r\nhello\r\n
... ( ? 以上結(jié)果為了更加清晰的展示,進(jìn)行了手動換行。
當(dāng)然,也同樣支持空數(shù)組(一般情況下,更習(xí)慣使用 Null Bulk String,但由于歷史原因,兩種方式都存在)
例如,當(dāng)使用 BLPOP 命令 timeout 時,將返回空數(shù)組:
"*-1\r\n"
當(dāng) redis 返回 NULL 數(shù)組時,客戶端實現(xiàn)庫最好也返回一個空對象,有助于區(qū)別到底是 empty 數(shù)組還是產(chǎn)生了其他問題
內(nèi)置數(shù)組,如下:
*2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n+Hello\r\n-World\r\n
... ( ?? 同樣,為了展示更加清晰,進(jìn)行了手動換行
該響應(yīng)結(jié)果表示,外層數(shù)組包含兩個元素,每個元素都是數(shù)組。第一個子數(shù)組包含 3 個整型數(shù)字,第二個子數(shù)組包含 1 個簡單字符串和一個錯誤。
數(shù)組中的空元素Null elements in Arrays?。數(shù)組出現(xiàn) NULL? 元素,這種場景也是很常見的,比如我們使用 MGET 批量獲取 key,當(dāng)其中一些 key 不存在時,返回的就是 NULL 元素。
例如響應(yīng)結(jié)果:
*3\r\n$5\r\nhello\r\n$-1\r\n$5\r\nworld\r\n
如上響應(yīng)編碼,客戶端庫解析之后應(yīng)該是這樣:
["hello",nil,"world"]多命令和管道
Multiple commands and pipelining。多命令和管道,redis 中提供了一次發(fā)送多條指令的操作,比如 ==MGET==、==MSET==、==pipline==,服務(wù)端接收并處理后一次性響應(yīng)。
這種形式就是上面提到的 數(shù)組?,數(shù)組里面可以是 批量字符串、整數(shù)?,甚至是 NULL 都可以。
我們先使用 telnet 看看原生響應(yīng)結(jié)果:
[root@VM-20-17-centos ~]# telnet 127.0.0.1 6379MGET key1 key2 key3*3$6value1$6value2$-1
我們再使用 redis-cli 看看被客戶端解碼后的結(jié)果:
127.0.0.1:6379> MGET key1 key2 key31) "value1"2) "value2"3) (nil)內(nèi)聯(lián)命令
Inline commands?。是這樣的,一般情況下我們和 redis 服務(wù)端通信都需要一個客戶端(比如redis-cli),因為雙方都遵循 RESP 協(xié)議,數(shù)據(jù)可以正常編碼和解析。
考慮這樣一種情況,當(dāng)你沒有任何客戶端工具可用時,是否也能正常和服務(wù)端通信呢?比如 telnet。
也是可以的?,redis 正式通過 內(nèi)聯(lián)指令 支持的,咱們來看看例子:
例1,通過 RESP 協(xié)議發(fā)送指令(由于沒有客戶端,這里我們手動編碼):
[root@VM-20-17-centos ~]# telnet 127.0.0.1 6379Trying 127.0.0.1...Connected to 127.0.0.1.Escape character is "^]".*3 $3set$4 key1$5 world+OK
我們正常的指令是 set key1 word?,經(jīng)過 RESP 編碼之后 *3\r\n$3\r\nset\r\n$4\r\nkey1\r\r$5\r\nworld,redis 服務(wù)端解碼之后便可得到正常指令。
例2,通過內(nèi)聯(lián)操作發(fā)送指令:
[root@VM-20-17-centos ~]# telnet 127.0.0.1 6379Trying 127.0.0.1...Connected to 127.0.0.1.Escape character is "^]".exists key1:1get key1$11set key1 hello +OKget key1$5hello
這里我們直接發(fā)送 內(nèi)聯(lián)指令? 比如 EXISTS key1、GET key1、SET key1 hello 等,無需 RESP 協(xié)議編碼,服務(wù)端仍可正常處理。
值得注意的是,因為沒有了統(tǒng)一請求協(xié)議中的 *? 項來聲明參數(shù)的數(shù)量,所以在 telnet 會話輸入命令的時候,必須使用空格來分割各個參數(shù),服務(wù)器在接收到數(shù)據(jù)之后,會按空格對用戶的輸入進(jìn)行解析,并獲取其中的命令參數(shù)。
高性能 Redis 協(xié)議解析器High performance parser for the Redis protocol,即,高性能 Redis 協(xié)議分析器。
RESP 是一款人類易讀、簡單實現(xiàn)的通信協(xié)議,它可以類似于二進(jìn)制協(xié)議的性能實現(xiàn)。
RESP 使用前綴長度來傳輸批量數(shù)據(jù),因此不需要像 JSON 那樣,為了查找某個特殊字符而掃描整個數(shù)據(jù),也無須對發(fā)送至服務(wù)器的數(shù)據(jù)進(jìn)行轉(zhuǎn)義。
程序可以在對協(xié)議文本中的各個字符進(jìn)行處理的同時, 查找 CR 字符, 并計算出批量回復(fù)或多條批量回復(fù)的長度, 就像這樣:
#include 得到了批量回復(fù)或多條批量回復(fù)的長度之后, 程序只需調(diào)用一次 read 函數(shù), 就可以將回復(fù)的正文數(shù)據(jù)全部讀入到內(nèi)存中, 而無須對這些數(shù)據(jù)做任何的處理。 在回復(fù)最末尾的 CR 和 LF 不作處理,丟棄它們。 Redis 協(xié)議的實現(xiàn)性能可以和二進(jìn)制協(xié)議的實現(xiàn)性能相媲美,并且由于 Redis 協(xié)議的簡單性,大部分高級語言都可以輕易地實現(xiàn)這個協(xié)議,這使得客戶端軟件的 bug 數(shù)量大大減少。 協(xié)議,本質(zhì)是雙方對數(shù)據(jù)處理的一種約定,redis 提供了簡單易實現(xiàn)的 RESP 協(xié)議,你也看到了,確實相當(dāng)簡單,按照這種協(xié)議約定,你也能很快寫出一個 redis 客戶端。 協(xié)議工作的一般流程是: redis 服務(wù)端除了支持 RESP? 協(xié)議,還支持內(nèi)聯(lián)指令,也就是我們原始的命令,這樣一來就不需要編碼解碼的過程了。 標(biāo)簽:
RESP
網(wǎng)絡(luò)通信
網(wǎng)絡(luò)協(xié)議
- 環(huán)球訊息:Redis 通信協(xié)議(RESP),最簡單的應(yīng)用層協(xié)議,沒有之一
- 【世界播資訊】網(wǎng)絡(luò)結(jié)構(gòu)這么多,這怕是爭議最大的一個
- 全球速讀:專家視點:是時候轉(zhuǎn)向無線了嗎?
- 今日報丨多機(jī)房該如何部署?數(shù)據(jù)如何同步?
- 天天速看:圖文并茂,詳解TCP和UDP協(xié)議的原理和區(qū)別
- 全球資訊:無人機(jī)技術(shù)和用例的影響
- 環(huán)球頭條:節(jié)能網(wǎng)絡(luò)如何支持可持續(xù)發(fā)展
- 亞馬遜發(fā)布新一代6英寸Kindle入門版電子書 號稱史上最輕巧便攜
- 全球新動態(tài):規(guī)劃 OTA 更新需要了解的三件事
- 關(guān)注:如何選擇以太網(wǎng)電纜