mongodb内核源码实现、性能调优、最佳运维实践系列-数百万行mongodb内核源码阅读经验分享
关于作者
前滴滴出行技术专家,现任OPPO 文档数据库 mongodb 负责人,负责 oppo 千万级峰值 TPS/ 十万亿级数据量文档数据库 mongodb 研发和运维工作,一直专注于分布式缓存、高性能服务端、数据库、中间件等相关研发。后续持续分享《 MongoDB 内核源码设计、性能优化、最佳运维实践》, Github 账号地址 : https://github.com/y123456yz
序言
Mongodb 内核源码由第三方库 third_party 和 mongodb 服务层源码组成,其中 mongodb 服务层代码在不同模块实现中依赖不同的third_party 库,第三方库是 mongodb 服务层代码实现的基础 ( 例如 : 网络底层 IO 实现依赖 asio-master 库 , 底层存储依赖 wiredtiger 存储引擎库 ) ,其中第三方库也会依赖部分其他库 ( 例如: wiredtiger 库依赖 snappy 算法库, asio-master 依赖 boost 库 ) 。
虽然Mongodb 内核源码数百万行,工程量巨大,但是 mongodb 服务层代码实现层次非常清晰,代码目录结构、类命名、函数命名、文件名命名都非常一目了然,充分体现了 10gen 团队的专业精神。
说明:mongodb 内核除第三方库 third_party 外的代码,这里统称为 mongodb 服务层代码。
本文以mongodb 服务层 transport 实现为例来说明如何快速阅读整个 mongodb 代码,我们在走读代码前,建议遵循如下准则。
1. 熟悉 mongodb 基本功能和使用方法
首先,我们需要熟悉mongodb 的基本功能,明白 mongodb 是做什么用的,用在什么地方,这样才能体现 mongodb 的真正价值。此外,我们需要提前搭建一个 mongodb 集群玩一玩,这样也可以进一步促使我们了解 mongodb 内部的一些常用基本功能。千万不要急于求成,如果连mongodb 是做什么的都不知道,或者连 mongodb 的运维操作方法都没玩过,直接读取代码会非常不适合,没有目的的走读代码不利于分析整个代码,同时阅读代码过程会非常痛苦。
2. 下载代码编译源码
熟悉了mongodb 的基本功能,并搭建集群简单体验后,我们就可以从 github 下载源码,自己编译源码生成二进制文件,编译文档存放于docs/building.md 代码目录中,源码编译步骤如下 :
1. 下载对应releases 中对应版本的源码
2. 进入对于目录,参考docs/building.md 文件内容进行相关依赖工具安装
3. 执行buildscripts/scons.py 编译出对应二进制文件,也可以直接 scons mongod mongos 这样编译。
4. 编译成功后的生产可执行文件存放于./build/opt/mongo/ 目录
在正在编译代码并运行的过程中,发现以下两个问题:
1. 编译出的二进制文件占用空间很大,如下图所示:
从上图可以看出,通过strip处理工具处理后,二进制文件大小已经和官方二进制包大小一样了。
2. 在一些低版本操作系统运行的时候出错,找不到对应stdlib库,如下图所示:
如上图所示,当编译出的二进制文件拷贝到线上运行后,发现无法运行,提示libstdc库找不到。原因是我们编译代码时候依赖的stdc库版本比其他操作系统上面的stdc库版本更高,造成了不兼容。
解决办法:编译的时候编译脚本中带上-static-libstdc++,把stdc库通过静态库的方式进行编译,而不是通过动态库方式。
3. 了解代码日志模块使用方法,试着加打印调试
由于前期我们对代码整体实现不熟悉,不知道各个接口的调用流程,这时候就可以通过加日志打印进行调试。Mongodb的日志模块设计的比较完善,从日志中可以很明确的看出由那个功能模块打印日志,同时日志模块有多种打印级别。
1. 日志打印级别设置
启动参数中verbose设置日志打印级别,日志打印级别设置方法如下:Mongod -f ./mongo.conf -vvvv
这里的v越多,表明日志打印级别设置的越低,也就会打印更多的日志。一个v表示只会输出LOG(1)日志,-vv表示LOG(1) LOG(2)都会写日志。
2. 如何在.cpp文件中使用日志模块记录日志
如果需要在一个新的.cpp文件中使用日志模块打印日志,需要进行如下步骤操作:
i) 添加宏定义 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kExecutor
ii) 使用LOG(N)或者log()来记录想要输出的日志内容,其中LOG(N)的N代表日志打印级别,log()对应的日志全记录到文件。
例如: LogComponent::kExecutor代表executor模块相关的日志,参考log_component.cpp日志模块文件实现,对应到日志文件内容如下:
4. 学会用gdb调试mongodb代码
Gdb是linux系统环境下优秀的代码调试工具,支持设置断点、单步调试、打印变量信息、获取函数调用栈信息等功能。gdb工具可以绑定某个线程进行线程级调试,由于mongodb是多线程环境,因此在用gdb调试前,我们需要确定调试的线程号,mongod进程包含的线程号及其对应线程名查看方法如下:
注意:在调试mongod工作线程处理流程的时候,不要选择adaptive动态线程池模式,因为线程可能因为流量低引起工作线程不饱和而被销毁,从而造成调试过程因为线程销毁而中断,synchronous线程模式是一个链接一个线程,只要我们不关闭这个链接,线程就会一直存在,不会影响我们理解mongodb服务层代码实现逻辑。 synchronous线程模式调试的时候可以通过mongo shell链接mongod服务端端口来模拟一个链接,因此调试过程相对比较可控。
在对工作线程调试的时候,发现gdb无法查找到mongod进程的符号表,无法进行各种gdb功能调试,如下图所示:
上述gdb无法attach到指定线程调试的原因是无法加载二进制文件符号表,这是因为编译的时候没有加上-g选项引起,mongodb通过SConstruct脚本来进行scons编译,要启用gdb功能需要在scons编译代码的时候指定gdbserver选项:scons --gdbserver=GDBSERVER -j 2。
编译出新的二进制文件后,就可以gdb调试了,如下图所示,可以很方便的定位到某个函数之前的调用栈信息,并进行单步、打印变量信息等调试:
5. 熟悉代码目录结构、模块细化拆分
在进行代码阅读前还有很重要的一步就是熟悉代码目录及文件命名实现,mongodb服务层代码目录结构及文件命名都有很严格的规范。下面以truansport网络传输模块为例,transport模块的具体目录文件结构:
从上面的文件分布内容,可以清晰的看出,整个目录中的源码实现文件大体可以分为如下几个部分:
- message_compressor_*网络传输数据压缩子模块
- service_entry_point*服务入口点子模块
- service_executor*服务运行子模块,即线程模型子模块
- service_state_machine*服务状态机处理子模块
- Session*回话信息子模块
- Ticket*数据分发子模块
- transport_layer*套接字处理及传输层模式管理子模块
通过上面的拆分,整个大的transport模块实现就被拆分成了7个小模块,这7个小的子模块各自负责对应功能实现,同时各个模块相互衔接,整体实现网络传输处理过程的整体实现,下面的章节将就这些子模块进行简单功能说明。
6. 从main入口开始大体走读代码
前面5个步骤过后,我们已经熟悉了mongodb编译调试以及transport模块的各个子模块的相关代码文件实现及大体子模块作用。至此,我们可以开始走读代码了,mongos和mongod的代码入口分别在mongoSMain()和mongoDbMain(),从这两个入口就可以一步一步了解mongodb服务层代码的整体实现。
注意:走读代码前期不要深入各种细节实现,大体了解代码实现即可,先大体弄明白代码中各个模块功能由那些子模块实现,千万不要深究细节。
7. 总结
本章节主要给出了数百万级mongodb内核代码阅读的一些建议,整个过程可以总结为如下几点:
- 提前了解mongodb的作用及工作原理。
- 自己搭建集群提前学习下mongodb集群的常用运维操作,可以进一步帮助理解mongodb的功能特性,提升后期代码阅读的效率。
- 自己下载源码编译二进制可执行文件,同时学会使用日志模块,通过加日志打印的方式逐步开始调试。
- 学习使用gdb代码调试工具调试线程的运行流程,这样可以更进一步的促使快速学习代码处理流程,特别是一些复杂逻辑,可以大大提升走读代码的效率。
- 正式走读代码前,提前了解各个模块的代码目录结构,把一个大模块拆分成各个小模块,先大体浏览各个模块的代码实现。
- 前期走读代码千万不要深入细节,捋清楚各个模块的大体功能作用后再开始一步一步的深入细节,了解深层次的内部实现。
- 从main()入口逐步开始走读代码,结合log日志打印和gdb调试。
- 跳过整体流程中不熟悉的模块代码,只走读本次想弄明白的模块代码实现。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
网易云音乐基于 Flink + Kafka 的实时数仓建设实践
简介: 本文由 网易云音乐实时计算平台研发工程师岳猛 分享,主要从以下四个部分 将为大家介绍 Flink + Kaf ka 在网易云音乐的应用实战 : 背景 Flink + Kafka 平台化设计 Kafka 在实时数仓中的应用 问题 & 改进 直播回放:https://developer.aliyun.com/live/2894 一、背景介绍 (一)流平台通用框架 目前流平台通用的架构一般来说包括消息队列、计算引擎和存储三部分,通用架构如下图所示。客户端或者 web 的 log 日志会被采集到消息队列;计算引擎实时计算消息队列的数据;实时计算结果以 Append 或者 Update 的形式存放到实时存储系统中去。 目前,我们常用的消息队列是 Kafka,计算引擎一开始我们采用的是 Spark Streaming,随着 Flink 在流计算引擎的优势越来越明显,我们最终确定了 Flink 作为我们统一的实时计算引擎。 (二)为什么选 Kafka? Kafka 是一个比较早的消息队列,但是它是一个非常稳定的消息队列,有着众多的用户群体,网易也是其中之一。我们考虑 Kafka 作为...
- 下一篇
HBase使用HashTable/SyncTable工具同步集群数据
复制(在上一篇博客文章中介绍)已经发布了一段时间,并且是Apache HBase最常用的功能之一。使集群与不同的对等方复制数据是非常常见的部署,无论是作为DR策略还是简单地作为在生产/临时/开发环境之间复制数据的无缝方式。尽管这是使不同的HBase数据库在亚秒级延迟内保持同步的有效方法,但是复制仅对启用该功能后所摄取的数据进行操作。这意味着复制部署中涉及的所有集群上的所有现有数据仍将需要以其他某种方式在同级之间进行复制。有很多工具可用于同步不同对等集群上的现有数据。Snapshots、BulkLoad、CopyTable是此类工具的知名示例,以前的Cloudera博客文章中都提到了这些示例。HashTable/SyncTable,详细介绍了它的一些内部实现逻辑,使用它的利弊以及如何与上述其他数据复制技术进行比较。 HashTable/SyncTable简介 HashTable/SyncTable是一种工具,实现为两个作为单独步骤执行的map-reduce作业。它看起来类似于CopyTable工具,该工具可以执行部分或全部表数据复制。与CopyTable不同,它仅在目标集群之间复制分散的...
相关文章
文章评论
共有0条评论来说两句吧...