跟着大彬讀源碼 - Redis 2 - 服務器如何響應客戶端請求?(上)

上次咱們經過問題「啓動服務器,程序都幹了什麼?」,跟着源碼,深刻了解了 Redis 服務器的啓動過程。算法

既然啓動了 Redis 服務器,那咱們就要連上 Redis 服務幹些事情。這裏咱們能夠經過 redis-cli 測試。數據庫

如今客戶端和服務器都準備好了,那麼Redis 客戶端和服務器如何創建鏈接?服務器又是如何響應客戶端的請求呢?bash

1 鏈接服務器

客戶端和服務器進行通信,首先應該就是創建鏈接。接下來,咱們來看下 redis-cli 與服務器的鏈接過程。服務器

還記得咱們上次使用 gdb 調試程序的步驟嗎?讓咱們對 redis-cli 再來一次,看看源碼的執行步驟。在開始以前,記得在編輯器打開 redis-cli.c,定位到 main 函數的位置,畢竟 gdb 看代碼沒有編輯器看着舒服。併發

debug 步驟以下:socket

# bash
cd /opt/redis-3.2.13
// 啓動 Redis 服務。Ctrl+c 可推出服務器啓動頁,同時保持服務器運行
./src/redis-server --port 8379 &
// 調試 redis-clli
gdb ./src/redis-cli
# gdb 
(gdb) b main
(gdb) r -p 8379
(gdb) layout src
(gdb) focus cmd

執行完上述步驟,咱們會進入以下界面:編輯器

圖 1 - 進入 redis-cli main 函數

這時候咱們就能夠回到編輯器頁,看看對 main 函數中哪一行比較感興趣,就停下來研究研究。到了 2618 行,咱們會看到有執行 parseOptions 這個函數,看名字,好像是初始化一些可選項。那就進去看看唄。函數

1.1 初始化客戶端配置

函數執行步驟:main -> parseOptions -> main測試

咱們會看到,在執行 redis-cli 時攜帶的參數都是在這個函數中解析,好比咱們啓動的時候帶着的 -p 參數,會在 996 行被解析到,同時賦值給客戶端的 hostport 配置項。以下圖:

圖 2 - 啓動 redis-cli 攜帶的 -p 參數被賦值給 hostport 配置項

1.2 客戶端啓動模式

函數執行步驟:main

回到 main 函數,會看到後面的代碼會出現不少 cliConnect 函數。要注意的是,這裏並不表示 redis-cli 會執行屢次 cliConnect 函數。實際上,每個 if 語句塊,都表明着客戶端的一種鏈接模式,3.2.13 版本支持如下模式:

  1. Latency mode:延遲模式。redis-cli --latency -p 8379 用來測試客戶端與服務器鏈接的延遲。還有 --history--dist 可選項,用來展現不一樣的形式。
  2. Slave mode:模擬從節點模式。
  3. Get RDB mode:生成併發送 RDB 持久化文件,保存在本地。
  4. Pipe mode:管道模式。將命令封裝成指定數據格式,批量發送給 redis 服務器執行。
  5. Find big keys:統計 bigkey 的分佈。
  6. Stat mode:統計模式。實時展現服務器的統計信息。
  7. Scan mode:掃描指定模式的鍵,至關於 scan 模式。
  8. LRU test mode:實時測試 LRU 算法的命中狀況。

1.3 鏈接服務器

函數執行步驟:main -> cliConnect -> redisConnect -> redisContextInit -> redisContextConnectTcp -> _redisContextConnectTcp -> cliConnect

咱們上面沒有使用特殊模式啓動,所以,咱們會看到在 2687 行真正的去調用 cliConnect 函數。跟蹤進去,讓咱們看看到底是如何和服務器進行鏈接的。

cliConnect 函數中,咱們看到,根據 hostsocket 的配置項,會使用不一樣的鏈接模式。從名字上,咱們大概能夠猜出,一個是 TCP Socket 鏈接,另外一個是本機 Unix Socket 鏈接。

若是想要使用 Unix Socket 鏈接,只需按格式配置 hostscoket 便可:./src/redis-cli -s /tmp/redis.sock

咱們這裏使用 TCP Scoket 鏈接,使用 redisConnect 函數創建鏈接。

不斷追蹤,咱們會看到上面所示的函數執行步驟,在 _redisContextConnectTcp 函數中會看到 getaddrinfoconnect 函數的調用,這裏就是創建 TCP 鏈接的地方。

1.4 校驗權限及選擇數據庫

函數執行步驟:cliConnect -> anetKeepAlive -> cliAuth -> cliSelect -> main

回到 cliConnect 函數,若是正常鏈接上服務器後,還會將咱們上面建立的 TCP 鏈接設置爲長鏈接,而後校驗權限,選擇鏈接數據庫。

...
/* Set aggressive KEEP_ALIVE socket option in the Redis context socket
 * in order to prevent timeouts caused by the execution of long
 * commands. At the same time this improves the detection of real
 * errors. */
anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
/* Do AUTH and select the right DB. */
if (cliAuth() != REDIS_OK)
    return REDIS_ERR;
if (cliSelect() != REDIS_OK)
    return REDIS_ERR;
...

至此,咱們已經跑完客戶端與服務器創建鏈接的全過程。感興趣的小夥伴能夠嘗試鏈接不存在的 IP 或 端口,觀察程序拋出異常的時機,熟悉整個鏈接過程。

客戶端與 服務器創建鏈接後,就可使用相關命令操做數據庫中的 key 了。下面咱們以 SET KEY VALUE 命令爲例,來看看命令的執行過程。

2 發送命令請求

當用戶在客戶端鍵入一個命令請求時,客戶端會將這個命令請求按協議格式轉換,而後經過鏈接到服務器的套接字,將轉換後的命令請求發送給服務器,如圖 3 所示:

圖 3 - 客戶端接收併發送命令請求的過程

所以,對於咱們上面的命令請求,客戶端會轉成:

"*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n"

而後發給服務器。

以上是客戶端發送命令給服務器的過程,在下一節中,咱們再來認識服務器是如何響應客戶端請的。

相關文章
相關標籤/搜索