所謂 協議?,本質是一種約定,需要使用者雙方來準守,常見于 C/S 通信模式?中,比如在瀏覽器中最常用的 HTTP 應用層通信協議。
(資料圖片)
通信兩端需要某種約定,才能保持正常通信。一端通過約定的格式發送數據,另一端通過約定的格式解析數據,這種約定,取了一個好聽的名字 ---- 協議。
典型的 HTTP 協議,最本質的原理也是如此。redis 作為一款高性能內存組件,要盡可能將精力花在數據的組織形式上,因此,沒有采用開源的一些復雜協議,比如 HTTP,而是簡單的自定義一套應用層通信協議。
Redis 客戶端 - 服務端通信協議稱之為 RESP 協議,全稱叫 Redis Serialization Protocol,即 redis 序列化協議。人類易讀,相當精巧!
RESP 協議特點人類易讀簡單實現快速解析RESP 是一種二進制安全協議,因為編碼后的每一個字符串都有前綴來表明其長度,通過長度就能知道數據邊界,從而避免越界訪問的問題。
值得注意的是,RESP 協議只用于 客戶端 - 服務端? 之間的交流,redis cluster? 各節點之間采用不同的二進制協議(采用 Gossip 協議)進行交流。
網絡通信我們知道,在傳統計算機網絡模型中,傳輸層?(TCP / UDP)的上一層便是應用層。應用層協議一般專注于數據的編解碼等約定,比如經典的 HTTP 協議。
RESP 協議本質和 HTTP 是一個級別,都屬于應用層協議。
在 redis 中,傳輸層協議使用的是 TCP,服務端從 TCP socket 緩沖區中讀取數據,然后經過 RESP 協議解碼得到我們的指令。
而寫入數據則是相反,服務器先將響應數據使用 RESP 編碼,然后將編碼后的數據寫入 TCP Socket 緩沖區發送給客戶端。
協議格式在 RESP 協議中,第一個字節決定了具體數據類型:
簡單字符串?:Simple Strings,第一個字節響應+錯誤?:Errors,第一個字節響應-整型?:Integers,第一個字節響應:批量字符串?:Bulk Strings,第一個字節響應$數組?:Arrays,第一個字節響應*我們來看看一具體的例子,我們一條正常指令 PSETEX test_redisson_batch_key8 120000 test_redisson_batch_key=>value:8,經 RESP 協議編碼后長這樣:
*4$6PSETEX$24test_redisson_batch_key8$6120000$32test_redisson_batch_key=>value:8
值得注意的是,在 RESP 協議中的每一部分都是以 \R\N 結尾。
簡單字符串:Simple Strings?。以 + 為前綴的響應數據,例如:
"+OK\r\n"
以上是字符串 OK,被編碼后的格式,總共 5 字節。
這是一種非二進制安全的編碼方式,因為, 我們無法確切的知道字符串的長度,只能以 \r\n? 來判斷,所以編碼的字符串中,不能包含 \r? 或者 \n 字符。
當然,如果你想要二進制安全字符串,可以選擇 Bulk Strings 方式,我們后面會介紹。
錯誤Errors?。RESP 提供了錯誤類型,和簡單字符串非常類似,不過是以 - 開頭,基本格式如下:
"-Error message\r\n"
與簡單字符串真正不同的之處在于客戶端的處理上,對 - 開頭的響應,客戶端直接以異常情況處理。
我們來看一個是實際例子,當我們的指令或者參數錯誤,redis 服務端會直接返回異常,如下:
-ERR unknown command "helloworld"-WRONGTYPE Operation against a key holding the wrong kind of value
- 后面的第一個單詞,直到第一個空格或換行符,表示返回的錯誤類型。這只是 Redis 使用的一個慣例,并不是 RESP 錯誤格式的一部分。
ERR? 是通用錯誤,而 WRONGTYPE 是一個更具體的錯誤,表示客戶端嘗試執行錯誤的數據類型,通常作為一個錯誤的前綴,它允許客戶端在不檢查確切錯誤消息的情況下理解服務器返回的錯誤類型。
我們在客戶端實現的時候,可以針對不同的錯誤返回不同類型的異常,或者提供一種捕獲錯誤的通用方法,比如,直接將錯誤名稱作為字符串提供給調用者。
然而,這樣的特性不應該被認為是至關重要的,因為它很少有用,而且有限的客戶端實現可能只是返回一個通用的錯誤條件,比如 false。
整型RESP Integers?。表示響應的是整數,以 :? 開頭,比如 :0\r\n? 和 :1000\r\n。
redis 中很多命令的響應都是整數,比如 ==INCR==, ==LLEN==, 及 ==LASTSAVE==。另外,響應值是一個 64 位的整數。
當然,整形也可以表示 true? 或者 false? 語義,比如 EXISTS? 或者 SISMEMBER 返回 1 表示 true,0 表示 false。
還有其他命令,比如 SADD?, SREM?, 和 SETNX 返回 1 表示實際執行,反之為 0。
以下命令會響應結果為整數:
SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD.批量字符串
RESP Bulk Strings。批量回復,是一個大小在 512 Mb 的二進制安全字符串,被編碼成:
以$? 開頭,緊跟一個整數代表回復字符串的大小,以\r\n 結束隨后是 實際的字符串數據最后以 "\r\n" 結尾比如 hello 被編碼為:
"$5\r\nhello\r\n"
一個空字符串被編碼為:
"$0\r\n\r\n"
另外,對于一些不存在的 value 可以返回 -1 表示 null,也被稱為 NULL 批量回復。
客戶端庫進行實現時,可以將此 -1 處理成空對象,比如 Ruby 將返回 nil?,而 C 則返回 NULL
數組RESP Arrays?。數組,對于響應的集合元素,比如 LRANGE 命令,返回的是元素列表,也就是數組形式。
編碼格式:
以*? 開頭表示,緊接著是一個整數,表示數組元素個數,并以\r\n 結尾。數組的每個元素的都是 RESP 提供的類型。例如,空數組:
"*0\r\n"
包含 "hello" 和 "world" 的響應數組(也叫多批量字符串,每一個元素是批量字符串):
"*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n"
3個整數的數組是這樣的:
"*3\r\n:1\r\n:2\r\n:3\r\n"
另外,數組也可以混合類型的。
比如以下5個元素中,有4個是整形,一個是 批量字符串:
*5\r\n:1\r\n:2\r\n:3\r\n:4\r\n$5\r\nhello\r\n
... ( ? 以上結果為了更加清晰的展示,進行了手動換行。
當然,也同樣支持空數組(一般情況下,更習慣使用 Null Bulk String,但由于歷史原因,兩種方式都存在)
例如,當使用 BLPOP 命令 timeout 時,將返回空數組:
"*-1\r\n"
當 redis 返回 NULL 數組時,客戶端實現庫最好也返回一個空對象,有助于區別到底是 empty 數組還是產生了其他問題
內置數組,如下:
*2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n+Hello\r\n-World\r\n
... ( ?? 同樣,為了展示更加清晰,進行了手動換行
該響應結果表示,外層數組包含兩個元素,每個元素都是數組。第一個子數組包含 3 個整型數字,第二個子數組包含 1 個簡單字符串和一個錯誤。
數組中的空元素Null elements in Arrays?。數組出現 NULL? 元素,這種場景也是很常見的,比如我們使用 MGET 批量獲取 key,當其中一些 key 不存在時,返回的就是 NULL 元素。
例如響應結果:
*3\r\n$5\r\nhello\r\n$-1\r\n$5\r\nworld\r\n
如上響應編碼,客戶端庫解析之后應該是這樣:
["hello",nil,"world"]多命令和管道
Multiple commands and pipelining。多命令和管道,redis 中提供了一次發送多條指令的操作,比如 ==MGET==、==MSET==、==pipline==,服務端接收并處理后一次性響應。
這種形式就是上面提到的 數組?,數組里面可以是 批量字符串、整數?,甚至是 NULL 都可以。
我們先使用 telnet 看看原生響應結果:
[root@VM-20-17-centos ~]# telnet 127.0.0.1 6379MGET key1 key2 key3*3$6value1$6value2$-1
我們再使用 redis-cli 看看被客戶端解碼后的結果:
127.0.0.1:6379> MGET key1 key2 key31) "value1"2) "value2"3) (nil)內聯命令
Inline commands?。是這樣的,一般情況下我們和 redis 服務端通信都需要一個客戶端(比如redis-cli),因為雙方都遵循 RESP 協議,數據可以正常編碼和解析。
考慮這樣一種情況,當你沒有任何客戶端工具可用時,是否也能正常和服務端通信呢?比如 telnet。
也是可以的?,redis 正式通過 內聯指令 支持的,咱們來看看例子:
例1,通過 RESP 協議發送指令(由于沒有客戶端,這里我們手動編碼):
[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?,經過 RESP 編碼之后 *3\r\n$3\r\nset\r\n$4\r\nkey1\r\r$5\r\nworld,redis 服務端解碼之后便可得到正常指令。
例2,通過內聯操作發送指令:
[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
這里我們直接發送 內聯指令? 比如 EXISTS key1、GET key1、SET key1 hello 等,無需 RESP 協議編碼,服務端仍可正常處理。
值得注意的是,因為沒有了統一請求協議中的 *? 項來聲明參數的數量,所以在 telnet 會話輸入命令的時候,必須使用空格來分割各個參數,服務器在接收到數據之后,會按空格對用戶的輸入進行解析,并獲取其中的命令參數。
高性能 Redis 協議解析器High performance parser for the Redis protocol,即,高性能 Redis 協議分析器。
RESP 是一款人類易讀、簡單實現的通信協議,它可以類似于二進制協議的性能實現。
RESP 使用前綴長度來傳輸批量數據,因此不需要像 JSON 那樣,為了查找某個特殊字符而掃描整個數據,也無須對發送至服務器的數據進行轉義。
程序可以在對協議文本中的各個字符進行處理的同時, 查找 CR 字符, 并計算出批量回復或多條批量回復的長度, 就像這樣:
#include 得到了批量回復或多條批量回復的長度之后, 程序只需調用一次 read 函數, 就可以將回復的正文數據全部讀入到內存中, 而無須對這些數據做任何的處理。 在回復最末尾的 CR 和 LF 不作處理,丟棄它們。 Redis 協議的實現性能可以和二進制協議的實現性能相媲美,并且由于 Redis 協議的簡單性,大部分高級語言都可以輕易地實現這個協議,這使得客戶端軟件的 bug 數量大大減少。 協議,本質是雙方對數據處理的一種約定,redis 提供了簡單易實現的 RESP 協議,你也看到了,確實相當簡單,按照這種協議約定,你也能很快寫出一個 redis 客戶端。 協議工作的一般流程是: redis 服務端除了支持 RESP? 協議,還支持內聯指令,也就是我們原始的命令,這樣一來就不需要編碼解碼的過程了。
X 關閉
X 關閉
- 15G資費不大降!三大運營商誰提供的5G網速最快?中國信通院給出答案
- 2聯想拯救者Y70發布最新預告:售價2970元起 迄今最便宜的驍龍8+旗艦
- 3亞馬遜開始大規模推廣掌紋支付技術 顧客可使用“揮手付”結賬
- 4現代和起亞上半年出口20萬輛新能源汽車同比增長30.6%
- 5如何讓居民5分鐘使用到各種設施?沙特“線性城市”來了
- 6AMD實現連續8個季度的增長 季度營收首次突破60億美元利潤更是翻倍
- 7轉轉集團發布2022年二季度手機行情報告:二手市場“飄香”
- 8充電寶100Wh等于多少毫安?鐵路旅客禁止、限制攜帶和托運物品目錄
- 9好消息!京東與騰訊續簽三年戰略合作協議 加強技術創新與供應鏈服務
- 10名創優品擬通過香港IPO全球發售4100萬股 全球發售所得款項有什么用處?