MongoDB作為一款NoSQL數(shù)據(jù)庫(kù),常應(yīng)用在游戲開(kāi)發(fā)領(lǐng)域。 作為一個(gè)后端程序,進(jìn)行CRUD操作是家常便飯,但如果不看源碼,便不會(huì)知道MongoDB底層是如何實(shí)現(xiàn)的,對(duì)自己寫(xiě)的CRUD代碼,心里就沒(méi)譜,不確定哪一行代碼就把MongoDB給壓垮了。遇到問(wèn)題,不知道為啥MongoDB支撐不住,也就無(wú)從說(shuō)起該怎樣哪里優(yōu)化。
開(kāi)源MongoDB有上萬(wàn)個(gè)文件,代碼量百萬(wàn)行。閱讀MongoDB的源碼是一項(xiàng)具有挑戰(zhàn)的任務(wù)。一般的,我們可以從簡(jiǎn)單的、自己感興趣的模塊開(kāi)始閱讀。例如,先理解MongoDB在執(zhí)行一條find命令時(shí),是如何找到我們想要的結(jié)果。
本文,介紹如何編譯MongoDB源碼、如何用GDB調(diào)試MongoDB。
編譯安裝MongoDB
因?yàn)榫€上使用的是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。
首先是安裝依賴庫(kù)
apt-get install aptitude
aptitude install scons build-essential
aptitude install libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev
然后是通過(guò)scons命令進(jìn)行MongoDB的編譯安裝。
scons core install --disable-warnings-as-errors
參數(shù)core,說(shuō)明想要安裝的包括mongod,mongos, mongo。加入?yún)?shù)--disable-warnings-as-errors是為了忽略編譯過(guò)程的warning。
為了GDB能夠調(diào)試MongoDB,需要在編譯MongoDB時(shí),加入-g參數(shù)。不然在調(diào)試時(shí),會(huì)收到No symbol table is loaded的報(bào)錯(cuò)。通過(guò)查看SConstruct文件,已經(jīng)加了-ggdb參數(shù),所以我們就不需要做修改,直接執(zhí)行scons就可以。
編譯安裝后,會(huì)在build/install/bin目錄下,生成可執(zhí)行文件:mongo,mongod,mongos,mongoperf。
啟用GDB調(diào)試
首先啟動(dòng)mongod。在build/install/bin目錄下,執(zhí)行 ./mongod啟動(dòng)mongod。
啟動(dòng)后,可以用ps -ef | grep mongod查看mongod的進(jìn)程號(hào),然后用ps -p 進(jìn)程號(hào) -T查看mongod創(chuàng)建的線程信息。
啟動(dòng)mongo。在build/install/bin目錄下,執(zhí)行./mongo啟動(dòng)mongo,使之直接連接mongod。
啟動(dòng)后,再次用ps -p 進(jìn)程號(hào) -T查看mongod創(chuàng)建的線程信息。這時(shí),會(huì)發(fā)現(xiàn)多了一個(gè)conn1線程。這個(gè)線程,是mongod為一個(gè)客戶端創(chuàng)建的。
在build/install/bin目錄下,啟動(dòng)GDB,attach到mongod進(jìn)程
gdb ./mongod 進(jìn)程號(hào)
mongod是多線程,我們這里只關(guān)心處理客戶端請(qǐng)求的線程。所以,先要切到相應(yīng)的線程中。
使用info threads命令, 顯示當(dāng)前可調(diào)試的所有線程,每個(gè)線程會(huì)有一個(gè)GDB為其分配的ID,后面操作線程的時(shí)候會(huì)用到這個(gè)ID。 前面有*的是當(dāng)前調(diào)試的線程
可以看到,處理客戶端連接的線程,在GDB的編號(hào)是21,用thread 21切換到對(duì)應(yīng)的線程中。
我們以find命令為例,介紹如何用GDB進(jìn)行斷點(diǎn)調(diào)試。
斷點(diǎn)調(diào)試的第一步,就是加斷點(diǎn)。這就需要找到find的入口在哪里,即在哪個(gè)文件的哪一行。 我們一般可以先快速過(guò)一遍mongod的源碼結(jié)構(gòu)。在src/mongo/db/commands發(fā)現(xiàn)了大量以命令命名的文件。通過(guò)簡(jiǎn)單分析我們有理由相信,find_cmd.cpp的run函數(shù),就是find的入口。
確定了行號(hào)之后,就可以用gdb命令加斷點(diǎn)了:
b src/mongo/db/commands/find_cmd.cpp:230
加好斷點(diǎn)后,我們?cè)趩?dòng)的mongo進(jìn)程中,觸發(fā)find命令。
觸發(fā)命令后,我們?cè)趃db會(huì)話中,輸入c告訴gdb繼續(xù)執(zhí)行,直到遇到我們?cè)O(shè)置的斷點(diǎn)。
現(xiàn)在gdb已經(jīng)定在了我們?cè)O(shè)置的斷點(diǎn)中,下面可以利用gdb的其他命令,如命令s,n等,跟蹤學(xué)習(xí)mongo的find命令實(shí)現(xiàn)了。
如果不幸的,我們沒(méi)法通過(guò)源碼發(fā)現(xiàn)find命令的入口,則只能借助gdb使用更暴力一些的辦法。 在前文介紹的步驟中,當(dāng)gdb關(guān)聯(lián)到客戶端的線程后,直接執(zhí)行bt命令,看看現(xiàn)在的調(diào)用棧。
可以看到,線程在等待客戶端數(shù)據(jù),對(duì)應(yīng)的文件是sock.cpp:692。
通過(guò)閱讀源碼,我們直接在sock.cpp:697加斷點(diǎn),客戶端發(fā)起find請(qǐng)求,然后一步步調(diào)試,就能進(jìn)入find的入口。
用Docker編譯調(diào)試mongod
編譯安裝mongod在不同的環(huán)境,會(huì)遇到各種奇葩的問(wèn)題。我們?yōu)榱擞肎DB調(diào)試mongod,可能會(huì)花大量時(shí)間在解決環(huán)境配置上。為了解決煩惱的環(huán)境問(wèn)題,這里提供一個(gè)dockerfile,用docker可以完美的解決環(huán)境問(wèn)題。
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++






