一直很羨慕那些能讀 Redis 源碼的童鞋,也一直想本身解讀一遍,但迫於 C 大魔王的壓力,解讀日期遙遙無期。linux
相信不少小夥伴應該也都對或曾對源碼感興趣,但一來以爲本身不會 C 語言,二來也不知從何入手,結果就和博主同樣,一拖再拖。git
但正所謂,種一棵樹的最好時間是十年前,其次就是如今。若是你真的想了解 Redis 源碼,又有緣看到了這系列博文,何不跟着博主一塊兒解讀 Redis 源碼,作個同行人呢?接下來,就讓咱們一塊兒走入 Redis 的源碼世界吧。github
決定要讀了,下一步就是如何讀。從 github 上克隆下來源碼,一看 src 目錄,望天,104 個文件,我該從哪一個文件開始呢?一個個文件看?不行不行,這樣對我毫無誘惑力,沒有誘惑力,怎麼能打敗遊戲、小說對個人吸引呢?苦苦思考,不得其解。而後忽然想起來 HTTP 協議的那個經典面試題:從瀏覽器輸入網址,到頁面展現,這個過程發生了什麼?面試
把這個面試題換成 Redis:輸入開啓 Redis 服務的命令,回車,到成功啓動 Redis 服務,這個過程發生了什麼?redis
很好,這個問題成功吸引到我了。就讓咱們從源碼中找出這個問題的答案吧。後續的全部文章咱們都嘗試經過提出問題,解答問題的步驟,來深刻了解 Redis。數據庫
要了解 Redis 命令的執行過程,首先要安裝 Redis 服務,搭建 debug 環境。若是咱們能一行行的看到命令在代碼中的執行過程,解讀源碼也就沒任何阻礙了。數組
後續全部文章均基於 redis3.2.13 版本。瀏覽器
一、下載編譯文件
在 linux 上,下載源碼文件,編譯,使用 gdb(cgdb) 進行 debug。bash
# 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 等
二、開啓 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 源碼解讀之路了。
繼續往下走,使用 n
命令,執行下一步,而後不斷回車、回車、回車,好像每一行都看不懂什麼意思。無論了,繼續走。咦,好像發現個能看懂的 initServerConfig()
。沒看錯的話,這個應該是初始化服務器配置的,讓咱們進到這個函數裏確認下:
(gdb) s
回車,走你。而後咱們就看到了下面這個界面:
提示咱們進入了 server.c 1464 行的 initServerConfig
函數中。 n
命令,繼續走。咱們會發如今這個函數裏對服務器的各類基礎參數進行初始化。這裏的參數詳見 server.h/redisServer 結構體。
回到 main 函數後,咱們繼續前進,還會發現一個 initServer()
的函數。這個函數是進行驅動事件的註冊,以及綁定回調函數等。
繼續走,直到執行 aeMain()
,以下圖:
程序執行到 4133 行時,Redis 服務已成功開啓了。此時服務器處於休眠狀態,並使用 aeMain()
進行事件輪詢,等待監聽事件的發生。
上述整個過程,咱們只是跟着程序的運行,大概看了一遍執行流程。下面,咱們來詳細解讀上面敘述的關鍵步驟:初始化基礎配置和初始化服務器數據結構。
初始化服務器的第一步就是建立一個 `redisServer
類型的實例變量 server 做爲服務器的狀態,併爲結構中的各個屬性設置默認值。
void initServerConfig(void) { int j; // 設置服務器運行 ID getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE); // 爲運行 ID 加上結尾字符 server.runid[CONFIG_RUN_ID_SIZE] = '\0'; // 設置服務器默認運行架構 server.arch_bits = (sizeof(long) == 8) ? 64 : 32; // 設置服務器默認配置文件路徑 server.configfile = NULL; // 設置服務器默認運行頻率 server.hz = CONFIG_DEFAULT_HZ; // 設置服務器默認端口 server.port = CONFIG_DEFAULT_SERVER_PORT; // ... }
對於 initServerConfig
函數來講,它主要完成如下主要工做:
initServerConfig 函數設置的服務器狀態屬性基本上都是一些整數、浮點數或者字符串屬性。除了命令吧以外,initServerConfig 函數沒有建立服務器狀態的其它數據結構。像數據庫、慢查詢日誌、Lua 環境、共享對象等這些數據結構是在以後的步驟中建立的。
當初始化基礎配置參數後,下一步就要開始載入配置選項。
在啓動服務器時,用戶能夠經過給定配置參數或者知道配置文件來修改服務器的默認配置。就像咱們能夠在啓動服務時指定端口:
# bash ./src/redis-server --port 7379
經過給定配置參數的方式,修改了服務器的運行端口號。
除了給定配置參數的方式,咱們能夠經過指定配置文件的形式啓動服務:
# bash ./src/redis-server ./redis.conf
經過指定配置文件的形式啓動服務時,咱們實際上就是經過配置文件的形式修改了服務器的數據庫配置。
服務器在用 initServerConfig
函數初始完 server
變量後,就會開始載入用戶給定的配置參數和配置文件,並根據用戶設定的配置,對 server
變量相關屬性進行修改。
關於命令行指定配置、配置文件配置、默認配置,這三種配置中:
initServerConfig
函數設置的默認值。在執行 initServerConfig
函數初始化配置時,程序只建立了命令表一個數據結構,而服務器除了命令表還包括其餘數據結構,好比:
RedisClient
結構實例。上述這些數據結構會在 initServer
函數爲其分配內存,並在有須要時爲這些數據結構設置或關聯初始化值。
之因此在載入用戶配置以後才初始化數據結構,就是由於服務器要先載入用戶的配置選項,才能根據選項正確的對數據結構進行初始化。避免再根據用戶配置修改數據結構相關屬性。
因此,咱們能夠看出,服務器對狀態的初始化分爲兩步進行:
initServerConfig
函數是初始化通常屬性。initServer
初始化數據結構。除了初始化數據結構以外,initServer
還進行了一些很是重要的設置操做,包括:
當 initServer
函數執行完畢以後,服務器將用 ASCII 字符在日誌中打印出咱們常見到的 Redis 圖標,以及 Redis 的版本號信息等。
在完成了對服務器狀態 server
變量的初始化以後,服務器須要載入 RDB 文件或者 AOF 文件(數據持久化保存文件),並根據文件記錄的內容來還原服務器的數據庫狀態。
還原過程當中,服務器會判斷是否啓用了 AOF 持久化功能:
當服務器完成數據庫狀態還原工做以後,會在日誌中打印出載入文件和還原數據庫狀態所耗費的時長。
8189:M 31 May 13:12:47.971 * DB loaded from disk: 0.000 seconds
在初始化的最後一步,服務器將打印出如下日誌:
8189:M 31 May 13:12:47.971 * The server is now ready to accept connections on port 8379
並開始執行服務器的事件循環。
至此,服務器的初始化工做所有完成。
命令 | 解釋 | 示例 |
---|---|---|
gdb file | 加載被調試的可執行程序文件 | gdb src/redis-server |
r | Run 的縮寫,運行被調試的程序。 | r ./redis.conf |
c | Continue 的縮寫。繼續執行被調試程序,直至下一個斷點或程序結束 | c |
b | Breakpoint 縮寫。設置斷點。可使用 行號、函數名稱、執行地址等方式指定斷點位置 | b main |
s/n | s 至關於「單步跟蹤並進入」,也就是說進入到執行的函數內部。n 至關於「單步跟蹤」,不進入到執行函數內部 | s/n |
p 變量名稱 | Print 縮寫。顯示指定變量的值。 | p server |