跟著大彬讀原始碼 - Redis 2 - 伺服器如何響應客戶端請求?(上)
上次我們通過問題“啟動伺服器,程式都幹了什麼?”,跟著原始碼,深入瞭解了 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
執行完上述步驟,我們會進入如下介面:
這時候我們就可以回到編輯器頁,看看對 main
函式中哪一行比較感興趣,就停下來研究研究。到了 2618 行,我們會看到有執行 parseOptions
1.1 初始化客戶端配置
函式執行步驟:main
-> parseOptions
-> main
。
我們會看到,在執行 redis-cli
時攜帶的引數都是在這個函式中解析,比如我們啟動的時候帶著的 -p
引數,會在 996 行被解析到,同時賦值給客戶端的 hostport 配置項。如下圖:
1.2 客戶端啟動模式
函式執行步驟:main
。
回到 main
函式,會看到後面的程式碼會出現很多 cliConnect
函式。要注意的是,這裡並不表示 redis-cli 會執行多次 cliConnect
函式。實際上,每一個 if
- Latency mode:延遲模式。
redis-cli --latency -p 8379
用來測試客戶端與伺服器連線的延遲。還有--history
和--dist
可選項,用來展示不同的形式。 - Slave mode:模擬從節點模式。
- Get RDB mode:生成併發送 RDB 持久化檔案,儲存在本地。
- Pipe mode:管道模式。將命令封裝成指定資料格式,批量傳送給 redis 伺服器執行。
- Find big keys:統計 bigkey 的分佈。
- Stat mode:統計模式。實時展示伺服器的統計資訊。
- Scan mode:掃描指定模式的鍵,相當於 scan 模式。
- 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
函式中會看到 getaddrinfo
和 connect
函式的呼叫,這裡就是建立 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\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n"
然後發給伺服器。
以上是客戶端傳送命令給伺服器的過程,在下一節中,我們再來認識伺服器是如何響應客戶端請的