暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MongoDB3.0 新功能详情及部分源码对照

suger 2023-05-10
212


更多关于MongoDB的技术分享请关注公众号:mongodb_side

原创文章 部分内容取自官方Release Notes(3.0-rc8)

作者 shingo(6623662005@163.com)



原计划12底发布的MongoDB2.8直接改为在3月份发布3.0了,原因是WiredTiger团队的加入让MongoDB的各项性能指标有了质的飞跃,最大的改变就是引入了WiredTiger存储引擎并带来了行级锁。


MongoDB的CTO Eliot Horowitz连发3篇博客来说明为什么直接从2.6跳到3.0。大致意思是本来打算发布2.8版本,后来因为加入wiredTiger引擎,做着做着越来越觉得牛X,他认为这些牛X的东西配得上提升主版本号。


他在博客里说到了下面这句话,足以证明3.0是一个里程碑似的版本。

By drawing on both academic research and their extensive commercial experience, the WiredTiger team built a storage engine that can underpin the next 20 years of data storage applications.


接下来详细介绍本次更新,有两个点配上了源码说明,受限于篇幅原因,以及手机上糟糕的代码阅读体验,有很多源码没有贴上来,将来会有专门的文章来介绍一些值得说的关键点。


主要更新


可拔插的存储引擎接口

MongoDB 3.0 对外提供可拔插的存储引擎接口,允许将第三方存储引引擎接入到MongoDB。


WiredTiger

MongoDB 3.0 引入了WiredTiger存储引擎,目前MongoDB原生包含两种存储引擎:

  • MMAPv1, 之前版本使用的存储引擎,也是唯一的存储引擎。

  • WiredTiger, 只支持MongoDB 3.0 64位版本。


WiredTiger用法

MongoDB 3.0仍然使用MMAPv1做为默认的存储引擎。WiredTiger支持所有的MongoDB特性,要切换到WiredTiger需要在启动mongod进程时声明, 如下:

mongod --storageEngine wiredtiger


在同一套复制集环境或分版中,允许各成员使用不同的存储引擎,但是性能不能保证,不同的生产环境负载下表现都会不同。

使用3.0的mongo shell或官方最新的客户端驱动包来连接开启了WiredTiger存储引擎的库。目前支持的各语言第三方包版本如下:

语言版本
C1.1.0
C++1.0.0
C#1.10
Java2.13
Node.js1.4.18
Perl0.708.0.0
PHP1.5
Python3.0
Motor
0.4
Ruby1.12
Scala
3.0


WiredTiger在磁盘上存储的索引格式已经升级到3.0-rc5,如果你使用了rc5之前的候选版操作过数据,升级到最新存储格式只需在启动 mongod 进程时加上 –repair。


WiredTigern属性配置

可以在启动mongod进程时将wiredTiger相关的信息声明在命令行里,比如

mongod--storageEngine wiredtiger --wiredTigerJournalCompressor snappy --wiredTigerCacheSizeGB2 --wiredTigerCheckpointDelaySecs 50


上面启动命令的意思是使用wiredtiger,将journal文件用snappy压缩,为数据创建2G的缓存空间,数据刷到磁盘文件的时间间隔为50秒。

这些配置可以按照MongoDB推荐的做法写在一个配置文件中,启动mongod进程时指定配置文件。


关于上边的wiredTigerCacheSizeGB多说一句,当使用最近比较火的Docker或类似的容器来部署MongoDB时,这个值要低于容器可使用的内存大小。


WiredTiger 并发和压缩

WiredTiger存储引擎提供行级锁和数据压缩功能。

3.0之前的所有版本,锁的粒度最多到db级,行级锁把MongoDB带到了一个崭新的层面,这意味着可以并行的对同一张表里的N条数据做不同的读写操作,这在以前是不敢想像的,2.6版本时大家都在等待表级锁,wiredtiger直接将MongoDB带到了彼岸。

wiredtiger对表的数据默认使用snappy库进行压缩。而对于索引,默认使用前辍压缩。


MMAPv1存储引擎改进

MMAPv1从3.0版开始支持表级锁。

为了支持多种存储引擎并存,3.0版对一些MMAPv1的配置做了重命名,具体如下

旧属性名
新属性名
storage.journal.commitIntervalMsstorage.mmapv1.journal.commitIntervalMs
storage.journal.debugFlagsstorage.mmapv1.journal.debugFlags
storage.nsSizestorage.mmapv1.nsSize
storage.preallocDataFilesstorage.mmapv1.preallocDataFiles
storage.quota.enforced storage.mmapv1.quota.enforced
storage.quota.maxFilesPerDBstorage.mmapv1.quota.maxFilesPerDB
storage.smallFilesstorage.mmapv1.smallFiles
storage.syncPeriodSecsstorage.mmapv1.syncPeriodSecs


MMAPv1 空间分配策略修改

mmapv1默认的空间分配策略变更为powerOf2Sizes,之前为paddingfactor。如果不想采用任何padding策略可以在创建表时将表设置为noPadding。

powerOf2Size是MongoDB中非常重要的存储空间使用策略,关于powerOf2Size相关代码如下

// 先定义好一系列以2的n次方为结果的数据

// record_store_v1_base.h

public:

static const int Buckets = 26;

static const int MaxAllowedAllocation =16*1024*1024 + 512*1024;

static const int bucketSizes[];

enum UserFlags {

Flag_UsePowerOf2Sizes = 1 <<0,

Flag_NoPadding = 1 << 1,

};

// (略去其它代码)…



// record_store_v1_base.app

const int RecordStoreV1Base::bucketSizes[] = {

0x20, 0x40, 0x80, 0x100, // 32, 64, 128, 256

0x200, 0x400, 0x800, 0x1000, // 512, 1K, 2K, 4K

0x2000, 0x4000, 0x8000, 0x10000, // 8K, 16K, 32K, 64K

0x20000, 0x40000, 0x80000, 0x100000, // 128K, 256K, 512K, 1M

0x200000, 0x400000, 0x600000, 0x800000, // 2M, 4M, 6M, 8M

0xA00000, 0xC00000, 0xE00000, // 10M, 12M, 14M,

MaxAllowedAllocation, // 16.5M

MaxAllowedAllocation + 1, // Only MaxAllowedAllocation sized records go here.

INT_MAX, // "oversized" bucket for unused parts of extents.

};




接下来截取分配空间的关键代码

// record_store_v1_simple.h

// 在这里返回shouldPadInserets(),表示是否需要对空间做冗余处理

virtual bool shouldPadInserts() const { return !_details->isUserFlagSet(Flag_NoPadding); }



// record_store_v1_base.app

// 注意下边标红部分的关键代码

Status RecordStoreV1Base::setCustomOption( OperationContext* txn,

const BSONElement& option,

BSONObjBuilder* info ) {

const StringData name = option.fieldNameStringData();

const int flag = (name == "usePowerOf2Sizes") ? Flag_UsePowerOf2Sizes :

(name == "noPadding") ? Flag_NoPadding :

0;

if (flag) {

bool oldSetting = _details->isUserFlagSet(flag);

bool newSetting = option.trueValue();

// (略去其它代码)…


// record_store_v1_base.app

int RecordStoreV1Base::quantizeAllocationSpace(int allocSize) {

invariant(allocSize <= MaxAllowedAllocation);

// 减2去掉两个高位

for ( int i = 0; i < Buckets - 2; i++ ) {

if ( bucketSizes[i] >= allocSize ) {

Return the size of the first bucket sized >= the requested size.

return bucketSizes[i];

}

}

invariant(false); prior invariant means we should find something.

}


以上代码描述了powerOf2Size的逻辑,简而言之就是将insert来的数据和2的n次方比大小,最接近哪个值就分配多少空间,这种策略主要是防止数据修改引起的data move。


下面截取了Redis字符串空间扩展的代码做为对比,原理是:当发生Append操作导致字符串超过原有空间时,新开辟的空间大小为

n = (原长度 + 增加的长度)*2 + 1

// sds.c

sds sdsMakeRoomFor(sds s, size_t addlen) {

struct sdshdr *sh, *newsh;

size_t free = sdsavail(s);

size_t len, newlen;


if (free >= addlen) return s;

len = sdslen(s);

sh = (void*) (s-(sizeof(struct sdshdr)));


// 新空间长度为原长度加新增长度

newlen = (len+addlen);

// 这个SDS_MAX_PREALLOC为定长1M

字符串长度超过1M后就按照2倍增加了,只增加1M

if (newlen < SDS_MAX_PREALLOC)

newlen *= 2;

else

newlen += SDS_MAX_PREALLOC;

newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);

if (newsh == NULL) return NULL;


newsh->free = newlen - len;

return newsh->buf;

}


复制集


增加复制集成员数量

3.0版的复制集最大可以支持50个成员,之前的版本不能超过12个成员,最多只能有7个可投票成员。

相关信息可以参见我之前的文章《MongoDB的限制和门槛标准》

以下驱动支持大复制集:

  • C# (.NET) Driver 1.10

  • Java Driver 2.13

  • Python Driver (PyMongo) 3.0+

  • Ruby Driver 2.0+

  • Node.JS Driver 2.0+


replSetStepDown操作更新

replSetStepDown命令用来将复制集中当前主节点降级为从节点,现在的replSetStepDown命令

db.runCommand( { replSetStepDown:<seconds> , secondaryCatchUpPeriodSecs: <seconds>, force:<true|false> } )


各参数说明如下

参数名类型描述
replSetStepDown数值可选。这个值的单位是秒,意思是在指定的秒级时间内防止当前被replSetDown的节点重新竞选为主节点。如果未设置这个参数,默认为60秒。
force布尔可选。2.0版之后的新参数,强制将主节点降级为从节点,即使当前没有合适的从节点(比如没有任何从节点数据同步完成)
secondaryCatchupPeriodSecs 数值

可选。在replSetDown命令发出后,可以容忍从节点赶上主节点数据进度的时间。如果时间截止时仍然没有任何从节点同步完最新数据,那么此次replSetDown失败,除非指定了force : true。 这个参数默认值为10,如果没有指定force:true,默认值为0。


另外,在replSetStepDown命令发出后,MongoDB会试图终止一些长时间执行的非系统操作,例如创建索引或map/reduce任务。


复制集的其它更改

  • 初始化同步时创建索引更有效率,使用多线程批量同步oplog数据。

  • w: “majority”从原先的“大多数节点”更改为“大多数可投票节点”。

  • 对Replica Set实行更加严格的配置,集中在一些细的属性调整上比如投票数,名称等。

  • 2.6.6及以前的版本不能以3.0版的成员为初始化同步目标,即不要将2.6.6版本的节点接入到含3.0版成员的复制集中。

  • 对于已经存在于子节点上的表, MongoDB 3.0不会再自动创建_id索引。


关于w:majority的源码解析在将来我会单写一篇来说明,MongoDB针对getLastError的响应主要用到了“链式同步”的方式。

篇幅有限,这里只贴一部分入口代码:

// write_concern.capp

Status waitForWriteConcern( OperationContext* txn,

const WriteConcernOptions& writeConcern,

const OpTime& replOpTime,

WriteConcernResult* result ) {

// (略去部分代码)…

result->syncMillis = syncTimer.millis();


Now wait for replication

if (replOpTime.isNull()) {

no write happened for this client yet

return Status::OK();

}


needed to avoid incrementing gleWtimeStats SERVER-9005

if (writeConcern.wNumNodes <= 1 && writeConcern.wMode.empty()) {

no desired replication check

return Status::OK();

}


// 这里的awaitReplication方法处理writeConcern的信息,按指定个数返回。

repl::ReplicationCoordinator::StatusAndDuration replStatus =

repl::getGlobalReplicationCoordinator()->awaitReplication(txn,replOpTime,writeConcern);


// replication_coordinator_impl.cpp

// _awaitReplication_inlock 通过一个while循环来处理复制集的子节点返回

// 这里只贴了方法签名,因篇幅原因,具体处理的代码太长,将来单写一篇具体描述复制集的GetLastError

ReplicationCoordinator::StatusAndDuration ReplicationCoordinatorImpl::_awaitReplication_inlock(

const Timer* timer,

boost::unique_lock<boost::mutex>* lock,

const OperationContext* txn,

const OpTime& opTime,

const WriteConcernOptions& writeConcern)


分片集群


MongoDB 3.0 对于分片集群有以下几个方面的增强:

  • 增加一个新的方法sh.removeTagRange() ,与原有的sh.addTagRange()方法一同来管理shard标签。

  • mongos在执行读操作时不会再将连接绑到复制集成员上,在每次操作完成后,连接会被归还到连接池中,所以每次读操作mongos都会根据当前的规则重新评定连接的目标机器。

  • 为balancer、moveChunk、cleanupOrphaned命令加上write concern设置,因为在chunk迁移时在复制集环境会有写入和同步的问题需要考虑。.

  • sh.status()现在会包含一些balancer的状态信息,这些信息将有助于排查故障,比如常会出的一各分片不均衡。新增的与balcancer相关的信息如下:

balancer:

Currently enabled: yes

Currently running: yes

Balancer lock taken at Wed Dec 10 2014 12:00:16 GMT+1100 (AEDT) by

Pixl.local:27017:1418172757:16807:Balancer:282475249

Collections with active migrations:

test.t2 started at Wed Dec 10 2014 11:54:51 GMT+1100 (AEDT)

Failed balancer rounds in last 5 attempts: 1

Last reported error: tag ranges not valid for: test.t2

Time of Reported error: Wed Dec 10 2014 12:00:33 GMT+1100 (AEDT)

Migration Results for the last 24 hours:

96 : Success

15 : Failed with error 'ns not found, should be impossible', from

shard01 to shard02


安全改进


MongoDB 3.0包含以下安全改进:

  • 增加SCRAM-SHA-1认证机制,这是一个混淆认证机制,它满足部署级别的challenge-response认证机制的需求。

  • 为Localhost Exception增加限制,限制连接只能在admin库中增加第一个用户,而之前的版本可以做任何操作。

  • 移除db.addUser()方法。

  • 用—sslAllowConnectionsWithoutCertificates代替—sslWeakCertificateValidation,相应的,如果放将放置到配置文件中,用net.ssl.allowConnectionsWithoutCertificates代替net.ssl.weakCertificateValidation,老的名称依然有效,但为了长期的兼容性考虑,建议使用3.0之后的新参数名。

  • 彻底不兼容2.4版本的用户模型,2.4的用户请升级到2.6或3.0。

3.0之前支持的认证机制如下

MONGODB-CRMongoDB实现的challenge-response认证机制
MONGODB-X509
MongoDB SSL证书认证
PLAIN
plain认证方式,只能在企业版使用
GSSAPI
gss-api,只能在企业版使用


其它改进


全新的db.collection.explain()方法

签名如下

db.collection.explain().<method(...)>


用法举例

db.products.explain().remove( { category: "apparel" }, { justOne: true } )


explain的返回值中主要包括三大块"queryPlanner", "executionStats", 和 "allPlansExecution",这也是和以前版本不同的。


日志增强

为了提高故障诊断时日志的可用率,MongoDB按模块将日志分类,3.0版定义了日志的系统级别和信息显示级别。日志显示格式如下:

<timestamp> <severity> <component> [<context>] <message>


第二个参数severity即系统日志级别,目前支持有:

级别描述
F
Fatal
EError
WWarning
IInfo
DDebug


举例说明

2014-11-03T18:28:32.450-0500 I NETWORK [initandlisten] waiting for connections on port 27017


工具增强

所有的MongoDB工具现在都用go语言来改写,并且做为单独的项目来维护。

  • 更小的二进制文件。

  • mongorestore 可以使用一个新的参数–numParallelCollections来指定同一时间内操作的表的数量。

  • 执行mongodump操作时,使用新参数-excludeCollection和--excludeCollectionsWithPrefix排除不需要的表。

  • mongorestore现在可以直接从标准输入中接收BSON格式的数据。

  • mongostat和mongotop可以通过加上—json参数来得到JSON格式的输出。

  • mongoimport、mongorestore和mongofiles现在也可以加上write concern设置,默认为majority。


索引

  • 当dropDatabase、dropIndexes、drop发生时,后台创建索引不再会自动中断。

  • 对于3.0版的mongod进程,如果在后台创建索引时进程终止,在mongod重新启动时这个后台创建索引的工作将会被拿到前台重新执行,这期间出了任何错误mongod进程都会退出。可以在命令行加上--noIndexBuildRetry来防止故障重启时重新创建索引。

  • 如果createIndexes命令中包含多个索引,只会扫一次表,如果其中有某 一个索引在前台创建那所有这条命令中的其它索引都会在前台创建。

  • 对于分片表,如果查询条件中包含shard key,索引可以覆盖查询。


查询增强

  • aggregate()框架 增加$dateToString运算符提供更方便的方式将日期转为字符串。

  • 增加$eq查询操作符,用来判断相等的场景 。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论