為什么自己做的網(wǎng)站打開是亂碼百度seo公司整站優(yōu)化
簡述
Redis 使用 RESP 協(xié)議(Redis Serialzation Protocol)協(xié)議定義了客戶端和服務(wù)器端交互的命令、數(shù)據(jù)的編碼格式。在 Redis 2.0 版本中,RESP 協(xié)議正式稱為客戶端和服務(wù)器端的標(biāo)準(zhǔn)通信協(xié)議。從 Redis 2.0 到 Redis 5.0 ,RESP 協(xié)議都稱為 RESP 2 協(xié)議,從 Redis 6.0 開始,Redis 就采用 RESP 3 協(xié)議了。
1.客戶端和服務(wù)器端交互的內(nèi)容有哪些?
RESP 2 協(xié)議
是如何對命令和數(shù)據(jù)進(jìn)行格式編碼的呢?
我們可以把交互內(nèi)容,分成客戶端請求和服務(wù)器端響應(yīng)兩類:
- 在客戶端請求中,客戶端會(huì)給 Redis 發(fā)送命令,以及要寫入的鍵和值。
- 而在服務(wù)器響應(yīng)中,Redis 實(shí)例會(huì)返回:
- 讀取的值
- OK 標(biāo)識
- 成功寫入的元素個(gè)數(shù)
- 錯(cuò)誤信息
- 以及命令(如 Redis Cluster 中的 MOVE 命令)
其實(shí),這些交互內(nèi)容還可以再進(jìn)一步細(xì)分成 7 類:
- 命令:這就是針對不同數(shù)據(jù)類型的
命令操作
。例如對 String 類型的 SET、GET 操作,對 Hash 類型的 HSET、HGET 等,這些命令就是代表操作語義的字符串。 - 鍵:鍵值對中的鍵,可以直接用
字符串
表示。 - 單個(gè)值:對應(yīng)
String 類型
的數(shù)據(jù),數(shù)據(jù)本身可以是字符串、數(shù)值(整數(shù)或浮點(diǎn)數(shù)),布爾值(True 或 False)等。 - 集合值:對應(yīng) List、Hash、Set、Sorted Set 類型的數(shù)據(jù),不僅包含多個(gè)值,而且每個(gè)值也可以是字符串、數(shù)值或布爾值等。
- OK 回復(fù):對應(yīng)命令操作成功的結(jié)果,就是一個(gè)字符串的
OK
。 - 整數(shù)回復(fù):這里有兩種情況。
- 一種是
命令操作返回的結(jié)果是整數(shù)
,例如 LLEN 命令返回列表的長度。 - 另一種是集合命令成功操作時(shí),
實(shí)際操作元素的個(gè)數(shù)
,例如 SADD 命令返回成功添加的元素個(gè)數(shù)。
- 一種是
- 錯(cuò)誤信息:命令操作出錯(cuò)是的返回結(jié)果,包括
error
標(biāo)識,以及具體的錯(cuò)誤信息。
下面再接個(gè)三個(gè)具體的例子,幫助你進(jìn)一步掌握這些交互內(nèi)容。
第一個(gè)例子
# 成功寫入String類型數(shù)據(jù),返回 OK
127.0.0.1:6379> SET testkey testvalue
OK
這里的交互內(nèi)容就包括了命令(SET 命令)、鍵(String類型的鍵 testkey)和單個(gè)值(String 類型的 testvalue),而服務(wù)器端直接返回一個(gè) OK 回復(fù)。
第二個(gè)例子
# 成功寫入Hash類型數(shù)據(jù),返回實(shí)際寫入的集合元素個(gè)數(shù)
127.0.0.1:6379> HSET testhash a 1 b 2 c 3
(integer) 3
這里的交互內(nèi)容包括三個(gè) key-value 的 Hash 集合值(a 1 b 2 c 3),而服務(wù)器端返回整數(shù)回復(fù)(3),表示成功寫入的元素個(gè)數(shù)。
第三個(gè)例子
# 發(fā)送的命令不對,報(bào)錯(cuò),并返回報(bào)錯(cuò)信息
127.0.0.1:6379> PUT testkey2 testvalue
(error) ERR unknown command 'PUT', with args beginning with: 'testkey2', 'testvalue'
這里的交互內(nèi)容包括三個(gè) key-value 的 Hash 集合值(a 1 b 2 c 3),而服務(wù)器端返回整數(shù)回復(fù)(3),表示成功寫入的元素個(gè)數(shù)。
可以看到,這里的交互內(nèi)容包括錯(cuò)誤信息,這是因?yàn)?#xff0c;Redis 實(shí)例本身不支持 PUT 命令,所以服務(wù)器報(bào)錯(cuò) error
,并返回具體的報(bào)錯(cuò),也就是位置的命令 “PUT”。
2.RESP 2的編碼格式規(guī)范
RESP 2 協(xié)議的設(shè)計(jì)目標(biāo)是,希望 Redis 開發(fā)人員實(shí)現(xiàn)客戶端時(shí)簡單方便,這樣可以減少客戶端開發(fā)時(shí)出現(xiàn)的 Bug。而且,客戶端和服務(wù)器端交互出現(xiàn)問題時(shí),開發(fā)人員可以通過查看協(xié)議交互過程,能快速定位問題,方便調(diào)試。所以,RESP 2 協(xié)議采用了可讀性很好的文本形式進(jìn)行編碼,也就是通過字符串,來表示各種命令和數(shù)據(jù)。
不過交互內(nèi)容有多重,而且,實(shí)際傳輸?shù)拿罨驍?shù)據(jù)也會(huì)有多個(gè)。針對這兩種情況,RESP 2 協(xié)議在編碼時(shí)設(shè)計(jì)了兩個(gè)基本規(guī)范。
- 為了對不同類型的交互內(nèi)容進(jìn)行編碼,RESP 2 協(xié)議實(shí)現(xiàn)了物資編碼格式類型。同時(shí),為了區(qū)分這 5 種編碼類型,RESP 2 使用一個(gè)專門的字符,作為每種編碼類型的開頭字符。這樣,客戶端或服務(wù)器端進(jìn)行解析時(shí),可以直接通過開頭字符串知道當(dāng)前解析的編碼類型。
- RESP 2 進(jìn)行編碼時(shí),會(huì)按照單個(gè)命令或單個(gè)數(shù)據(jù)的粒度進(jìn)行編碼,并在每個(gè)編碼結(jié)果后增加一個(gè)換行符
\r\n
(有時(shí)也表示成 CRLF),表示一次編碼結(jié)束。
接下來,分別結(jié)束下這 5 中編碼類型。
2.1 簡單字符串類型(RESP Simple String)
這種類型就是用一個(gè)字符串來進(jìn)行編碼,比如,請求操作在服務(wù)器端成功執(zhí)行后的 OK 表示回復(fù),就是這種類型進(jìn)行編碼的
當(dāng)服務(wù)器端成功執(zhí)行一個(gè)操作后,返回的 OK 標(biāo)識就可以編碼如下:
+OK\r\n
2.2 長字符串類型(RESP Bulk String)
這種類型是用一個(gè)二進(jìn)制安全的字符串
來進(jìn)行編碼。
這里的二進(jìn)制安全,是相對于 C 語言中對字符串的處理方式來說的。 Redis 在解析字符串時(shí),不會(huì)像 C 語言那樣,使用 \0
來判定一個(gè)字符串的結(jié)尾,Redis 會(huì)把 \0
解析成正常的 0 字符,并使用額外的屬性值表示字符串的長度。
對于 Redis\0Cluster\0
這個(gè)字符串來說,C 語言會(huì)解析為 “Redis”,而 Redis 會(huì)解析為 “Redis Cluster”,并用 len 屬性字符串的真實(shí)長度是 14 字節(jié),如下圖所示:
這樣一來,字符串中即使存儲了 \0
字符,也不會(huì)導(dǎo)致 Redis 解析到 \0
時(shí),就認(rèn)為字符串結(jié)束了從而停止,這就保證了數(shù)據(jù)的安全性。和長字符串類相比,簡單字符串就是非二進(jìn)制安全的。
長字符串類型最大可以達(dá)到 512 MB,所以可以對很大的數(shù)據(jù)量進(jìn)行編碼,正好可以滿足鍵值對本身的數(shù)據(jù)量需求,所以 RESP 2 就用這種類型對交互內(nèi)容中的鍵或值進(jìn)行編碼,并且使用 $
字符作為開頭字符, $
字符后面會(huì)緊跟一個(gè)數(shù)字,這個(gè)數(shù)字表示字符串的實(shí)際長度。
例如,我們使用 GET 命令讀取一個(gè)鍵(假設(shè)為 testkey)的值(假設(shè)值為 testvalue)時(shí),服務(wù)器端返回的 String 值編碼如下,其中 $
字符后的 9 ,表示數(shù)據(jù)長度為 9 個(gè)字符。
$9 testvalue\r\n
2.3 整數(shù)類型(RESP Integer)
這種數(shù)據(jù)類型也是一個(gè)字符串,但表示的是一個(gè)有符號 64 位整數(shù)。為了和包含數(shù)字的簡答字符串類型區(qū)分開,整數(shù)類型使用 :
字符作為開頭,可以用于對服務(wù)器端返回的整數(shù)回復(fù)進(jìn)行編碼。
例如,上面的第二個(gè)例子中,我們使用 HSET 命令設(shè)置了 testhash 的三個(gè)元素,服務(wù)器端實(shí)際返回的編碼結(jié)果如下:
:3\r\n
2.4 錯(cuò)誤類型(RESP Errors)
它是一個(gè)字符串,包括了錯(cuò)誤類型和具體的錯(cuò)誤信息。Redis 服務(wù)器端報(bào)錯(cuò)響應(yīng)就是用這種類型進(jìn)行編碼的。RESP 2 使用 -
字符作為它的開頭字符。
例如,上面的第三個(gè)例子中,我們在 redis-cli 執(zhí)行 PUT testkey2 testvalue
命令報(bào)錯(cuò),服務(wù)器端實(shí)際返回給客戶端的報(bào)錯(cuò)編碼結(jié)果如下:
-ERR unknown command 'PUT', with args beginning with: 'testkey2', 'testvalue'
其中 ERR
就是報(bào)錯(cuò)類型,表示一個(gè)統(tǒng)一錯(cuò)誤,ERR 后面的文字內(nèi)容就是具體的報(bào)錯(cuò)信息。
2.5 數(shù)組編碼類型(RESP Arrays)
這是一個(gè)包含多個(gè)元素的數(shù)組,之前,元素的類型可以是剛剛介紹的 4 種編碼類型。
在客戶端發(fā)送請求和服務(wù)器端返回結(jié)果時(shí),數(shù)組編碼類型都能用的上。客戶端在發(fā)送請求操作時(shí),一般會(huì)同時(shí)包括命令和要操作的數(shù)據(jù)。而數(shù)組類型包含了多個(gè)元素,所以,就適合用來對發(fā)送的命令和數(shù)據(jù)進(jìn)行編碼。為了和其他類型區(qū)分開,RESP 2 使用 *
字符作為它的開頭字符。
例如,我們執(zhí)行 GET testkey
,此時(shí),客戶端發(fā)送給服務(wù)器端的命令的編碼結(jié)果就是使用數(shù)組類型編碼的,如下所示:
*2\r\n$3\r\nGET\r\n$7\r\ntestkey\r\n
- 其中,第一個(gè)
*
字符標(biāo)識當(dāng)前是數(shù)組類型的編碼結(jié)果,2 標(biāo)識該數(shù)組有 2 個(gè)元素,分別對應(yīng)命令 GET 和 鍵 testkey。 - 命令 GET 和 鍵 testkey,都是使用長字符串類型編碼的,所以用
$
字符加字符串長度來表示。
類型的,當(dāng)服務(wù)器端返回包含多個(gè)元素的集合類型數(shù)據(jù)時(shí),也會(huì)用 *
字符和元素個(gè)數(shù)作為標(biāo)識,并用長字符串類型對返回的集合元素進(jìn)行編碼。
RESP 2 協(xié)議的 5 種編碼類型和相應(yīng)的開頭字符,我在下表做了小結(jié)。
編碼類型 | 簡單字符串 | 長字符串 | 整數(shù) | 錯(cuò)誤 | 數(shù)組 |
---|---|---|---|---|---|
開頭第一個(gè)字符 | + | $ | : | - | * |
3.RESP 2 的不足和 RESP 3 的改進(jìn)
雖然 RESP 2 協(xié)議提供了 5 種編碼類型,看起來很豐富,其實(shí)是不夠的。比較,基本數(shù)據(jù)類型還包括很多,例如浮點(diǎn)數(shù)、布爾值等。編碼類型偏少,會(huì)帶來兩個(gè)問題。
- 一方面,在值的基本數(shù)據(jù)類型方面,RESP 2 只能區(qū)分字符串和證書,對于其他的數(shù)據(jù)類型,客戶端使用 RESP 2 協(xié)議時(shí),就需要進(jìn)行額外的轉(zhuǎn)換操作。例如,當(dāng)一個(gè)浮點(diǎn)數(shù)用字符串表示時(shí),客戶端需要將字符串中的值和實(shí)際數(shù)字值比較,判斷是否為數(shù)字值,然后再將字符串轉(zhuǎn)換成實(shí)際的浮點(diǎn)數(shù)。
- 另一方面,RESP 2 用數(shù)組類別編碼表示所有的集合類型,但是,Redis 的集合類型包括了 List、Hash、Set 和 Sorted Set。當(dāng)客戶端接收到數(shù)組類型編碼的結(jié)果時(shí),還需要根據(jù) 調(diào)用的命令操作接口,來判斷返回的數(shù)組究竟是哪一種集合類型。
假設(shè)一個(gè) Hash 類型的鍵是 testhash,集合元素分別為 a:1、b:2、c:3。同時(shí),有一個(gè) Sorted Set 類型的鍵 testzset,集合的元素分別是 a、b、c,它們的分?jǐn)?shù)分別是 1、2、3.我們在 redis-cli 客戶端中讀取它們返回的結(jié)果時(shí),返回的形式都是一個(gè)數(shù)組:
127.0.0.1:6379> HGETALL testhash
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"127.0.0.1:6379> ZRANGE testzset 0 3 withscores
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"
為了在客戶端按照 Hash 和 Sorted Set 兩種類型處理代碼中返回的數(shù)據(jù),客戶端還需要根據(jù)發(fā)送的命令操作 HGETALL 和 ZRANGE ,來把這兩個(gè)編碼的數(shù)組結(jié)果轉(zhuǎn)換成相應(yīng)的 Hash 集合和有序集合,增加了客戶端額外的開銷。
從 Redis 6.0 版本開始,RESP 3 協(xié)議增加了對多種數(shù)據(jù)類型的支持,包括:
- 空值
- 浮點(diǎn)數(shù)
- 布爾值
- 有序的字典集合
- 無需的集合
- 等
RESP 3 也是通過不同的開頭字符區(qū)分不同的數(shù)據(jù)類型,例如,當(dāng)開頭第一個(gè)字符是 ,
,就表示接下來的結(jié)果是浮點(diǎn)數(shù)。這樣一來,客戶端就不用再通過額外的字符串對比,來實(shí)現(xiàn)數(shù)據(jù)轉(zhuǎn)換操作了,提升了客戶端的效率。
4.小結(jié)
RESP 2 協(xié)議題了 5 中類型的編碼格式,包括簡單字符串類型、長字符串類型、整數(shù)類型、錯(cuò)誤類型和數(shù)組類型。為了區(qū)分這 5 種類型,RESP 2 協(xié)議使用了 5 種不同的開頭字符作為這 5 種類型編碼結(jié)果的第一個(gè)字符,分別是 +
、$
、:
、-
、*
。
RESP 2 協(xié)議是文本形式的協(xié)議,實(shí)現(xiàn)簡單,可以減少客戶端開發(fā)出現(xiàn)的 Bug,而且可讀性強(qiáng),便于開發(fā)調(diào)試。當(dāng)你需要定制化 Redis 客戶端時(shí),就需要了解和掌握 RESP 2 協(xié)議。
RESP 2 協(xié)議的一個(gè)不足就是支持的類型偏少,所以 Redis 6.0 版本使用了 RESP 3 協(xié)議。和 RESP 2 協(xié)議相比,RESP 3 協(xié)議增加了對浮點(diǎn)數(shù)、布爾類型、有序字典集合、無序集合等多種類型數(shù)據(jù)的支持。不過,這里,有個(gè)地方需要你注意,Redis 6.0 只支持 RESP 3,對 RESP 2 協(xié)議不兼容,所以如果你使用 Redis 6.0 版本,需要確認(rèn)客戶端已經(jīng)支持了 RESP 3 協(xié)議,否則將無法使用 Redis 6.0。
如果你想查看服務(wù)器端返回?cái)?shù)據(jù)的 RESP 2 編碼結(jié)果,就可以使用 telnet 命令和 Redis 實(shí)例連接,執(zhí)行如下命令:
telnet 實(shí)例IP 實(shí)例端口
接著,給實(shí)例發(fā)送命令,這樣就能看到 RESP 2 協(xié)議編碼后的返回結(jié)果了。當(dāng)然,你也可以在 telnet 中,想 Redis 實(shí)例發(fā)送 RESP 2 協(xié)議編寫的命令,實(shí)例同樣能處理。