跟著大彬讀原始碼 - Redis 2 - 伺服器如何響應客戶端請求?(上)

北國丶風光發表於2019-07-10

上次我們通過問題“啟動伺服器,程式都幹了什麼?”,跟著原始碼,深入瞭解了 Redis 伺服器的啟動過程。

既然啟動了 Redis 伺服器,那我們就要連上 Redis 服務幹些事情。這裡我們可以通過 redis-cli 測試。

現在客戶端和伺服器都準備好了,那麼Redis 客戶端和伺服器如何建立連線?伺服器又是如何響應客戶端的請求呢?

1 連線伺服器

客戶端和伺服器進行通訊,首先應該就是建立連線。接下來,我們來看下 redis-cli 與伺服器的連線過程。

還記得我們上次使用 gdb 除錯程式的步驟嗎?讓我們對 redis-cli 再來一次,看看原始碼的執行步驟。在開始之前,記得在編輯器開啟 redis-cli.c,定位到 main 函式的位置,畢竟 gdb 看程式碼沒有編輯器看著舒服。

debug 步驟如下:

# 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"

然後發給伺服器。

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

相關文章