MongoDB作為一款NoSQL數據庫,常應用在游戲開發領域。 作為一個后端程序,進行CRUD操作是家常便飯,但如果不看源碼,便不會知道MongoDB底層是如何實現的,對自己寫的CRUD代碼,心里就沒譜,不確定哪一行代碼就把MongoDB給壓垮了。遇到問題,不知道為啥MongoDB支撐不住,也就無從說起該怎樣哪里優化。
開源MongoDB有上萬個文件,代碼量百萬行。閱讀MongoDB的源碼是一項具有挑戰的任務。一般的,我們可以從簡單的、自己感興趣的模塊開始閱讀。例如,先理解MongoDB在執行一條find命令時,是如何找到我們想要的結果。
本文,介紹如何編譯MongoDB源碼、如何用GDB調試MongoDB。
編譯安裝MongoDB
因為線上使用的是3.4.24版本,所以本文也采用該版本作為例子。
首先下載MongoDB源碼
wget https://fastdl.mongodb.org/src/mongodb-src-r3.4.24.tar.gz
解壓縮
tar -zxvf mongodb-src-r3.4.24.tar.gz
在解壓的目錄中,docs/building.md介紹了如何編譯安裝MongoDB。
首先是安裝依賴庫
apt-get install aptitude
aptitude install scons build-essential
aptitude install libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev
然后是通過scons命令進行MongoDB的編譯安裝。
scons core install --disable-warnings-as-errors
參數core,說明想要安裝的包括mongod,mongos, mongo。加入參數--disable-warnings-as-errors是為了忽略編譯過程的warning。
為了GDB能夠調試MongoDB,需要在編譯MongoDB時,加入-g參數。不然在調試時,會收到No symbol table is loaded的報錯。通過查看SConstruct文件,已經加了-ggdb參數,所以我們就不需要做修改,直接執行scons就可以。
編譯安裝后,會在build/install/bin目錄下,生成可執行文件:mongo,mongod,mongos,mongoperf。
啟用GDB調試
首先啟動mongod。在build/install/bin目錄下,執行 ./mongod啟動mongod。
啟動后,可以用ps -ef | grep mongod查看mongod的進程號,然后用ps -p 進程號 -T查看mongod創建的線程信息。
啟動mongo。在build/install/bin目錄下,執行./mongo啟動mongo,使之直接連接mongod。
啟動后,再次用ps -p 進程號 -T查看mongod創建的線程信息。這時,會發現多了一個conn1線程。這個線程,是mongod為一個客戶端創建的。
在build/install/bin目錄下,啟動GDB,attach到mongod進程
gdb ./mongod 進程號
mongod是多線程,我們這里只關心處理客戶端請求的線程。所以,先要切到相應的線程中。
使用info threads命令, 顯示當前可調試的所有線程,每個線程會有一個GDB為其分配的ID,后面操作線程的時候會用到這個ID。 前面有*的是當前調試的線程
可以看到,處理客戶端連接的線程,在GDB的編號是21,用thread 21切換到對應的線程中。
我們以find命令為例,介紹如何用GDB進行斷點調試。
斷點調試的第一步,就是加斷點。這就需要找到find的入口在哪里,即在哪個文件的哪一行。 我們一般可以先快速過一遍mongod的源碼結構。在src/mongo/db/commands發現了大量以命令命名的文件。通過簡單分析我們有理由相信,find_cmd.cpp的run函數,就是find的入口。
確定了行號之后,就可以用gdb命令加斷點了:
b src/mongo/db/commands/find_cmd.cpp:230
加好斷點后,我們在啟動的mongo進程中,觸發find命令。
觸發命令后,我們在gdb會話中,輸入c告訴gdb繼續執行,直到遇到我們設置的斷點。
現在gdb已經定在了我們設置的斷點中,下面可以利用gdb的其他命令,如命令s,n等,跟蹤學習mongo的find命令實現了。
如果不幸的,我們沒法通過源碼發現find命令的入口,則只能借助gdb使用更暴力一些的辦法。 在前文介紹的步驟中,當gdb關聯到客戶端的線程后,直接執行bt命令,看看現在的調用棧。
可以看到,線程在等待客戶端數據,對應的文件是sock.cpp:692。
通過閱讀源碼,我們直接在sock.cpp:697加斷點,客戶端發起find請求,然后一步步調試,就能進入find的入口。
用Docker編譯調試mongod
編譯安裝mongod在不同的環境,會遇到各種奇葩的問題。我們為了用GDB調試mongod,可能會花大量時間在解決環境配置上。為了解決煩惱的環境問題,這里提供一個dockerfile,用docker可以完美的解決環境問題。
FROM debian:9
RUN apt-get install -y wget
RUN apt-get install -y vim
RUN apt-get install -y make
# 在Docker Debian容器中安裝ps,top等命令RUN apt-get install -y procps
RUN apt-get install -y gcc
RUN apt-get install -y g++