自己動手寫Redis客戶端- Redis協議(1)

来源:http://www.cnblogs.com/tangruixin/archive/2017/11/29/7918428.html
-Advertisement-
Play Games

網路層 客戶端和伺服器通過 TCP 連接來進行數據交互, 伺服器預設的埠號為 6379 。 客戶端和伺服器發送的命令或數據一律以 \r\n (CRLF)結尾。 請求 Redis 伺服器接受命令以及命令的參數。 伺服器會在接到命令之後,對命令進行處理,並將命令的回覆傳送回客戶端。 新版統一請求協議 ...


 

網路層

客戶端和伺服器通過 TCP 連接來進行數據交互, 伺服器預設的埠號為 6379 。

客戶端和伺服器發送的命令或數據一律以 \r\n (CRLF)結尾。

請求

Redis 伺服器接受命令以及命令的參數。

伺服器會在接到命令之後,對命令進行處理,並將命令的回覆傳送回客戶端。

新版統一請求協議

新版統一請求協議在 Redis 1.2 版本中引入, 並最終在 Redis 2.0 版本成為 Redis 伺服器通信的標準方式。

你的 Redis 客戶端應該按照這個新版協議來進行實現。

在這個協議中, 所有發送至 Redis 伺服器的參數都是二進位安全(binary safe)的。

以下是這個協議的一般形式:

*<參數數量> CR LF
$<參數 1 的位元組數量> CR LF
<參數 1 的數據> CR LF
...
$<參數 N 的位元組數量> CR LF
<參數 N 的數據> CR LF

譯註:命令本身也作為協議的其中一個參數來發送。

舉個例子, 以下是一個命令協議的列印版本:

*3
$3
SET
$5
mykey
$7
myvalue

這個命令的實際協議值如下:

"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"

稍後我們會看到, 這種格式除了用作命令請求協議之外, 也用在命令的回覆協議中: 這種只有一個參數的回覆格式被稱為批量回覆(Bulk Reply)。

統一協議請求原本是用在回覆協議中, 用於將列表的多個項返回給客戶端的, 這種回覆格式被稱為多條批量回覆(Multi Bulk Reply)。

一個多條批量回覆以 *<argc>\r\n 為首碼, 後跟多條不同的批量回覆, 其中 argc 為這些批量回覆的數量。

回覆

Redis 命令會返回多種不同類型的回覆。

通過檢查伺服器發回數據的第一個位元組, 可以確定這個回覆是什麼類型:

狀態回覆(status reply)的第一個位元組是 "+"

錯誤回覆(error reply)的第一個位元組是 "-"

整數回覆(integer reply)的第一個位元組是 ":"

批量回覆(bulk reply)的第一個位元組是 "$"

多條批量回覆(multi bulk reply)的第一個位元組是 "*"

狀態回覆

一個狀態回覆(或者單行回覆,single line reply)是一段以 "+" 開始、 "\r\n" 結尾的單行字元串。

以下是一個狀態回覆的例子:

+OK

客戶端庫應該返回 "+" 號之後的所有內容。 比如在在上面的這個例子中, 客戶端就應該返回字元串 "OK" 。

狀態回覆通常由那些不需要返回數據的命令返回,這種回覆不是二進位安全的,它也不能包含新行。

狀態回覆的額外開銷非常少,只需要三個位元組(開頭的 "+" 和結尾的 CRLF)。

錯誤回覆

錯誤回覆和狀態回覆非常相似, 它們之間的唯一區別是, 錯誤回覆的第一個位元組是 "-" , 而狀態回覆的第一個位元組是 "+" 。

錯誤回覆只在某些地方出現問題時發送: 比如說, 當用戶對不正確的數據類型執行命令, 或者執行一個不存在的命令, 等等。

一個客戶端庫應該在收到錯誤回覆時產生一個異常。

以下是兩個錯誤回覆的例子:

-ERR unknown command 'foobar'
-WRONGTYPE Operation against a key holding the wrong kind of value

在 "-" 之後,直到遇到第一個空格或新行為止,這中間的內容表示所返回錯誤的類型。

ERR 是一個通用錯誤,而 WRONGTYPE 則是一個更特定的錯誤。 一個客戶端實現可以為不同類型的錯誤產生不同類型的異常, 或者提供一種通用的方式, 讓調用者可以通過提供字元串形式的錯誤名來捕捉(trap)不同的錯誤。

不過這些特性用得並不多, 所以並不是特別重要, 一個受限的(limited)客戶端可以通過簡單地返回一個邏輯假(false)來表示一個通用的錯誤條件。

整數回覆

整數回覆就是一個以 ":" 開頭, CRLF 結尾的字元串表示的整數。

比如說, ":0\r\n" 和 ":1000\r\n" 都是整數回覆。

返回整數回覆的其中兩個命令是 INCRLASTSAVE 。 被返回的整數沒有什麼特殊的含義, INCR 返回鍵的一個自增後的整數值, 而 LASTSAVE 則返回一個 UNIX 時間戳, 返回值的唯一限制是這些數必須能夠用 64 位有符號整數表示。

整數回覆也被廣泛地用於表示邏輯真和邏輯假: 比如 EXISTSSISMEMBER 都用返回值 1 表示真, 0 表示假。

其他一些命令, 比如 SADDSREMSETNX , 只在操作真正被執行了的時候, 才返回 1 , 否則返回 0 。

以下命令都返回整數回覆: SETNXDELEXISTSINCRINCRBYDECRDECRBYDBSIZELASTSAVERENAMENXMOVELLENSADDSREMSISMEMBERSCARD

批量回覆

伺服器使用批量回覆來返回二進位安全的字元串,字元串的最大長度為 512 MB 。

客戶端:GET mykey
伺服器:foobar

伺服器發送的內容中:

第一位元組為 "$" 符號

接下來跟著的是表示實際回覆長度的數字值

之後跟著一個 CRLF

再後面跟著的是實際回覆數據

最末尾是另一個 CRLF

對於前面的 GET 命令,伺服器實際發送的內容為:

"$6\r\nfoobar\r\n"

如果被請求的值不存在, 那麼批量回覆會將特殊值 -1 用作回覆的長度值, 就像這樣:

客戶端:GET non-existing-key
伺服器:$-1

這種回覆稱為空批量回覆(NULL Bulk Reply)。

當請求對象不存在時,客戶端應該返回空對象,而不是空字元串: 比如 Ruby 庫應該返回 nil , 而 C 庫應該返回 NULL (或者在回覆對象中設置一個特殊標誌), 諸如此類。

多條批量回覆

LRANGE 這樣的命令需要返回多個值, 這一目標可以通過多條批量回覆來完成。

多條批量回覆是由多個回覆組成的數組, 數組中的每個元素都可以是任意類型的回覆, 包括多條批量回覆本身。

多條批量回覆的第一個位元組為 "*" , 後跟一個字元串表示的整數值, 這個值記錄了多條批量回覆所包含的回覆數量, 再後面是一個 CRLF 。

客戶端: LRANGE mylist 0 3
伺服器: *4
伺服器: $3
伺服器: foo
伺服器: $3
伺服器: bar
伺服器: $5
伺服器: Hello
伺服器: $5
伺服器: World

在上面的示例中,伺服器發送的所有字元串都由 CRLF 結尾。

正如你所見到的那樣, 多條批量回覆所使用的格式, 和客戶端發送命令時使用的統一請求協議的格式一模一樣。 它們之間的唯一區別是:

統一請求協議只發送批量回覆。

而伺服器應答命令時所發送的多條批量回覆,則可以包含任意類型的回覆。

以下例子展示了一個多條批量回覆, 回覆中包含四個整數值, 以及一個二進位安全字元串:

*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$6\r\n
foobar\r\n

在回覆的第一行, 伺服器發送 *5\r\n , 表示這個多條批量回覆包含 5 條回覆, 再後面跟著的則是 5 條回覆的正文。

多條批量回覆也可以是空白的(empty), 就像這樣:

客戶端: LRANGE nokey 0 1
伺服器: *0\r\n

無內容的多條批量回覆(null multi bulk reply)也是存在的, 比如當 BLPOP 命令的阻塞時間超過最大時限時, 它就返回一個無內容的多條批量回覆, 這個回覆的計數值為 -1 :

客戶端: BLPOP key 1
伺服器: *-1\r\n

客戶端庫應該區別對待空白多條回覆和無內容多條回覆: 當 Redis 返回一個無內容多條回覆時, 客戶端庫應該返回一個 null 對象, 而不是一個空數組。

多條批量回覆中的空元素

多條批量回覆中的元素可以將自身的長度設置為 -1 , 從而表示該元素不存在, 並且也不是一個空白字元串(empty string)。

SORT 命令使用 GET pattern 選項對一個不存在的鍵進行操作時, 就會發生多條批量回覆中帶有空白元素的情況。

以下例子展示了一個包含空元素的多重批量回覆:

伺服器: *3
伺服器: $3
伺服器: foo
伺服器: $-1
伺服器: $3
伺服器: bar

其中, 回覆中的第二個元素為空。

對於這個回覆, 客戶端庫應該返回類似於這樣的回覆:

["foo", nil, "bar"]

多命令和流水線

客戶端可以通過流水線, 在一次寫入操作中發送多個命令:

在發送新命令之前, 無須閱讀前一個命令的回覆。

多個命令的回覆會在最後一併返回。

內聯命令

當你需要和 Redis 伺服器進行溝通, 但又找不到 redis-cli , 而手上只有 telnet 的時候, 你可以通過 Redis 特別為這種情形而設的內聯命令格式來發送命令。

以下是一個客戶端和伺服器使用內聯命令來進行交互的例子:

客戶端: PING
伺服器: +PONG

以下另一個返回整數值的內聯命令的例子:

客戶端: EXISTS somekey
伺服器: :0

因為沒有了統一請求協議中的 "*" 項來聲明參數的數量, 所以在 telnet 會話輸入命令的時候, 必須使用空格來分割各個參數, 伺服器在接收到數據之後, 會按空格對用戶的輸入進行分析(parse), 並獲取其中的命令參數。

高性能 Redis 協議分析器

儘管 Redis 的協議非常利於人類閱讀, 定義也很簡單, 但這個協議的實現性能仍然可以和二進位協議一樣快。

因為 Redis 協議將數據的長度放在數據正文之前, 所以程式無須像 JSON 那樣, 為了尋找某個特殊字元而掃描整個 payload , 也無須對發送至伺服器的 payload 進行轉義(quote)。

程式可以在對協議文本中的各個字元進行處理的同時, 查找 CR 字元, 並計算出批量回覆或多條批量回覆的長度, 就像這樣:

#include <stdio.h>

int main(void) {
    unsigned char *p = "$123\r\n";
    int len = 0;

    p++;
    while(*p != '\r') {
        len = (len*10)+(*p - '0');
        p++;
    }

    /* Now p points at '\r', and the len is in bulk_len. */
    printf("%d\n", len);
    return 0;
}

得到了批量回覆或多條批量回覆的長度之後, 程式只需調用一次 read 函數, 就可以將回覆的正文數據全部讀入到記憶體中, 而無須對這些數據做任何的處理。

在回覆最末尾的 CR 和 LF 不作處理,丟棄它們。

Redis 協議的實現性能可以和二進位協議的實現性能相媲美, 並且由於 Redis 協議的簡單性, 大部分高級語言都可以輕易地實現這個協議, 這使得客戶端軟體的 bug 數量大大減少。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Redis請求協議的一般形式: 備註:CR表示為\r; LF表示\n 下麵是一個例子: Redis回覆 Redis 命令會返回多種不同類型的回覆。 通過檢查伺服器發回數據的第一個位元組, 可以確定這個回覆是什麼類型: 狀態回覆(status reply)的第一個位元組是 "+" 錯誤回覆(error r ...
  • 生命周期函數:需要繼承 MonoBehaviour 類才能使用。生命周期函數全部都是由系統定義好的,系統會自動調用,且調用順序和我們在代碼裡面的書寫順序無關。 常用的生命周期函數: Awake():喚醒事件,游戲一開始運行就執行,只執行一次。 OnEnable():啟用事件,只執行一次。當腳本組件被 ...
  • hangfire是執行後臺任務的利器,具體請看官網介紹:https://www.hangfire.io/ 新建一個asp.net core mvc 項目 引入nuget包 Hangfire.AspNetCore hangfire的任務需要資料庫持久化,我們在Startup類中修改ConfigureS ...
  • 實習狗的每天新知識日常 準備工作: 1.在項目中添加對NPOI的引用,NPOI下載地址:http://npoi.codeplex.com/releases/view/38113 2.NPOI學習系列教程推薦:http://www.cnblogs.com/tonyqus/archive/2009/04 ...
  • 紙殼CMS可以運行在Docker上,接下來看看如何自動構建紙殼CMS的Docker Image。我們希望的是在代碼提交到GitHub以後,容器鏡像服務可以自動構建Docker Image,構建好以後,就可以直接拿這個Docker Image來運行了。 ...
  • EF Power Tools最高支持到vs2013,但是可以通過變更版本號的方式在vs2015上進行安裝。 參考文章:(英文)http://thedatafarm.com/data-access/installing-ef-power-tools-into-vs2015/ (中文)https://w ...
  • 演示產品下載地址:http://www.jinhusns.com ...
  • 返回總目錄 7 Introduce Null Object(引入Null對象) 概要 你需要再三檢查某對象是否為null。 將null值替換為null對象。 動機 系統在使用對象的相關功能時,總要檢查對象是否為null,如果不為null,我們才會調用它的相關方法,完成某種邏輯。這樣的檢查在一個系統中 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...