摘要:此時服務器處于休眠狀態,并使用進行事件輪詢,等待監聽事件的發生。繼續執行被調試程序,直至下一個斷點或程序結束縮寫。服務啟動包括初始化基礎配置數據結構對外提供服務的準備工作還原數據庫執行事件循環等。
一直很羨慕那些能讀 Redis 源碼的童鞋,也一直想自己解讀一遍,但迫于 C 大魔王的壓力,解讀日期遙遙無期。
相信很多小伙伴應該也都對或曾對源碼感興趣,但一來覺得自己不會 C 語言,二來也不知從何入手,結果就和博主一樣,一拖再拖。
但正所謂,種一棵樹的最好時間是十年前,其次就是現在。如果你真的想了解 Redis 源碼,又有緣看到了這系列博文,何不跟著博主一起解讀 Redis 源碼,做個同行人呢?接下來,就讓我們一起走入 Redis 的源碼世界吧。
決定要讀了,下一步就是如何讀。從 github 上克隆下來源碼,一看 src 目錄,望天,104 個文件,我該從哪個文件開始呢?一個個文件看?不行不行,這樣對我毫無誘惑力,沒有誘惑力,怎么能戰勝游戲、小說對我的吸引呢?苦苦思考,不得其解。然后突然想起來 HTTP 協議的那個經典面試題:從瀏覽器輸入網址,到頁面展示,這個過程發生了什么?
把這個面試題換成 Redis:輸入開啟 Redis 服務的命令,回車,到成功啟動 Redis 服務,這個過程發生了什么?
很好,這個問題成功吸引到我了。就讓我們從源碼中找出這個問題的答案吧。后續的所有文章我們都嘗試通過提出問題,解答問題的步驟,來深入了解 Redis。
要了解 Redis 命令的執行過程,首先要安裝 Redis 服務,搭建 debug 環境。如果我們能一行行的看到命令在代碼中的執行過程,解讀源碼也就沒任何阻礙了。
后續所有文章均基于 redis3.2.13 版本。
1 搭建 debug 環境1、下載編譯文件
在 linux 上,下載源碼文件,編譯,使用 gdb(cgdb) 進行 debug。
# bash wget https://github.com/antirez/redis/archive/3.2.13.tar.gz tar -zxvf 3.2.13.tar.gz mv redis-3.2.13 /opt/ cd redis-3.2.13 make # 編譯文件,得到可執行文件 redis-server、redis-cli 等
2、開啟 debug
# bash gdb src/redis-server # 在 redis 安裝目錄,進入 gdb 調試環境
按我們平時調試的習慣,找到一個函數設置斷點,然后一步步運行調試。對于 Redis 也一樣,我們找到 server.c 文件,服務器運行的 main 函數就在此文件中。我們對 main 函數設置斷點:
# gdb (gdb) b main Breakpoint 1 at 0x42ed05: file server.c, line 3962.
頁面會提示我們在 server.c 文件的 3962 行設置了斷點,也就是我們指定的 main 函數的位置。
設置好斷點,下一步就是啟動服務:
// 啟動服務 (gdb) r ./redis.conf Starting program: /opt/redis-3.2.13/src/redis-server ./redis.conf [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main (argc=2, argv=0x7fffffffe5a8) at server.c:3962 3962 int main(int argc, char **argv) {
通過頁面輸出信息,我們會發現程序已經運行到我們設置的斷點了。但是我們看不到運行處的代碼,這可不行,看不到源碼的調試,沒法接受使用以下命令”召喚“源碼:
(gdb) layout src
出現下圖所示的界面:
到了這一步,我們已經正式開始踏上 Redis 源碼解讀之路了。
2 初始化服務繼續往下走,使用 n 命令,執行下一步,然后不斷回車、回車、回車,好像每一行都看不懂什么意思。不管了,繼續走。咦,好像發現個能看懂的 initServerConfig()。沒看錯的話,這個應該是初始化服務器配置的,讓我們進到這個函數里確認下:
(gdb) s
回車,走你。然后我們就看到了下面這個界面:
提示我們進入了 server.c 1464 行的 initServerConfig 函數中。 n 命令,繼續走。我們會發現在這個函數里對服務器的各種基礎參數進行初始化。這里的參數詳見 server.h/redisServer 結構體。
回到 main 函數后,我們繼續前進,還會發現一個 initServer() 的函數。這個函數是進行驅動事件的注冊,以及綁定回調函數等。
繼續走,直到執行 aeMain(),如下圖:
程序執行到 4133 行時,Redis 服務已成功開啟了。此時服務器處于休眠狀態,并使用 aeMain() 進行事件輪詢,等待監聽事件的發生。
上述整個過程,我們只是跟著程序的運行,大概看了一遍執行流程。下面,我們來詳細解讀上面敘述的關鍵步驟:初始化基礎配置和初始化服務器數據結構。
3 初始化詳細解讀 3.1 初始化基礎配置初始化服務器的第一步就是創建一個 `redisServer 類型的實例變量 server 作為服務器的狀態,并為結構中的各個屬性設置默認值。
void initServerConfig(void) { int j; // 設置服務器運行 ID getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE); // 為運行 ID 加上結尾字符 server.runid[CONFIG_RUN_ID_SIZE] = "