上次咱們經過問題「啓動服務器,程序都幹了什麼?」,跟着源碼,深刻了解了 Redis 服務器的啓動過程。算法
既然啓動了 Redis 服務器,那咱們就要連上 Redis 服務幹些事情。這裏咱們能夠經過 redis-cli 測試。數據庫
如今客戶端和服務器都準備好了,那麼Redis 客戶端和服務器如何創建鏈接?服務器又是如何響應客戶端的請求呢?bash
客戶端和服務器進行通信,首先應該就是創建鏈接。接下來,咱們來看下 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
執行完上述步驟,咱們會進入以下界面:編輯器
這時候咱們就能夠回到編輯器頁,看看對 main
函數中哪一行比較感興趣,就停下來研究研究。到了 2618 行,咱們會看到有執行 parseOptions
這個函數,看名字,好像是初始化一些可選項。那就進去看看唄。函數
函數執行步驟:main
-> parseOptions
-> main
。測試
咱們會看到,在執行 redis-cli
時攜帶的參數都是在這個函數中解析,好比咱們啓動的時候帶着的 -p
參數,會在 996 行被解析到,同時賦值給客戶端的 hostport 配置項。以下圖:
函數執行步驟:main
。
回到 main
函數,會看到後面的代碼會出現不少 cliConnect
函數。要注意的是,這裏並不表示 redis-cli 會執行屢次 cliConnect
函數。實際上,每個 if
語句塊,都表明着客戶端的一種鏈接模式,3.2.13 版本支持如下模式:
redis-cli --latency -p 8379
用來測試客戶端與服務器鏈接的延遲。還有 --history
和 --dist
可選項,用來展現不一樣的形式。函數執行步驟: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 鏈接的地方。
函數執行步驟: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
命令爲例,來看看命令的執行過程。
當用戶在客戶端鍵入一個命令請求時,客戶端會將這個命令請求按協議格式轉換,而後經過鏈接到服務器的套接字,將轉換後的命令請求發送給服務器,如圖 3 所示:
所以,對於咱們上面的命令請求,客戶端會轉成:
"*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n"
而後發給服務器。
以上是客戶端發送命令給服務器的過程,在下一節中,咱們再來認識服務器是如何響應客戶端請的。