redis源碼有必要讀嗎,跟著大彬讀源碼 - Redis 1 - 啟動服務,程序都干了什么?

 2023-10-21 阅读 28 评论 0

摘要:目錄 1 搭建 debug 環境2 初始化服務3 初始化詳細解讀4 其它操作5 gdb 基礎使用總結 一直很羨慕那些能讀 Redis 源碼的童鞋,也一直想自己解讀一遍,但迫于 C 大魔王的壓力,解讀日期遙遙無期。 相信很多小伙伴應該也都對或曾對源碼感興趣,但一來覺

目錄

  • 1 搭建 debug 環境
  • 2 初始化服務
  • 3 初始化詳細解讀
  • 4 其它操作
  • 5 gdb 基礎使用
  • 總結

一直很羨慕那些能讀 Redis 源碼的童鞋,也一直想自己解讀一遍,但迫于 C 大魔王的壓力,解讀日期遙遙無期。

相信很多小伙伴應該也都對或曾對源碼感興趣,但一來覺得自己不會 C 語言,二來也不知從何入手,結果就和博主一樣,一拖再拖。

但正所謂,種一棵樹的最好時間是十年前,其次就是現在。如果你真的想了解 Redis 源碼,又有緣看到了這系列博文,何不跟著博主一起解讀 Redis 源碼,做個同行人呢?接下來,就讓我們一起走入 Redis 的源碼世界吧。

決定要讀了,下一步就是如何讀。從 github 上克隆下來源碼,一看 src 目錄,望天,104 個文件,我該從哪個文件開始呢?一個個文件看?不行不行,這樣對我毫無誘惑力,沒有誘惑力,怎么能戰勝游戲、小說對我的吸引呢?苦苦思考,不得其解。然后突然想起來 HTTP 協議的那個經典面試題:從瀏覽器輸入網址,到頁面展示,這個過程發生了什么?

redis源碼有必要讀嗎。把這個面試題換成 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 等

redis源碼閱讀、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啟動?出現下圖所示的界面:

圖 1 - gdb 的 src 和 cmd 并存

到了這一步,我們已經正式開始踏上 Redis 源碼解讀之路了。

2 初始化服務

繼續往下走,使用 n 命令,執行下一步,然后不斷回車、回車、回車,好像每一行都看不懂什么意思。不管了,繼續走。咦,好像發現個能看懂的 initServerConfig()。沒看錯的話,這個應該是初始化服務器配置的,讓我們進到這個函數里確認下:

(gdb) s

回車,走你。然后我們就看到了下面這個界面:

redis項目,圖 2 - 進入初始化服務器配置函數

提示我們進入了 server.c 1464 行的 initServerConfig 函數中。 n 命令,繼續走。我們會發現在這個函數里對服務器的各種基礎參數進行初始化。這里的參數詳見 server.h/redisServer 結構體。

回到 main 函數后,我們繼續前進,還會發現一個 initServer() 的函數。這個函數是進行驅動事件的注冊,以及綁定回調函數等。

繼續走,直到執行 aeMain(),如下圖:

圖 3 - Redis 服務已開啟

redis源碼分析?程序執行到 4133 行時,Redis 服務已成功開啟了。此時服務器處于休眠狀態,并使用 aeMain() 進行事件輪詢,等待監聽事件的發生。

上述整個過程,我們只是跟著程序的運行,大概看了一遍執行流程。下面,我們來詳細解讀上面敘述的關鍵步驟:初始化基礎配置初始化服務器數據結構

3 初始化詳細解讀

3.1 初始化基礎配置

初始化服務器的第一步就是創建一個 `redisServer 類型的實例變量 server 作為服務器的狀態,并為結構中的各個屬性設置默認值。

void initServerConfig(void) {int j;// 設置服務器運行 IDgetRandomHexChars(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 函數來說,它主要完成以下主要工作:

  • 設置服務器的運行 ID。
  • 設置服務器的默認運行頻率。
  • 設置服務器的默認配置文件路徑。
  • 設置服務器的運行架構。
  • 設置服務器的默認端口號

initServerConfig 函數設置的服務器狀態屬性基本上都是一些整數、浮點數或者字符串屬性。除了命令吧之外,initServerConfig 函數沒有創建服務器狀態的其它數據結構。像數據庫、慢查詢日志、Lua 環境、共享對象等這些數據結構是在之后的步驟中創建的。

redis實戰,當初始化基礎配置參數后,下一步就要開始載入配置選項

3.2 載入配置選項

在啟動服務器時,用戶可以通過給定配置參數或者知道配置文件來修改服務器的默認配置。就像我們可以在啟動服務時指定端口:

# bash
./src/redis-server --port 7379

通過給定配置參數的方式,修改了服務器的運行端口號。

除了給定配置參數的方式,我們可以通過指定配置文件的形式啟動服務:

# bash
./src/redis-server ./redis.conf

通過指定配置文件的形式啟動服務時,我們實際上就是通過配置文件的形式修改了服務器的數據庫配置。

看完redis源碼需要多久,服務器在用 initServerConfig 函數初始完 server 變量后,就會開始載入用戶給定的配置參數和配置文件,并根據用戶設定的配置,對 server 變量相關屬性進行修改。

關于命令行指定配置、配置文件配置、默認配置,這三種配置中:

  • 如果有指定配置,服務器就是有用戶指定的值來更新對應的屬性。
  • 如果沒有指定值,則沿用 initServerConfig 函數設置的默認值。

3.3 初始化服務器數據結構

在執行 initServerConfig 函數初始化配置時,程序只創建了命令表一個數據結構,而服務器除了命令表還包括其他數據結構,比如:

  • server.clients 鏈表。這個鏈表記錄了所有與服務器相連的客戶端的狀態結構。鏈表的每個節點都包含了一個 RedisClient 結構實例。
  • server.db 數組。數組中包含了服務器所有的數據庫。
  • server.pubsub_channels 字典。字典中保存頻道訂閱信息。
  • server.pubsub_patterna 鏈表。鏈表中保存模式訂閱信息。
  • server.lua 屬性。用來執行 Lua 腳本。
  • server.slowlog 屬性。用來保存慢日志。

上述這些數據結構會在 initServer 函數為其分配內存,并在有需要時為這些數據結構設置或關聯初始化值。

之所以在載入用戶配置之后才初始化數據結構,就是因為服務器要先載入用戶的配置選項,才能根據選項正確的對數據結構進行初始化。避免再根據用戶配置修改數據結構相關屬性。

小程序源碼?所以,我們可以看出,服務器對狀態的初始化分為兩步進行:

  1. initServerConfig 函數是初始化一般屬性。
  2. initServer 初始化數據結構。

除了初始化數據結構之外,initServer 還進行了一些非常重要的設置操作,包括:

  • 為服務器設置進程信號處理器。
  • 創建共享對象。這些對象包含 Redis 服務器常用到的一些只,比如包含 "OK" 回復的字符串對象,包含 "ERR" 回復的字符串對象,包含整數 1 到 10000 的字符串對象等等。服務器正是通過重用這些共享對象來避免反復創建相同的對象,節約內存。
  • 打開服務器的監聽端口,并為監聽套接字關聯應答事件處理器,等待服務器正式運行時接受客戶端的連接。
  • 為服務器創建時間事件,等待服務器正是運行時執行 serverCron 函數。
  • 如果開啟了 AOF 持久化功能,打開現有的 AOF 文件。如果 AOF 文件不存在,就創建并打開新的 AOF 文件,為 AOF 寫入做好準備。
  • 初始化服務器的后臺 IO 模塊,為 IO 操作做好準備。

initServer 函數執行完畢之后,服務器將用 ASCII 字符在日志中打印出我們常見到的 Redis 圖標,以及 Redis 的版本號信息等。

圖 4 - 服務器啟動后打印的 Redis 圖標和版本信息等

4 其它操作

4.1 還原數據庫

在完成了對服務器狀態 server 變量的初始化之后,服務器需要載入 RDB 文件或者 AOF 文件(數據持久化保存文件),并根據文件記錄的內容來還原服務器的數據庫狀態。

有了源碼該如何使用、還原過程中,服務器會判斷是否啟用了 AOF 持久化功能:

  • 如果啟用了 AOF 持久化功能,服務器將使用 AOF 文件來還原數據庫狀態。
  • 如果沒有啟用 AOF,服務器使用 RDB 文件來還原數據庫狀態。

當服務器完成數據庫狀態還原工作之后,會在日志中打印出載入文件和還原數據庫狀態所耗費的時長。

8189:M 31 May 13:12:47.971 * DB loaded from disk: 0.000 seconds

4.2 執行事件循環

在初始化的最后一步,服務器將打印出以下日志:

8189:M 31 May 13:12:47.971 * The server is now ready to accept connections on port 8379

軟件源碼。并開始執行服務器的事件循環。

至此,服務器的初始化工作全部完成。

5 gdb 基礎使用

命令解釋示例
gdb file加載被調試的可執行程序文件gdb src/redis-server
rRun 的縮寫,運行被調試的程序。r ./redis.conf
cContinue 的縮寫。繼續執行被調試程序,直至下一個斷點或程序結束c
bBreakpoint 縮寫。設置斷點。可以使用 行號、函數名稱、執行地址等方式指定斷點位置b main
s/ns 相當于“單步跟蹤并進入”,也就是說進入到執行的函數內部。n 相當于“單步跟蹤”,不進入到執行函數內部s/n
p 變量名稱Print 縮寫。顯示指定變量的值。p server

總結

  1. 搭建環境三步走:下載、編譯、gdb。
  2. 服務啟動包括:初始化基礎配置、數據結構、對外提供服務的準備工作、還原數據庫、執行事件循環等。
  3. gdb 基礎命令:r c b n p。

轉載于:https://www.cnblogs.com/BeiGuo-FengGuang/p/11141761.html

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/154355.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息