1. 程式人生 > >【Redis基礎】客戶端原始碼解析

【Redis基礎】客戶端原始碼解析

1.定義

Redis客戶端與伺服器之間通過TCP協議進行通訊。TCP協議是一種流式協議,資料以位元組流的形式進行傳遞,沒有固有的”報文”或”報文邊界”的概念,如果需要設定邊界,需要應用層自行處理。

2.原始碼

客戶端redis.h/redisClient原始碼如下:

/* With multiplexing we need to take per-client state.
 * Clients are taken in a liked list.
 *
 * 因為 I/O 複用的緣故,需要為每個客戶端維持一個狀態。
 *
 * 多個客戶端狀態被伺服器用連結串列連線起來。
 */
typedef
struct redisClient { // 套接字描述符 int fd; // 當前正在使用的資料庫 redisDb *db; // 當前正在使用的資料庫的 id (號碼) int dictid; // 客戶端的名字 robj *name; /* As set by CLIENT SETNAME */ // 查詢緩衝區 sds querybuf; // 查詢緩衝區長度峰值 size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */
// 引數數量 int argc; // 引數物件陣列 robj **argv; // 記錄被客戶端執行的命令 struct redisCommand *cmd, *lastcmd; // 請求的型別:內聯命令還是多條命令 int reqtype; // 剩餘未讀取的命令內容數量 int multibulklen; /* number of multi bulk arguments left to read */ // 命令內容的長度 long bulklen; /* length of bulk argument in multi bulk request */
// 回覆連結串列 list *reply; // 回覆連結串列中物件的總大小 unsigned long reply_bytes; /* Tot bytes of objects in reply list */ // 已傳送位元組,處理 short write 用 int sentlen; /* Amount of bytes already sent in the current buffer or object being sent. */ // 建立客戶端的時間 time_t ctime; /* Client creation time */ // 客戶端最後一次和伺服器互動的時間 time_t lastinteraction; /* time of the last interaction, used for timeout */ // 客戶端的輸出緩衝區超過軟性限制的時間 time_t obuf_soft_limit_reached_time; // 客戶端狀態標誌 int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */ // 當 server.requirepass 不為 NULL 時 // 代表認證的狀態 // 0 代表未認證, 1 代表已認證 int authenticated; /* when requirepass is non-NULL */ // 複製狀態 int replstate; /* replication state if this is a slave */ // 用於儲存主伺服器傳來的 RDB 檔案的檔案描述符 int repldbfd; /* replication DB file descriptor */ // 讀取主伺服器傳來的 RDB 檔案的偏移量 off_t repldboff; /* replication DB file offset */ // 主伺服器傳來的 RDB 檔案的大小 off_t repldbsize; /* replication DB file size */ sds replpreamble; /* replication DB preamble. */ // 主伺服器的複製偏移量 long long reploff; /* replication offset if this is our master */ // 從伺服器最後一次傳送 REPLCONF ACK 時的偏移量 long long repl_ack_off; /* replication ack offset, if this is a slave */ // 從伺服器最後一次傳送 REPLCONF ACK 的時間 long long repl_ack_time;/* replication ack time, if this is a slave */ // 主伺服器的 master run ID // 儲存在客戶端,用於執行部分重同步 char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */ // 從伺服器的監聽埠號 int slave_listening_port; /* As configured with: SLAVECONF listening-port */ // 事務狀態 multiState mstate; /* MULTI/EXEC state */ // 阻塞型別 int btype; /* Type of blocking op if REDIS_BLOCKED. */ // 阻塞狀態 blockingState bpop; /* blocking state */ // 最後被寫入的全域性複製偏移量 long long woff; /* Last write global replication offset. */ // 被監視的鍵 list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */ // 這個字典記錄了客戶端所有訂閱的頻道 // 鍵為頻道名字,值為 NULL // 也即是,一個頻道的集合 dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */ // 連結串列,包含多個 pubsubPattern 結構 // 記錄了所有訂閱頻道的客戶端的資訊 // 新 pubsubPattern 結構總是被新增到表尾 list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */ sds peerid; /* Cached peer ID. */ /* Response buffer */ // 回覆偏移量 int bufpos; // 回覆緩衝區 char buf[REDIS_REPLY_CHUNK_BYTES]; } redisClient;

3.原始碼剖析

我在此只剖析redisClient的通用屬性,特定功能屬性在下篇博文剖析。

(1)fd

根據客戶端型別的不同,fd的屬性可以是-1(偽客戶端)或大於-1(普通客戶端)的整數

(2)name

預設情況下,一個連線到伺服器的客戶端是沒有名字的,但可以使用client setname為客戶端設定一個名字。

(3)flags

flags記錄了客戶端的角色及客戶端的狀態,可以是單個標誌,也可以是多個標誌。

(4) querybuf

這是客戶端的輸入緩衝區(sds型別),用於儲存客戶端傳送的命令請求。可以動態縮小或擴大,但不能超過1GB,否則伺服器將關閉這個客戶端。

(5)argc與argv

  • 引數物件argv:是一個數組,陣列中的每一項都是字串物件,其中argv[0]是要執行的命令,而其它項是傳給命令的引數。

  • 引數數量argc:記錄argv陣列的長度

例:執行set key value命令,argc的值為3,因為set本身也是一個引數。

這裡寫圖片描述

(6)redisCommand *cmd

當伺服器從協議中分析並得出argc與argv屬性的值之後,伺服器將根據argv[0]的值,在命令表cmd中查詢命令所對應redisCommand的命令實現函式。

這裡寫圖片描述

(7)bufpos和buf[REDIS_REPLY_CHUNK_BYTES]

  • bufpos:記錄了buf陣列已使用的位元組數量

  • buf[REDIS_REPLY_CHUNK_BYTES]:大小為REDIS_REPLY_CHUNK_BYTES的位元組陣列,預設大小16*1024,即16KB。

注:輸出緩衝區分為固定大小緩衝區(儲存長度較小的回覆)和可變大小緩衝區(儲存長度大的回覆)。

(8)authenticated

記錄客戶端是否通過了身份驗證。若authenticated的值為0,那麼客戶端未通過身份驗證,若authenticated的值為1,表示客戶端通過了驗證。

4.客戶端的建立與關閉

(1)建立客戶端

若是普通客戶端,使用connect函式連線到伺服器時,伺服器會呼叫連線事件處理器,併為客戶端建立相應的客戶端狀態,然後將這個狀態新增到伺服器狀態結構的clients連結串列的末尾。

(2)關閉客戶端

  • 客戶端關閉的原因:網路連線關閉、傳送了不合適格式的命令請求、執行了Client Kill命令、空轉時間超時、輸出緩衝區的大小超過控制等

  • 伺服器使用硬性限制和軟性限制兩種模式來限制客戶端輸出緩衝區的大小

    • 硬性限制:若輸出緩衝區的大小超過了硬性限制所設定的大小,就立即關閉客戶端
    • 軟性限制:若輸出緩衝區的大小超過了軟性緩衝區的大小,但沒超過硬性限制,則會記錄客戶端到達軟性限制的起始時間,若持續時間伺服器設定的時長,則關閉客戶端。

(3)偽客戶端

  • 建立Lua指令碼的偽客戶端:在伺服器初始化時建立,一直持續到伺服器關閉。
  • 載入AOF檔案時使用的偽客戶端:在載入時建立,載入完成後關閉。



本人才疏學淺,若有錯,請指出,謝謝!
如果你有更好的建議,可以留言我們一起討論,共同進步!
衷心的感謝您能耐心的讀完本篇博文!

參考書籍:《Redis設計與實現(第二版)》—黃健巨集