1、背景
MongoDB是主流的NoSQL数据库,其弱化schema的文档存储方式在开发中可以提供很大的便利性,很多项目都将其作为主数据库存储重要的业务数据。作为经常和MongoDB打交道的开发有必要了解其底层原理,这有助于更好的去使用它。本系列会从源码层面介绍MongoDB底层实现,要读懂源码,编译调试是必不可少的,本文先从搭建MongoDB调试环境开始介绍。
2、编译环境
本文介绍的是Ubuntu平台MongoDB编译环境搭建。
2.1 版本说明
本文使用的系统或工具版本号:
- ubuntu:20.04
- gcc:9.4.0
- make:4.2.1
- python:2.7.18
- mongo:4.0
- wiredtiger:3.1.1
2.2 依赖工具
执行命令如下:
apt update apt install -y vim apt install -y zip # scon使用git获取mongo源码仓库版本 apt install -y git # 安装gcc、g++、make等工具 apt install -y build-essential # 安装setuptools需要的依赖 apt install -y zlib1g apt install -y zlib1g-dev # 安装libcurl devel apt install -y libcurl4-gnutls-dev # 安装ssl,安装mongo依赖时需要 apt install -y libbz2-dev libffi-dev libssl-dev # 安装boost apt install -y libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev
2.3 安装python
下载源码编译安装python,mongo 4.0需要python2.7,本文选择版本2.7.18,下载地址为:https://www.python.org/downloads/。执行命令如下:
wget https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tgz tar -zxf Python-2.7.18.tgz cd Python-2.7.18 ./configure make make install
2.4 安装setuptools
安装setuptools用于安装pip,下载地址为:https://pypi.python.org/pypi/setuptools。执行命令如下:
wget https://pypi.python.org/packages/45/29/8814bf414e7cd1031e1a3c8a4169218376e284ea2553cc0822a6ea1c2d78/setuptools-36.6.0.zip#md5=74663b15117d9a2cc5295d76011e6fd1 unzip setuptools-36.6.0.zip cd setuptools-36.6.0 python setup.py install
2.5 安装pip
安装pip用于安装mongo依赖,下载地址为:https://pypi.python.org/pypi/pip。执行命令如下:
wget https://pypi.python.org/packages/11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/pip-9.0.1.tar.gz#md5=35f01da33009719497f01a4ba69d63c9 tar -zxf pip-9.0.1.tar.gz cd pip-9.0.1 python setup.py install
2.6 编译mongo源码
本文使用mongo 4.0,对应的wiredtiger引擎版本为3.1.1,执行命令如下:
git clone https://github.com/mongodb/mongo.git git checkout -B v4.0 origin/v4.0 cd mongo # 安装依赖 python -m pip install -r buildscripts/requirements.txt # 禁用warning作为error python buildscripts/scons.py core --disable-warnings-as-errors # 创建db目录 mkdir -p /data/db # 启动mongod服务 ./mongod --directoryperdb # gdb调试mongod apt install -y gdb gdb -p {mongod pid}
2.7 容器环境
以上步骤执行完后即可开始调试mongod服务了,不过因为每个人的环境差异,可能会遇到各种奇葩问题,为减少搭建环境的时间,可以采用容器来制作固定的调试环境,本文后续也是通过容器环境来调试mongo。制作镜像可以提前将依赖工具下载好放入目录,如下所示:
- 构建镜像脚本sh
#!/bin/sh docker build -t mongo-debug:1.0 -f ./Dockerfile .
- 制作镜像Dockerfile
FROM ubuntu:20.04 WORKDIR /data ADD ./ ./ # 安装依赖 RUN apt update && \ apt install -y vim && \ apt install -y git && \ apt install -y build-essential && \ apt install -y zlib1g && \ apt install -y zlib1g-dev && \ apt install -y zip && \ apt install -y libcurl4-gnutls-dev && \ apt install -y libbz2-dev libffi-dev libssl-dev && \ apt install -y libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev # 安装python RUN tar -zxf Python-2.7.18.tgz && \ cd Python-2.7.18 && \ ./configure && \ make && \ make install # 安装setuptools # 下载地址:https://pypi.python.org/pypi/setuptools # wget https://pypi.python.org/packages/45/29/8814bf414e7cd1031e1a3c8a4169218376e284ea2553cc0822a6ea1c2d78/setuptools-36.6.0.zip#md5=74663b15117d9a2cc5295d76011e6fd1 RUN unzip setuptools-36.6.0.zip && \ cd setuptools-36.6.0 && \ python setup.py install # 安装pip # 下载地址:https://pypi.python.org/pypi/pip # wget https://pypi.python.org/packages/11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/pip-9.0.1.tar.gz#md5=35f01da33009719497f01a4ba69d63c9 RUN tar -zxf pip-9.0.1.tar.gz && \ cd pip-9.0.1 && \ python setup.py install # 编译mongo RUN cd mongo && \ python -m pip install -r buildscripts/requirements.txt && \ python buildscripts/scons.py core --disable-warnings-as-errors CMD python pause.py
- MongoDB源码目录mongo
- 启动容器脚本sh
#/bin/sh docker run --privileged -it -d -v /data/datacenter/mongodb:/data/db mongo-debug:1.0
注意:需要带--privileged提供内核参数的更改权限,否则无法通过gdb调试,会报错Could not attach to process, ptrace: Operation not permitted。
- 容器常驻脚本py
import time time.sleep(9999999)
3、gdb使用
Linux系统中常用的调试工具是gdb,后面我们也会采用gdb调试mongod,因此有必要熟悉gdb的用法。
首先是安装gdb,可以执行命令apt install -y gdb。
针对已经启动的服务,gdb可以通过指定pid attach到该服务,执行命令gdb -p {pid}。
进入gdb后,常用命令如下:
info threads //显示所有线程信息 thread {tid} //切换到指定线程 info breakpoints //简写info b,显示所有断点 break {函数名} //简写b {函数名},如:b mongo::BasicCommand::Invocation::run、b mongo::(anonymous namespace)::FindCmd::run break {文件名:行数} //简写b {文件名:行数},如:b src/mongo/db/commands.cpp:517 continue //简写c,执行到下一断点 next //简写n,单步跟踪,遇到函数调用不进入函数体 step //简写s,单步跟踪,遇到函数调用进入函数体 delete {n} //删除第n个断点 delete breakpoints //删除所有断点 list //简写l,列出执行点附件源码 print {变量名} //打印变量值 set scheduler-locking on //调试加锁当前线程,停止所有其他线程调度,在多线程情况下很有用,防止调试过程中切换到其他线程 show scheduler-locking //显示线程的scheduler-locking状态,查看上面设置是否生效
4、调试mongo
4.1 启动mongod
编译mongo源码后会生成mongo、mongod,可以执行如下命令启动mongod服务:
mkdir -p /data/db && /data/mongo/mongod --directoryperdb #directoryperdb表示每个db单独目录
启动mongod后,可以通过ps -ef|grep mongod查看mongod进程:
4.2 查看线程信息
mongod服务会启动多个线程,可以通过ps -p {pid} -T查看线程情况:
当通过mongo客户端连接mongod后,再次查看线程情况:
进入gdb也可以查看mongod的线程相关信息,以下分别为显示所有线程、切换指定线程:
4.3 CURD调试
本文接下来会介绍单步跟踪CURD的调用栈。
4.3.1 Insert
单步跟踪insert语句的调用栈如下:
由调用栈可以看到class RecordStore提供了抽象的底层数据操作接口,由各存储引擎具体实现,MongoDB默认使用的是WiredTiger存储引擎,insert最终会调用wt引擎的insertRecords()进行实际的插入操作。
调用链如下:
src/mongo/db/service_entry_point_common.cpp/mongo::ServiceEntryPointCommon::handleRequest() //处理请求 =>src/mongo/db/service_entry_point_common.cpp/mongo::receivedCommands() =>src/mongo/db/service_entry_point_common.cpp/mongo::execCommandDatabase() =>src/mongo/db/service_entry_point_common.cpp/mongo::runCommandImpl() =>src/mongo/db/commands/write_commands/write_commands.cpp/mongo::WriteCommand::InvocationBase::run() =>src/mongo/db/commands/write_commands/write_commands.cpp/mongo::CmdInsert::Invocation::runImpl() =>src/mongo/db/ops/write_ops_exec.cpp/mongo::performInserts() =>src/mongo/db/ops/write_ops_exec.cpp/mongo::insertBatchAndHandleErrors() =>src/mongo/db/ops/write_ops_exec.cpp/mongo::insertDocuments() =>src/mongo/db/catalog/collection.h/mongo::Collection::insertDocuments() =>src/mongo/db/catalog/collection_impl.cpp/mongo::CollectionImpl::insertDocuments() =>src/mongo/db/catalog/collection_impl.cpp/mongo::CollecctionImpl::_insertDocuments() =>src/mongo/db/storage/record_store.h/mongo::RecordStore::insertRecords() //class RecordStore提供抽象存储接口,底层由存储引擎实现 =>src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp/mongo::WiredTigerRecordStore::insertRecords() //调用wt存储引擎
4.3.2 Update
单步跟踪update语句的调用栈如下:
由调用栈可以看到update最终会调用wt引擎的updateRecord()进行实际的更新操作,源码中执行update时是需判断能否inplace update,进而采用不同的策略,此处调用栈是非inplace update。
调用链如下:
src/mongo/db/service_entry_point_common.cpp/mongo::ServiceEntryPointCommon::handleRequest() =>src/mongo/db/service_entry_point_common.cpp/mongo::receivedCommands() =>src/mongo/db/service_entry_point_common.cpp/mongo::execCommandDatabase() =>src/mongo/db/service_entry_point_common.cpp/mongo::runCommandImpl() =>src/mongo/db/commands/write_commands/write_commands.cpp/mongo::WriteCommand::InvocationBase::run() =>src/mongo/db/commands/write_commands/write_commands.cpp/mongo::CmdUpdate::Invocation::runImpl() =>src/mongo/db/ops/write_ops_exec.cpp/mongo::performUpdates() =>src/mongo/db/ops/write_ops_exec.cpp/mongo::performSingleUpdateOp() =>src/mongo/db/query/plan_executor.cpp/mongo::PlanExecutor::executePlan() =>src/mongo/db/query/plan_executor.cpp/mongo::PlanExecutor::getNext() =>src/mongo/db/query/plan_executor.cpp/mongo::PlanExecutor::getNextImpl() =>src/mongo/db/exec/plan_stage.cpp/mongo::PlanStage::work() =>src/mongo/db/exec/update.cpp/mongo::UpdateStage::doWork() =>src/mongo/db/exec/update.cpp/mongo::UpdateStage::transformAndUpdate() =>src/mongo/db/catalog/collection_impl.cpp/mongo::CollectionImpl::updateDocument() =>src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp/mongo::WiredTigerRecordStore::updateRecord() //调用wt存储引擎
4.3.3 Find
单步跟踪find语句的调用栈如下:
由调用栈可以看到find最终会调用wt引擎进行迭代查找。
调用链如下:
src/mongo/db/service_entry_point_common.cpp/mongo::ServiceEntryPointCommon::handleRequest() =>src/mongo/db/service_entry_point_common.cpp/mongo::receivedCommands() =>src/mongo/db/service_entry_point_common.cpp/mongo::execCommandDatabase() =>src/mongo/db/service_entry_point_common.cpp/mongo::runCommandImpl() =>src/mongo/db/commands.cpp/mongo::BasicCommand::Invocation::run() =>src/mongo/db/commands/find_cmd.cpp/mongo::FindCmd::run() =>src/mongo/db/query/plan_executor.cpp/mongo::PlanExecutor::getNext() =>src/mongo/db/query/plan_executor.cpp/mongo::PlanExecutor::getNextImpl() =>src/mongo/db/exec/plan_stage.cpp/mongo::PlanStage::work() =>src/mongo/db/exec/collection_scan.cpp/mongo::CollectionScan::doWork() =>src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp/mongo::WiredTigerRecordStoreCursorBase::next() //调用wt存储引擎
4.3.4 Delete
单步跟踪delete语句的调用栈如下:
由调用栈可以看到delete最终会调用wt引擎的deleteRecord进行删除。
调用链如下:
src/mongo/db/service_entry_point_common.cpp/mongo::ServiceEntryPointCommon::handleRequest() =>src/mongo/db/service_entry_point_common.cpp/mongo::receivedCommands() =>src/mongo/db/service_entry_point_common.cpp/mongo::execCommandDatabase() =>src/mongo/db/service_entry_point_common.cpp/mongo::runCommandImpl() =>src/mongo/db/commands/write_commands/write_commands.cpp/mongo::WriteCommand::InvocationBase::run() =>src/mongo/db/commands/write_commands/write_commands.cpp/mongo::CmdDelete::Invocation::runImpl() =>src/mongo/db/ops/write_ops_exec.cpp/mongo::performDeletes() =>src/mongo/db/ops/write_ops_exec.cpp/mongo::performSingleDeleteOp() =>src/mongo/db/query/plan_executor.cpp/mongo::PlanExecutor::executePlan() =>src/mongo/db/query/plan_executor.cpp/mongo::PlanExecutor::getNext() =>src/mongo/db/query/plan_executor.cpp/mongo::PlanExecutor::getNextImpl() =>src/mongo/db/exec/plan_stage.cpp/mongo::PlanStage::work() =>src/mongo/db/exec/delete.cpp/mongo::DeleteStage::doWork() =>src/mongo/db/exec/delete.cpp/mongo::Collection::deleteDocument() =>src/mongo/db/catalog/collection_impl.cpp/mongo::CollectionImpl::deleteDocument() =>src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp/mongo::WiredTigerRecordStore::deleteRecord() //调用wt存储引擎