redis?protocol通信協(xié)議及使用詳解
簡介
redis是一個(gè)非常優(yōu)秀的軟件,它可以用作內(nèi)存數(shù)據(jù)庫或者緩存。因?yàn)樗膬?yōu)秀性能,redis被應(yīng)用在很多場合中。
redis是一個(gè)客戶端和服務(wù)器端的模式,客戶端和服務(wù)器端是通過TCP協(xié)議進(jìn)行連接的,客戶端將請求數(shù)據(jù)發(fā)送到服務(wù)器端,服務(wù)器端將請求返回給客戶端。這樣一個(gè)請求流程就完成了。
當(dāng)然在最開始的時(shí)候,因?yàn)橛玫娜撕苌?,系統(tǒng)還不夠穩(wěn)定,通過TCP協(xié)議傳輸?shù)臄?shù)據(jù)不規(guī)范的。但是當(dāng)用的人越來越多,尤其是希望開發(fā)適用于不同語言和平臺(tái)的redis客戶端的時(shí)候,就要考慮到兼容性的問題了。
這時(shí)候客戶端和服務(wù)器端就需要一個(gè)統(tǒng)一的交互協(xié)議,對于redis來說這個(gè)通用的交互協(xié)議就叫做Redis serialization protocol(RESP)。
RESP是在Redis 1.2版本中引入的,并在Redis 2.0中成為了與 Redis 服務(wù)器通信的標(biāo)準(zhǔn)方式。
這就是說,從Redis 2.0之后,就可以基于redis protocol協(xié)議開發(fā)出自己的redis客戶端了。
redis的高級(jí)用法
一般來說,redis的客戶端和服務(wù)器端組成的是一個(gè)請求-響應(yīng)的模式,也就是說客戶端向服務(wù)器端發(fā)送請求,然后得到服務(wù)器端的響應(yīng)結(jié)果。
請求和響應(yīng)是redis中最簡單的用法。熟悉redis的朋友可能會(huì)想到了兩個(gè)redis的高級(jí)用法,這兩個(gè)用法并不是傳統(tǒng)意義上的請求-響應(yīng)模式。
到底是哪兩種用法呢?
第一種就是redis支持pipline,也就是管道操作,管道的好處就是redis客戶端可以一次性向服務(wù)器端發(fā)送多條命令,然后等待服務(wù)器端的返回。
第二種redis還支持Pub/Sub,也就是廣播模型,在這一種情況下,就不是請求和響應(yīng)的模式了,在Pub/Sub下,切換成了服務(wù)器端推送的模式。
Redis中的pipline
為什么要用pipline呢?
因?yàn)閞edis是一個(gè)典型的請求響應(yīng)模式,我們來舉個(gè)常見的incr命令的例子:
Client: INCR X Server: 1 Client: INCR X Server: 2 Client: INCR X Server: 3 Client: INCR X Server: 4
事實(shí)上客戶端只想得到最終的結(jié)果,但是每次客戶端都需要等待服務(wù)器端返回結(jié)果之后,才能發(fā)送下一次的命令。這樣就會(huì)導(dǎo)致一個(gè)叫做RTT(Round Trip Time)的時(shí)間浪費(fèi)。
雖然每次RTT的時(shí)間不長,但是累計(jì)起來也是一個(gè)非常客觀的數(shù)字。
那么可不可以將所有的客戶端命令放在一起發(fā)送給服務(wù)器呢? 這個(gè)優(yōu)化就叫做Pipeline。
piepline的意思就是客戶端可以在沒有收到服務(wù)器端返回的時(shí)候繼續(xù)向服務(wù)器端發(fā)送命令。
上面的命令可以用pipline進(jìn)行如下改寫:
(printf "INCR X\r\nINCR X\r\nINCR X\r\nINCR X\r\n"; sleep 1) | nc localhost 6379 :1 :2 :3 :4
因?yàn)閞edis服務(wù)器支持TCP協(xié)議進(jìn)行連接,所以我們可以直接用nc連到redis服務(wù)器中執(zhí)行命令。
在使用pipline的時(shí)候有一點(diǎn)要注意,因?yàn)閞edis服務(wù)器會(huì)將請求的結(jié)果緩存在服務(wù)器端,等到pipline中的所有命令都執(zhí)行完畢之后再統(tǒng)一返回,所以如果服務(wù)器端返回的數(shù)據(jù)比較多的情況下,需要考慮內(nèi)存占用的問題。
那么pipline僅僅是為了減少RTT嗎?
熟悉操作系統(tǒng)的朋友可能有聽說過用戶空間和操作系統(tǒng)空間的概念,從用戶輸入讀取數(shù)據(jù)然后再寫入到系統(tǒng)空間中,這里涉及到了一個(gè)用戶空間的切換,在IO操作中,這種空間切換或者拷貝是比較耗時(shí)的,如果頻繁的進(jìn)行請求和響應(yīng),就會(huì)造成這種頻繁的空間切換,從而降低了系統(tǒng)的效率。
使用pipline可以一次性發(fā)送多條指令,從而有效避免空間的切換行為。
Redis中的Pub/Sub
和Pub/Sub相關(guān)的命令是SUBSCRIBE, UNSUBSCRIBE 和 PUBLISH。
為什么要用Pub/Sub呢?其主要的目的就是解耦,在Pub/Sub中消息發(fā)送方不需要知道具體的接收方的地址,同樣的對于消息接收方來說,也不需要知道具體的消息發(fā)送方的地址。他們只需要知道關(guān)聯(lián)的主題即可。
subscribe和publish的命令比較簡單,我們舉一個(gè)例子,首先是客戶端subscribe topic:
redis-cli -h 127.0.0.1 127.0.0.1:6379> subscribe topic Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "topic" 3) (integer) 1
然后在另外一個(gè)終端,調(diào)用publish命令:
redis-cli -h 127.0.0.1 127.0.0.1:6379> publish topic "what is your name?" (integer) 1
可以看到客戶端會(huì)收到下面的消息:
1) "message" 2) "topic" 3) "what is your name?"
RESP protocol
RESP協(xié)議有5種類型,分別是imple Strings, Errors, Integers, Bulk Strings 和 Arrays。
不同的類型以消息中的第一個(gè)byte進(jìn)行區(qū)分,如下所示:
類型 | 第一個(gè)byte |
---|---|
Simple Strings | + |
Errors | - |
Integers | : |
Bulk Strings | $ |
Arrays | * |
protocol中不同的部分以 "\r\n" (CRLF)來進(jìn)行區(qū)別。
Simple Strings
Simple Strings的意思是簡單的字符串。
通常用在服務(wù)器端的返回中,這種消息的格式就是"+"加上文本消息,最后以"\r\n"結(jié)尾。
比如服務(wù)器端返回OK,那么對應(yīng)的消息就是:
"+OK\r\n"
上面的消息是一個(gè)非二進(jìn)制安全的消息,如果想要發(fā)送二進(jìn)制安全的消息,則可以使用Bulk Strings。
什么是非二進(jìn)制安全的消息呢?對于Simple Strings來說,因?yàn)橄⑹且?quot;\r\n"結(jié)尾,所以消息中間不能包含"\r\n"這兩個(gè)特殊字符,否則就會(huì)產(chǎn)生錯(cuò)誤的含義。
Bulk Strings
Bulk Strings是二進(jìn)制安全的。這是因?yàn)锽ulk Strings包含了一個(gè)字符長度字段,因?yàn)槭歉鶕?jù)長度來判斷字符長度的,所以并不存在根據(jù)字符中某個(gè)特定字符來判斷是否字符結(jié)束的缺點(diǎn)。
具體而言Bulk Strings的結(jié)構(gòu)是"$"+字符串長度+"\r\n"+字符串+"\r\n"。
以O(shè)K為例,如果以Bulk Strings來表示,則如下所示:
"$2\r\nok\r\n"
Bulk Strings還可以包含空字符串:
"$0\r\n\r\n"
當(dāng)然還可以表示不存在的Null值:
"$-1\r\n"
RESP Integers
這是redis中的整數(shù)表示,具體的格式是":"+整數(shù)+"\r\n"。
比如18這個(gè)整數(shù)就可以用下面的格式來表示:
":18\r\n"
RESP Arrays
redis的多個(gè)命令可以以array來表示,服務(wù)器端返回的多個(gè)值也可以用arrays來表示。
RESP Arrays的格式是"*"+數(shù)組中的元素個(gè)數(shù)+其他類似的數(shù)據(jù)。
所以RESP Arrays是一個(gè)復(fù)合結(jié)構(gòu)的數(shù)據(jù)。比如一個(gè)數(shù)組中包含了兩個(gè)Bulk Strings:"redis","server"則可以用下面的格式來表示:
"*2\r\n$5\r\nredis\r\n$6\r\nserver\r\n"
RESP Arrays中的原始不僅可以使用不同類型,還能包含RESP Arrays,也就是array的嵌套:
"*3\r\n$5\r\nredis\r\n$6\r\nserver\r\n*1\r\n$4\r\ngood\r\n"
為了方便觀察,我們將上面的消息格式一下:
"*3\r\n $5\r\nredis\r\n $6\r\nserver\r\n *1\r\n $4\r\ngood\r\n"
上面的消息是一個(gè)包含三個(gè)元素的數(shù)組,前面兩個(gè)元素是Bulk Strings,最后一個(gè)是包含一個(gè)元素的數(shù)組。
RESP Errors
最后,RESP還可以表示錯(cuò)誤消息。RESP Errors的消息格式是"-"+字符串,如下所示:
"-Err something wrong\r\n"
一般情況下,"-"后面的第一個(gè)單詞表示的是錯(cuò)誤類型,但是這只是一個(gè)約定俗成的規(guī)定,并不是RESP協(xié)議中的強(qiáng)制要求。
另外,經(jīng)過對比,大家可能會(huì)發(fā)現(xiàn)RESP Errors和Simple Strings是消息格式是差不多的。
這種對不同消息類型的處理是在客戶端進(jìn)行區(qū)分的。
Inline commands
如果完全按RESP協(xié)議的要求,當(dāng)我們連接到服務(wù)器端的時(shí)候需要包含RESP中定義消息的所有格式,但是這些消息中包含了額外的消息類型和回車換行符,所以直接使用協(xié)議來執(zhí)行的話會(huì)比較困惑。
于是redis還提供一些內(nèi)聯(lián)的命令,也就是協(xié)議命令的精簡版本,這個(gè)精簡版本去除了消息類型和回車換行符。
我們以"get world"這個(gè)命令為例。來看下不同方式的連接情況。
首先是使用redis-cli進(jìn)行連接:
redis-cli -h 127.0.0.1 127.0.0.1:6379> get world "hello"
因?yàn)閞edis-cli是redis的客戶端,所以可以直接使用inline command來執(zhí)行命令。
如果使用telnet,我們也可以使用同樣的命令來獲得結(jié)果:
telnet 127.0.0.1 6379 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. get world $5 hello
可以看到返回的結(jié)果是"$5\r\nhello\r\n"。
如果要使用協(xié)議消息來請求redis服務(wù)器應(yīng)該怎么做呢?
我們要請求的命令是"get world",將其轉(zhuǎn)換成為RESP的消息則是:
"*2\r\n$3\r\nget\r\n$5\r\nworld\r\n"
我們嘗試一下將上述命令使用nc傳遞到redis server上:
(printf "*2\r\n$3\r\nget\r\n$5\r\nworld\r\n"; sleep 1) | nc localhost 6379 -ERR Protocol error: expected '$', got ' '
很遺憾我們得到了ERR,那么是不是不能直接使用RESP消息格式進(jìn)行傳輸呢?當(dāng)然不是,上面的問題在于$
符號(hào)是一個(gè)特殊字符,我們需要轉(zhuǎn)義一下:
(printf "*2\r\n\$3\r\nget\r\n\$5\r\nworld\r\n"; sleep 1) | nc localhost 6379 $5 hello
可以看到輸出的結(jié)果和直接使用redis-cli一致。
總結(jié)
以上就是RESP協(xié)議的基本內(nèi)容和手動(dòng)使用的例子,有了RESP,我們就可以根據(jù)協(xié)議中定義的格式來創(chuàng)建redis客戶端。
可能大家又會(huì)問了,為什么只是redis客戶端呢?有了協(xié)議是不是redis服務(wù)器端也可以創(chuàng)建呢?答案當(dāng)然是肯定的,只需要按照協(xié)議進(jìn)行消息傳輸即可。主要的問題在于redis服務(wù)器端的實(shí)現(xiàn)比較復(fù)雜,不是那么容易實(shí)現(xiàn)的。
以上就是redis protocol通信協(xié)議及使用詳解的詳細(xì)內(nèi)容,更多關(guān)于redis protocol通信協(xié)議的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redis不是一直號(hào)稱單線程效率也很高嗎,為什么又采用多線程了?
這篇文章主要介紹了Redis不是一直號(hào)稱單線程效率也很高嗎,為什么又采用多線程了的相關(guān)資料,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03Redis主從復(fù)制與讀寫分離的實(shí)現(xiàn)
Redis在作為緩存的時(shí)候,隨著項(xiàng)目訪問量的增加,對Redis服務(wù)器的操作也越加頻繁,雖然Redis讀寫速度都很快,但是一定程度上也會(huì)造成一定的延時(shí),本文主要介紹了Redis主從復(fù)制與讀寫分離的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12解析高可用Redis服務(wù)架構(gòu)分析與搭建方案
我們按照由簡至繁的步驟,搭建一個(gè)最小型的高可用的Redis服務(wù)。 本文通過四種方案給大家介紹包含每種方案的優(yōu)缺點(diǎn)及詳細(xì)解說,具體內(nèi)容詳情跟隨小編一起看看吧2021-06-06通過 Redis 實(shí)現(xiàn) RPC 遠(yuǎn)程方法調(diào)用(支持多種編程語言)
這篇文章主要介紹了通過 Redis 實(shí)現(xiàn) RPC 遠(yuǎn)程方法調(diào)用,支持多種編程語言,本文就以Ruby和Python為例,給出了實(shí)現(xiàn)代碼,需要的朋友可以參考下2014-09-09Redis集群新增、刪除節(jié)點(diǎn)以及動(dòng)態(tài)增加內(nèi)存的方法
本文主要介紹了Redis集群新增、刪除節(jié)點(diǎn)以及動(dòng)態(tài)增加內(nèi)存的方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09詳解利用redis + lua解決搶紅包高并發(fā)的問題
本篇文章主要介紹了利用redis + lua解決搶紅包高并發(fā)的問題 ,詳細(xì)的講訴了需求分析和方案,有興趣的可以了解一下。2016-11-11Redis分布式鎖的使用和實(shí)現(xiàn)原理詳解
這篇文章主要給大家介紹了關(guān)于Redis分布式鎖的使用和實(shí)現(xiàn)原理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11