【Redis原始碼研究】Redis的RESP協議
作者:張仕華
resp協議
redis客戶端和服務端互動使用的是redis作者制定的一個協議,叫resp(REdis Serialization Protocol)。
具體分如下幾個層次
- 基於tcp
- 請求響應模式,但在兩種情況下不再是簡單的請求和響應模式(下文介紹)
- 支援五種型別的資料,分別是簡單字串,錯誤,整型,bulk strings ,陣列
客戶端發給服務端的命令都會序列化為array,而服務端返回給客戶端的可以為如上任意一種型別,各簡單舉例如下
- 簡單字串,第一個byte為 '+',例如"+OKrn"
- 錯誤,第一個byte為'-',例如 "-Error messagern"
- 整型, 第一個byte為':",例如 ":10rn"
- bulk strings,第一個byte為"$",例如 "$6rnfoobarrn"(此處為美元符號,沒有前邊的反斜槓)
- 陣列,第一個byte為'',例如" 2rn$3\r\nfoo\r\n$3rnbarrn"
具體介紹參考:https://redis.io/topics/protocol
sub pattern
請求響應模式有兩種特殊情況
- 1.pipeline模式,客戶端同時傳送多條命令到服務端
- 2.sub pattern:當客戶端執行subscribe命令後,不再要求客戶端傳送命令,當有其他客戶端在訂閱渠道上publish訊息後,服務端會主動push資訊到客戶端
我們拿redis-cli客戶端試一下執行subscribe
127.0.0.1:6379> subscribe foo Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "foo" 3) (integer) 1
可以看到,redis-cli會阻塞,當另起一個客戶端,publish訊息後,會收到該訊息並列印
~$redis-cli 127.0.0.1:6379> publish foo bar (integer) 1
~$redis-cli 127.0.0.1:6379> subscribe foo Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "foo" 3) (integer) 1 1) "message" 2) "foo" 3) "bar"
直觀感覺是服務端阻塞了,沒有返回資料給客戶端。但看redis原始碼,實際服務端並沒有阻塞,並且可以在連線上繼續接收並處理命令
void subscribeCommand(client *c) { int j; //將訂閱的渠道加入相應結構體並直接返回 for (j = 1; j < c->argc; j++) pubsubSubscribeChannel(c,c->argv[j]); //將客戶端置CLINET_PUBSUB標記 c->flags |= CLIENT_PUBSUB; }
當客戶端置了CLINET_PUBSUB標記後,命令處理會做如下特殊邏輯
int processCommand(client *c) { ... //當置CLIENT_PUBSUB標記後,只有ping/subscribe/unsubscribe/psubscribe/punsubscribe命令能夠執行 if (c->flags & CLIENT_PUBSUB && c->cmd->proc != pingCommand && c->cmd->proc != subscribeCommand && c->cmd->proc != unsubscribeCommand && c->cmd->proc != psubscribeCommand && c->cmd->proc != punsubscribeCommand) { addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context"); return C_OK; } ... }
godis-cli
如上文所示,當客戶端執行subscribe命令後,實際上是可以繼續訂閱或者取消訂閱渠道,並且可以執行ping命令。redis-cli客戶端實際上是自己阻塞了,如下程式碼:
if (config.pubsub_mode) { if (config.output != OUTPUT_RAW) printf("Reading messages... (press Ctrl-C to quit)\n"); //進入死迴圈,一直等待服務端傳送訊息 while (1) { if (cliReadReply(output_raw) != REDIS_OK) exit(1); } }
那麼,我們可以拿go寫一個不阻塞的版本,並且可以測試redis的subscribe 模式。效果如下
>bogon:godis-cli $ go run godis-cli.go > set k1 v1 OK > get k1 v1 > subscribe foo subscribe foo 1 [sub]>subscribe foo1//sub模式下可以繼續訂閱其他渠道 subscribe foo1 2 [sub]> unsubscribe foo1//取消訂閱 unsubscribe foo1 1 [sub]> ping//sub模式也可以執行ping pong [sub]> get k1 //sub模式下不能執行get命令 Redis Error: only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context [sub]> exit exit sub pattern.... >get k1//退出sub模式後可以繼續正常執行get命令 v1 > exit
由於godis-cli直接實現了RESP協議,雖然只是為了觀察subscribe pattern的效果,但實際上可以支援所有redis命令的執行
具體程式碼地址見:https://github.com/erpeng/god...