mongorocks引擎原理解析

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:

mongorocks 是基于著名的开源KV数据库RocksDB)实现的一个MongoDB存储引擎,借助rocksdb的优秀特性,mongorocks能很好的支持一些高并发随机写入、读取的应用场景。

MongoDB 与 mongorocks 的关系

rocks1

mongodb 支持多种引擎,目前官方已经支持了mmapv1、wiredtiger、in-Memory等,而mongorocks则是第三方实现的存储引擎之一(对应上图红框的位置)。

MongoDB KV存储引擎模型

MongoDB 从 3.0 版本 开始,引入了存储引擎的概念,并开放了 StorageEngine 的API 接口,为了方便KV存储引擎接入作为 MongoDB 的存储引擎,MongoDB 又封装出一个 KVEngine 的API接口,比如官方的 wiredtiger 存储引擎就是实现了 KVEngine 的接口,本文介绍的 mongorocks 也是实现了KVEngine的接口。

KVEngine 主要需要支持如下接口

创建/删除集合

MongoDB 使用 KVEngine 时,将所有集合的元数据会存储到一个特殊的 _mdb_catalog 的集合里,创建、删除集合时,其实就是往这个特殊集合里添加、删除元数据。

_mdb_catalog 特殊的集合不需要支持索引,只需要能遍历读取集合数据即可,MongoDB在启动时,会遍历该集合,来加载所有集合的元数据信息。

数据存储及索引

插入新文档时,MongoDB 会调用底层KV引擎存储文档内容,并生成一个 RecordId 的作为文档的位置信息标识,通过 RecordId 就能在底层KV引擎读取到文档的内容。

如果插入的集合包含索引(MongoDB的集合默认会有_id索引),针对每项索引,还会往底层KV引擎插入一个新的 key-value,key 是索引的字段内容,value 为插入文档时生成的 RecordId,这样就能快速根据索引找到文档的位置信息。

rocks2

如上图所示,集合包含{_id: 1}, {name: 1} 2个索引

  1. 用户插入文档时,底层引擎将文档内容存储,返回对应的位置信息,即 RecordId1
  2. 集合包含2个索引

    • 插入 {_id: ObjectId1} ==> RecordId1 的索引
    • 插入 {name: "rose"} ==> RecordId1 的索引

有了上述的数据,在根据_id访问时文档时 (根据其他索引字段类似)

  1. 根据文档的 _id 字段从底层KV引擎读取 RecordId
  2. 根据 RecordId 从底层KV引擎读取文档内容

mongorock 存储管理

_2016_12_16_6_01_01

mongorocks 存储数据时,每个key都会包含一个32位整型前缀,实际存储时将整型转换为big endian格式存储。

  • 所有的元数据的前缀都是0000
  • 每个集合、以及集合的每个索引都包含不同的前缀,集合及索引与前缀的关系存储在 0000metadata-*为前缀的key里
  • _mdb_catalog 在mongorocks也是一个普通的集合,有单独的前缀

创建集合、写数据

  1. 创建集合或索引时,mongrocks会为其分配一个前缀,并将对应关系持久化,比如创建集合 bar(默认会创建_id字段的索引),mongorocks 会给集合和索引各分配一个前缀,如上图所示的 0002, 0003,并将对应关系持久化。
  2. 接下来往bar集合里写的所有数据,都会带上 0002 前缀;
  3. 往其_id索引里写的数据都会带上前缀 0003

写索引时,有个比较有意思的设计,重点介绍下 (其他的key-value引擎,如wiredtiger也使用类似的机制)

MongoDB 支持复合索引,比如db.createIndex({a: 1, b, -1, c, 1}),这个索引要先按a字段升序、a相同的按b字段降序.. 依此类推,但KV引擎并没有这么强大的接口,如何实现对这种复合索引的支持呢?

MongoDB针对每个索引,会有一个位图来描述索引各个字段的排序方向,比如插入如下2条索引时 (key的部分会转换为BSON格式插入到底层)

{a: 100, b: 200, c: 300}  == > RecordId1
{a: 100, b: 300, c: 400}  ==> RecordId2  

插入到底层 RocksDB,第1条记录会排在第2条记录前面,但我们建立的索引是 {a: 1, b, -1, c, 1},按这个索引,第2条记录应该排在前面才对,否则索引顺序就是错误的。

mongorocks 在存储索引数据时,会根据索引的排序位图,如果方向是逆序(如b: -1),会把key的内容里将b字段对应的bit全部取反,这样在 RocksDB 里第2条记录就会排在第1条前面。

读取数据

根据_id来查找集合数据时,其他访问方式类似

  1. 根据集合的名字,在元数据里找到集合的前缀 0002 及其_id索引对应的前缀 0003
  2. 根据 0003 + 文档id 生成文档_id索引的key,并根据key读取出文档的RecordId
  3. 根据 0002 + RecordId 生成存储文档内容的key,并根据key读取出文档的内容

删除集合

  1. 将集合的元数据从_mdb_catalog移除
  2. 将集合及其索引与前缀的对应关系都删除掉
  3. 将第2步里删除的前缀加入到待删除列表,并通知 RocksDB 把该前缀开头的所有key通过compact来删除掉(通过定制CompactionFilter来实现,这个compact过程是异步做的,所以集合删了,会看到底层的数据量不会立马降下来),同时持久化一条0000droppedprefix-被删除前缀的记录,这样是防止compact被删除前缀的过程中宕机,重启后被删除前缀的key不会被会收掉,直到待删除前缀所有的key都被回收时,最终会把0000droppedprefix-被删除前缀的记录删除掉。

文档原子性

MongoDB 写入文档时,包含如下步骤

  1. 插入文档到集合
  2. 更新集合所有的索引
  3. 记录oplog(如果是复制集模式运行)

MongoDB 保证单文档的原子性,上述3个步骤必须全部成功应用或者全部不应用,mongorocks 借助 RocksDB 的 WriteBatch 接口来保证,将上述3个操作放到一个WriteBatch中,最后一次提交,RocksDB 层面会保证 WriteBatch 操作的原子性。

特殊的oplog

在MongoDB里,oplog是一个特殊的 capped collection(可以理解为环形存储区域),超过配置的大小后,会将最老的数据删除掉,如下是2个oplog的例子,mongorocks在存储oplog时,会以oplog集合前缀 + oplog的ts字段作为key来存储,这样在RocksDB,oplog的数据都是按ts字段的顺序来排序的。

0008:ts_to_uint64 ==> { "ts" : Timestamp(1481860966, 1), "t" : NumberLong(71), "h" : NumberLong("-6964295105894894386"), "v" : 2, "op" : "i", "ns" : "test.tt", "o" : { "_id" : ObjectId("58536766d38c0573d2ff5b90"), "x" : 2000 } }
0008:ts_to_uint64 ==> { "ts" : Timestamp(1481860960, 1), "t" : NumberLong(71), "h" : NumberLong("3883981042971627762"), "v" : 2, "op" : "i", "ns" : "test.tt", "o" : { "_id" : ObjectId("58536760d38c0573d2ff5b8f"), "x" : 1000 } }

capped collection 当集合超出capped集合最大值时,就会逐个遍历最先写入的数据来删除,直到空间降到阈值以下。

mongorocks 为了提升回收oplog的效率,做了一个小的优化。

针对oplog集合,插入的每一个文档,除了插入数据本身,还会往一个特殊的集合(该集合的前缀为oplog集合的前缀加1)里插入一个相同的key,value为文档大小。比如

0008:ts_to_uint64 ==> { "ts" : Timestamp(1481860966, 1), "t" : NumberLong(71), "h" : NumberLong("-6964295105894894386"), "v" : 2, "op" : "i", "ns" : "test.tt", "o" : { "_id" : ObjectId("58536766d38c0573d2ff5b90"), "x" : 2000 } }
0009:ts_to_uint64 ==> 88 (假设88为上面这个文档的大小)

有了这个信息,在删除oplog最老的数据时,就可以先遍历包含oplog文档大小信息的集合,获取被删除文档的大小,而不用把整个oplog的key-value都读取出来,然后统计大小。个人觉得这个优化当oplog文档大小比较大效果会比较好,文档小的时候并不一定能有效。

集合大小元数据管理

MongoDB 针对collection的count()接口,如果是全量的count,默认是O(1)的时间复杂度,但结果不保证准确。mongorocks 为了兼容该特性,也将每个集合的『大小及文档数』也单独的存储起来。

比如集合foo的大小、文档数分别对应2个key

0000datasize-foo ==> 14000  (0000是metadata的前缀)
0000numrecords-foo  ==> 100

上面2个key,当集合里有增删改查时,默认并不是每次都更新,而是累计到一定的次数或大小时更新,后台也会周期性的去更新所有集合对应的这2个key。

mongorocks 也支持每次操作都将 datasize、numrecords 的更新进行持久化存储,配置storage.rocksdb.crashSafeCounters参数为true即可,但这样会对写入的性能有影响。

数据备份

借助 RocksDB 本身的特性,mongorocks能很方便的支持对数据进行物理备份,执行下面的命令,就会将产生一份快照数据,并将对应的数据集都软链接到/var/lib/mongodb/backup/1下,直接拷贝该目录备份即可。

db.adminCommand({setParameter:1, rocksdbBackup: "/var/lib/mongodb/backup/1"})

总结

总体来说,MongoDB 存储引擎需要的功能,mongorocks 都实现了,但因为 RocksDB 本身的机制,还有一些缺陷,比如

  • 集合的数据删除后,存储空间并不是立即回收,RocksDB 要通过后台压缩来逐步回收空间
  • mongorcks 对 oplog 空间的删除机制是在用户请求路径里进行的,这样可能导致写入的延迟上升,应像 wiredtiger 这样当 oplog 空间超出时,后台线程来回收。
  • RocksDB 缺乏批量日志提交的机制,无法将多次并发的写log进行合并,来提升效率。
相关文章
|
2月前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
113 14
|
3月前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
134 3
|
6天前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
53 22
解析静态代理IP改善游戏体验的原理
|
4天前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
48 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
1月前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
|
2月前
|
存储 物联网 大数据
探索阿里云 Flink 物化表:原理、优势与应用场景全解析
阿里云Flink的物化表是流批一体化平台中的关键特性,支持低延迟实时更新、灵活查询性能、无缝流批处理和高容错性。它广泛应用于电商、物联网和金融等领域,助力企业高效处理实时数据,提升业务决策能力。实践案例表明,物化表显著提高了交易欺诈损失率的控制和信贷审批效率,推动企业在数字化转型中取得竞争优势。
120 16
|
2月前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
174 3
|
3月前
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
117 17
|
3月前
|
运维 持续交付 虚拟化
深入解析Docker容器化技术的核心原理
深入解析Docker容器化技术的核心原理
83 1
|
3月前
|
存储 供应链 算法
深入解析区块链技术的核心原理与应用前景
深入解析区块链技术的核心原理与应用前景
100 0

推荐镜像

更多