Tag Archives: mongodb

mongodb数据迁移2种方式比较

尝试了2种方式对数据进行迁移,一种是rsync,直接拉取数据;另一种是使用mongodump/mongorestore

1.rsync
操作步骤:
1.2:
[mongodb]
path = /data1/mongodb/data
hosts allow = 192.168.1.0/24
read only = no
write only = no
1.3:
rsync -avz root@192.168.1.2::mongodb/dbname /data/mongodb-linux-x86_64-1.8.1/data/
chown -R mongodb:mongodb /data/mongodb-linux-x86_64-1.8.1/data/

使用时间:50分钟
到目标服务器数据:50G
优点:使用时间短
缺点:需要配置rsync,数据占用的空间大(数据原封不动的拉取过来,包括碎片)

2.mongodump/mongorestore
操作步骤:
mongodump:
/data/PRG/mongodb/bin/mongodump –host 192.168.1.2:27017 -d dbname -uuername -ppasswd -o /data/mongodb-linux-x86_64-1.8.1/data/ –directoryperdb
mongorestore:
/data/mongodb-linux-x86_64-1.8.1/bin/mongorestore –dbpath /data/mongodb-linux-x86_64-1.8.1/data/ –directoryperdb /data/dbname/
chown -R mongodb:mongodb /data/mongodb-linux-x86_64-1.8.1/data/

使用时间:35(mongodump)+90(mongorestore)
到目标服务器数据:20G(需要的空间大大减小,拉取过程中相当于做了一次碎片整理)
优点:迁移到新服务器的数据经过了整理,需要空间大大减小
缺点:需要时间长

数据迁移时需要停mongo进行操作,而2种方式各有优缺点,如果可以忽略操作时间内的数据的话,那么使用第2种方式会比较好(已经有不少例子因为碎片带来严重的后果)

mongodb sharding cluster(分片集群)

MongoDB的auto-sharding功能是指mongodb通过mongos自动建立一个水平扩展的数据库集群系统,将数据库分表存储在sharding的各个节点上。

通过把Sharding和Replica Sets相结合,可以搭建一个分布式的,高可用性,自动水平扩展的集群。

要构建MongoDB Sharding Cluster,需要三种角色:

Shard Server: mongod 实例, 使用 Replica Sets,确保每个数据节点都具有备份、自动容错转移、自动恢复能力。用于存储实际的数据块,实际生产环境中一个shard server角色可由几台机器组个一个relica set承担,防止主机单点故障

Config Server: mongod 实例,使用 3 个配置服务器,确保元数据完整性(two-phase commit)。存储了整个 Cluster Metadata,其中包括 chunk 信息。

Route Server: mongos 实例,配合 LVS,实现负载平衡,提高接入性能(high performance)。前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。

环境如下:

192.168.198.131

shard1:10001

shard2:10002

shard3:10003

config1:20000

192.168.198.129

shard1:10001

shard2:10002

shard3:10003

config2:20000

192.168.198.132

shard1:10001

shard2:10002

shard3:10003

config3:20000

192.168.198.133

mongos:27017

分别在三台服务器上安装mongod服务,安装如下:

# wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.0.3.tgz

# tar zxvf mongodb-linux-x86_64-2.0.3.tgz -C ../software/

# ln -s mongodb-linux-x86_64-2.0.3 /usr/local/mongodb

# useradd mongodb

# mkdir -p /data/mongodb/shard1

# mkdir -p /data/mongodb/shard2

# mkdir -p /data/mongodb/shard3

# mkdir -p /data/mongodb/config1

配置shard1的replica set

192.168.198.131

# cd /usr/local/mongodb/bin

# ./mongod –shardsvr –replSet shard1 –port 10001 –dbpath /data/mongodb/shard1 –oplogSize 100 –logpath /data/mongodb/shard1/shard1.log –logappend –fork

192.168.198.129

# ./mongod –shardsvr –replSet shard1 –port 10001 –dbpath /data/mongodb/shard1 –oplogSize 100 –logpath /data/mongodb/shard1/shard1.log –logappend –fork

192.168.198.132

# ./mongod –shardsvr –replSet shard1 –port 10001 –dbpath /data/mongodb/shard1 –oplogSize 100 –logpath /data/mongodb/shard1/shard1.log –logappend –fork

连接到192.168.198.131

# ./mongo –port 10001

> config={_id:”shard1″,members:[

… {_id:0,host:”192.168.198.131:10001″},

… {_id:1,host:”192.168.198.129:10001″},

… {_id:2,host:”192.168.198.132:10001″}]

… }

> rs.initiate(config)

{

“info” : “Config now saved locally. Should come online in about a minute.”,

“ok” : 1

}

PRIMARY> rs.status()

{

“set” : “shard1″,

“date” : ISODate(“2012-03-02T02:37:55Z”),

“myState” : 1,

“members” : [

{

“_id” : 0,

“name” : “192.168.198.131:10001”,

“health” : 1,

“state” : 1,

“stateStr” : “PRIMARY”,

“optime” : {

“t” : 1330655827000,

“i” : 1

},

“optimeDate” : ISODate(“2012-03-02T02:37:07Z”),

“self” : true

},

{

“_id” : 1,

“name” : “192.168.198.129:10001”,

“health” : 1,

“state” : 2,

“stateStr” : “SECONDARY”,

“uptime” : 36,

“optime” : {

“t” : 1330655827000,

“i” : 1

},

“optimeDate” : ISODate(“2012-03-02T02:37:07Z”),

“lastHeartbeat” : ISODate(“2012-03-02T02:37:53Z”),

“pingMs” : 0

},

{

“_id” : 2,

“name” : “192.168.198.132:10001”,

“health” : 1,

“state” : 2,

“stateStr” : “SECONDARY”,

“uptime” : 36,

“optime” : {

“t” : 1330655827000,

“i” : 1

},

“optimeDate” : ISODate(“2012-03-02T02:37:07Z”),

“lastHeartbeat” : ISODate(“2012-03-02T02:37:53Z”),

“pingMs” : 466553

}

],

“ok” : 1

}

配置shard2的replica set

192.168.198.129

# ./mongod –shardsvr –replSet shard2 –port 10002 –dbpath /data/mongodb/shard2 –oplogSize 100 –logpath /data/mongodb/shard2/shard2.log –logappend –fork

192.168.198.131

# ./mongod –shardsvr –replSet shard2 –port 10002 –dbpath /data/mongodb/shard2 –oplogSize 100 –logpath /data/mongodb/shard2/shard2.log –logappend –fork

192.168.198.132

# ./mongod –shardsvr –replSet shard2 –port 10002 –dbpath /data/mongodb/shard2 –oplogSize 100 –logpath /data/mongodb/shard2/shard2.log –logappend –fork

连接到192.168.198.129

# ./mongo –port 10002

> config={_id:”shard2″,members:[

… {_id:0,host:”192.168.198.129:10002″},

… {_id:1,host:”192.168.198.131:10002″},

… {_id:2,host:”192.168.198.132:10002″}]

… }

{

“_id” : “shard2″,

“members” : [

{

“_id” : 0,

“host” : “192.168.198.129:10002”

},

{

“_id” : 1,

“host” : “192.168.198.131:10002”

},

{

“_id” : 2,

“host” : “192.168.198.132:10002”

}

]

}

> rs.initiate(config)

{

“info” : “Config now saved locally. Should come online in about a minute.”,

“ok” : 1

}

> rs.status()

{

“set” : “shard2″,

“date” : ISODate(“2012-03-02T02:53:17Z”),

“myState” : 1,

“members” : [

{

“_id” : 0,

“name” : “192.168.198.129:10002”,

“health” : 1,

“state” : 1,

“stateStr” : “PRIMARY”,

“optime” : {

“t” : 1330656717000,

“i” : 1

},

“optimeDate” : ISODate(“2012-03-02T02:51:57Z”),

“self” : true

},

{

“_id” : 1,

“name” : “192.168.198.131:10002”,

“health” : 1,

“state” : 2,

“stateStr” : “SECONDARY”,

“uptime” : 73,

“optime” : {

“t” : 1330656717000,

“i” : 1

},

“optimeDate” : ISODate(“2012-03-02T02:51:57Z”),

“lastHeartbeat” : ISODate(“2012-03-02T02:53:17Z”),

“pingMs” : 1

},

{

“_id” : 2,

“name” : “192.168.198.132:10002”,

“health” : 1,

“state” : 2,

“stateStr” : “SECONDARY”,

“uptime” : 73,

“optime” : {

“t” : 1330656717000,

“i” : 1

},

“optimeDate” : ISODate(“2012-03-02T02:51:57Z”),

“lastHeartbeat” : ISODate(“2012-03-02T02:53:17Z”),

“pingMs” : 209906

}

],

“ok” : 1

}

配置shard3的replica set

192.168.198.132

# ./mongod –shardsvr –replSet shard3 –port 10003 –dbpath /data/mongodb/shard3 –oplogSize 100 –logpath /data/mongodb/shard3/shard3.log –logappend –fork

192.168.198.129

# ./mongod –shardsvr –replSet shard3 –port 10003 –dbpath /data/mongodb/shard3 –oplogSize 100 –logpath /data/mongodb/shard3/shard3.log –logappend –fork

192.168.198.131

# ./mongod –shardsvr –replSet shard3 –port 10003 –dbpath /data/mongodb/shard3 –oplogSize 100 –logpath /data/mongodb/shard3/shard3.log –logappend –fork

连接到192.168.198.132

# ./mongo –port 10003

> config={_id:”shard3″,members:[

… {_id:0,host:”192.168.198.132:10003″},

… {_id:1,host:”192.168.198.131:10003″},

… {_id:2,host:”192.168.198.129:10003″}]

… }

{

“_id” : “shard3″,

“members” : [

{

“_id” : 0,

“host” : “192.168.198.132:10003”

},

{

“_id” : 1,

“host” : “192.168.198.131:10003”

},

{

“_id” : 2,

“host” : “192.168.198.129:10003”

}

]

}

> rs.initiate(config)

{

“info” : “Config now saved locally. Should come online in about a minute.”,

“ok” : 1

}

> rs.status()

{

“set” : “shard3″,

“date” : ISODate(“2012-03-02T03:04:52Z”),

“myState” : 1,

“members” : [

{

“_id” : 0,

“name” : “192.168.198.132:10003”,

“health” : 1,

“state” : 1,

“stateStr” : “PRIMARY”,

“optime” : {

“t” : 1330657451000,

“i” : 1

},

“optimeDate” : ISODate(“2012-03-02T03:04:11Z”),

“self” : true

},

{

“_id” : 1,

“name” : “192.168.198.131:10003”,

“health” : 1,

“state” : 2,

“stateStr” : “SECONDARY”,

“uptime” : 39,

“optime” : {

“t” : 1330657451000,

“i” : 1

},

“optimeDate” : ISODate(“2012-03-02T03:04:11Z”),

“lastHeartbeat” : ISODate(“2012-03-02T03:04:52Z”),

“pingMs” : 0

},

{

“_id” : 2,

“name” : “192.168.198.129:10003”,

“health” : 1,

“state” : 2,

“stateStr” : “SECONDARY”,

“uptime” : 39,

“optime” : {

“t” : 1330657451000,

“i” : 1

},

“optimeDate” : ISODate(“2012-03-02T03:04:11Z”),

“lastHeartbeat” : ISODate(“2012-03-02T03:04:52Z”),

“pingMs” : 0

}

],

“ok” : 1

}

配置config

192.168.198.131

# ./mongod –configsvr –dbpath /data/mongodb/config1 –port 20000 –logpath /data/mongodb/config1/config1.log –logappend –fork

192.168.198.129

# ./mongod –configsvr –dbpath /data/mongodb/config1 –port 20000 –logpath /data/mongodb/config1/config1.log –logappend –fork

192.168.198.132

# ./mongod –configsvr –dbpath /data/mongodb/config1 –port 20000 –logpath /data/mongodb/config1/config1.log –logappend –fork

配置mongos

# ./mongos –configdb 192.168.198.131:20000,192.168.198.129:20000,192.168.198.132:20000 –port 27017 –chunkSize 1 –logpath /data/mongodb/mongos.log –logappend –fork

配置shard cluster

# ./mongo –port 27017

mongos> use admin

switched to db admin

加入shards

mongos> db.runCommand({addshard:”shard1/192.168.198.131:10001,192.168.198.129:10001,192.168.198.132:10001″});

{ “shardAdded” : “shard1”, “ok” : 1 }

mongos> db.runCommand({addshard:”shard2/192.168.198.131:10002,192.168.198.129:10002,192.168.198.132:10002″});

{ “shardAdded” : “shard2”, “ok” : 1 }

mongos> db.runCommand({addshard:”shard3/192.168.198.131:10003,192.168.198.129:10003,192.168.198.132:10003″});

{ “shardAdded” : “shard3”, “ok” : 1 }

列出shards

mongos> db.runCommand({listshards:1})

{

“shards” : [

{

“_id” : “shard1”,

“host” : “shard1/192.168.198.129:10001,192.168.198.131:10001,192.168.198.132:10001”

},

{

“_id” : “shard2”,

“host” : “shard2/192.168.198.129:10002,192.168.198.131:10002,192.168.198.132:10002”

},

{

“_id” : “shard3”,

“host” : “shard3/192.168.198.129:10003,192.168.198.131:10003,192.168.198.132:10003”

}

],

“ok” : 1

}

激活数据库分片

mongos> db.runCommand({enablesharding:”test”});

{ “ok” : 1 }

通过以上命令,可以将数据库test跨shard,如果不执行,数据库只会存放在一个shard,一旦激活数据库分片,数据库中的不同的collection将被存放在不同的shard上,但一个collection仍旧存放在同一个shard上,要使collection也分片,需对collection做些其他操作。

collection分片

mongos> db.runCommand({shardcollection:”test.data”,key:{_id:1}})

{ “collectionsharded” : “test.data”, “ok” : 1 }

分片的collection只能有一个在分片key上的唯一索引,其他唯一索引不被允许。

查看shard信息

mongos> printShardingStatus()

— Sharding Status —

sharding version: { “_id” : 1, “version” : 3 }

shards:

{ “_id” : “shard1”, “host” : “shard1/192.168.198.129:10001,192.168.198.131:10001,192.168.198.132:10001” }

{ “_id” : “shard2”, “host” : “shard2/192.168.198.129:10002,192.168.198.131:10002,192.168.198.132:10002” }

{ “_id” : “shard3”, “host” : “shard3/192.168.198.129:10003,192.168.198.131:10003,192.168.198.132:10003” }

databases:

{ “_id” : “admin”, “partitioned” : false, “primary” : “config” }

{ “_id” : “test”, “partitioned” : true, “primary” : “shard1” }

test.data chunks:

shard1 1

{ “_id” : { $minKey : 1 } } –>> { “_id” : { $maxKey : 1 } } on : shard1 { “t” : 1000, “i” : 0 }

mongos> use test

switched to db test

mongos> db.data.stats()

{

“sharded” : true,

“flags” : 1,

“ns” : “test.data”,

“count” : 0,

“numExtents” : 1,

“size” : 0,

“storageSize” : 8192,

“totalIndexSize” : 8176,

“indexSizes” : {

“_id_” : 8176

},

“avgObjSize” : 0,

“nindexes” : 1,

“nchunks” : 1,

“shards” : {

“shard1” : {

“ns” : “test.data”,

“count” : 0,

“size” : 0,

“storageSize” : 8192,

“numExtents” : 1,

“nindexes” : 1,

“lastExtentSize” : 8192,

“paddingFactor” : 1,

“flags” : 1,

“totalIndexSize” : 8176,

“indexSizes” : {

“_id_” : 8176

},

“ok” : 1

}

},

“ok” : 1

}

测试:插入大量数据

mongos> for (var i=1;i<=500000;i++) db.data.save ({_id:i,value:”www.strongd.net”})

mongos> printShardingStatus()

— Sharding Status —

sharding version: { “_id” : 1, “version” : 3 }

shards:

{ “_id” : “shard1”, “host” : “shard1/192.168.198.129:10001,192.168.198.131:10001,192.168.198.132:10001” }

{ “_id” : “shard2”, “host” : “shard2/192.168.198.129:10002,192.168.198.131:10002,192.168.198.132:10002” }

{ “_id” : “shard3”, “host” : “shard3/192.168.198.129:10003,192.168.198.131:10003,192.168.198.132:10003” }

databases:

{ “_id” : “admin”, “partitioned” : false, “primary” : “config” }

{ “_id” : “test”, “partitioned” : true, “primary” : “shard1” }

test.data chunks:

shard1 6

shard2 5

shard3 11

too many chunks to print, use verbose if you want to force print

mongos> db.data.stats()

{

“sharded” : true,

“flags” : 1,

“ns” : “test.data”,

“count” : 500000,

“numExtents” : 19,

“size” : 22000084,

“storageSize” : 43614208,

“totalIndexSize” : 14062720,

“indexSizes” : {

“_id_” : 14062720

},

“avgObjSize” : 44.000168,

“nindexes” : 1,

“nchunks” : 22,

“shards” : {

“shard1” : {

“ns” : “test.data”,

“count” : 112982,

“size” : 4971232,

“avgObjSize” : 44.00021242321786,

“storageSize” : 11182080,

“numExtents” : 6,

“nindexes” : 1,

“lastExtentSize” : 8388608,

“paddingFactor” : 1,

“flags” : 1,

“totalIndexSize” : 3172288,

“indexSizes” : {

“_id_” : 3172288

},

“ok” : 1

},

“shard2” : {

“ns” : “test.data”,

“count” : 124978,

“size” : 5499056,

“avgObjSize” : 44.00019203379795,

“storageSize” : 11182080,

“numExtents” : 6,

“nindexes” : 1,

“lastExtentSize” : 8388608,

“paddingFactor” : 1,

“flags” : 1,

“totalIndexSize” : 3499328,

“indexSizes” : {

“_id_” : 3499328

},

“ok” : 1

},

“shard3” : {

“ns” : “test.data”,

“count” : 262040,

“size” : 11529796,

“avgObjSize” : 44.000137383605555,

“storageSize” : 21250048,

“numExtents” : 7,

“nindexes” : 1,

“lastExtentSize” : 10067968,

“paddingFactor” : 1,

“flags” : 1,

“totalIndexSize” : 7391104,

“indexSizes” : {

“_id_” : 7391104

},

“ok” : 1

}

},

“ok” : 1

}

mongodb的NUMA问题

mongodb日志显示如下:

WARNING: You are running on a NUMA machine.

We suggest launching mongod like this to avoid performance problems:

numactl –interleave=all mongod [other options]

解决方案:

1.在原启动命令前面加numactl –interleave=all

如# numactl –interleave=all ${MONGODB_HOME}/bin/mongod –config conf/mongodb.conf

2.修改内核参数

echo 0 > /proc/sys/vm/zone_reclaim_mode

http://www.mongodb.org/display/DOCS/NUMA

下面注释转自网络

一、NUMA和SMP

NUMA和SMP是两种CPU相关的硬件架构。在SMP架构里面,所有的CPU争用一个总线来访问所有内存,优点是资源共享,而缺点是总线争用激烈。随着PC服务器上的CPU数量变多(不仅仅是CPU核数),总线争用的弊端慢慢越来越明显,于是Intel在Nehalem CPU上推出了NUMA架构,而AMD也推出了基于相同架构的Opteron CPU。

NUMA最大的特点是引入了node和distance的概念。对于CPU和内存这两种最宝贵的硬件资源,NUMA用近乎严格的方式划分了所属的资源组(node),而每个资源组内的CPU和内存是几乎相等。资源组的数量取决于物理CPU的个数(现有的PC server大多数有两个物理CPU,每个CPU有4个核);distance这个概念是用来定义各个node之间调用资源的开销,为资源调度优化算法提供数据支持。

二、NUMA相关的策略

1、每个进程(或线程)都会从父进程继承NUMA策略,并分配有一个优先node。如果NUMA策略允许的话,进程可以调用其他node上的资源。

2、NUMA的CPU分配策略有cpunodebind、physcpubind。cpunodebind规定进程运行在某几个node之上,而physcpubind可以更加精细地规定运行在哪些核上。

3、NUMA的内存分配策略有localalloc、preferred、membind、interleave。localalloc规定进程从当前node上请求分配内存;而preferred比较宽松地指定了一个推荐的node来获取内存,如果被推荐的node上没有足够内存,进程可以尝试别的node。membind可以指定若干个node,进程只能从这些指定的node上请求分配内存。interleave规定进程从指定的若干个node上以RR算法交织地请求分配内存。

三、NUMA和swap的关系

可能大家已经发现了,NUMA的内存分配策略对于进程(或线程)之间来说,并不是公平的。在现有的Redhat Linux中,localalloc是默认的NUMA内存分配策略,这个配置选项导致资源独占程序很容易将某个node的内存用尽。而当某个node的内存耗尽时,Linux又刚好将这个node分配给了某个需要消耗大量内存的进程(或线程),swap就妥妥地产生了。尽管此时还有很多page cache可以释放,甚至还有很多的free内存。

四、解决swap问题

虽然NUMA的原理相对复杂,实际上解决swap却很简单:只要在启动MySQL之前使用numactl –interleave来修改NUMA策略即可。

值得注意的是,numactl这个命令不仅仅可以调整NUMA策略,也可以用来查看当前各个node的资源是用情况,是一个很值得研究的命令。

MongoDB 1.8通过Journaling日志改善可靠性​

面向文档的数据库引擎MongoDB在3月16日发布了1.8版本。关键的变更包括新增Journaling日志、提升分片性能以及Shell的Tab​补全。​

Journaling日志通过预写式的Redo日志为MongoDB增加了额外的可靠性。开启该功能时,变更会先写入Journaling日志,​ 定期集中提交(目前是每100ms提交一次)​,然后在真实数据上进行这些变更。如果服务器安全关闭,日志会被清除。在服务器启动时,如果存在 Journaling日志​,则会进行回放。这保证了那些已写入,但在服务器崩溃前还没有回放的​日志能在用户连接前​被执行。​两次提交之间那 100ms的时间窗口​在未来的版本中有望被缩小。

MongoDB是​一种 NoSQL数据库​,不同于SQL Server这样的关系型数据库,MongoDB中数据的基本单位是文档。类似于JavaScript对象,文档中包含一系列带有类型的键值对​,这些类型可以是字符串、对象、数组、正则表达式和代码。​这些文档以​BSON格式存 储​,根据文档类型被分组到集合(类似于SQL Server里的表)中​。Schema的设计取决于哪些文档应该有自己的集合​,哪些应该被嵌入到其他集合中去。嵌入的文档就像类里的成员对象。在关系 型系统中,你会用一张表来存储订单,另一张外键的表来存储订单项。在MongoDB中,​针对同样的场景,推荐的做法是用一个集合来保存订单,每个订单中 保存一个订单项的数组,嵌入其中。​

水平扩展是通过​自动分片来 ​做的​,​它允许有序的集合数据分布。每个分片都是一组配置成Replica集的机器​,这意味着分片里的每台机器​都拥有分片数据的完整拷贝。​分片 中会自动进行故障转移。MongoDB会自动将查询引导到合适的分片上,因此应用程序并不需要了解哪个分片持有什么数据元素。​新的Replica集身份 认证功能允许Replica集的成员之间进行自动身份认证,其中使用了密钥文件和 –keyfile 选项。​

Covered索引和Sparse索引也是该版本中新增加的特性。​Covered索引允许​在索引本身里存储数据,而​Sparse索引则会排除掉不包含索引字段的文档。Covered索引在查询所请求的全部字段​都包含在Covered索引中时能提升性能,因为不再需要取出完整的文档记录。Sparse索引在所检索的字段并非经常出现在集合中时能提升性能。目前,Sparse索引只能有一个字段。​

在MongoDB的工具集中也有一些变化。mongostat 增加了​discover模式(–discover)​,它会自动从集群的节点中取回统计信息。​通过mongodump –oplogmongorestore –oplogReplay提供了高级事务日志转储和恢复​功能。​

欲更多地了解该版本中的新特性,请查看MongoDB 1.8 Webinar

查看英文原文:MongoDB 1.8 Improves Reliability with Journaling

 

MongoDB 1.8 版本发布

MongoDB 1.8.0版本发布了!最大的显著改变是增加了一个日志存储引擎,可以快速安全地恢复崩溃系统。还包括许多内部改进,极大地提高了效率。

MongoDB是一个基于分布式文件存储的数据库开源项目,由C++语言编写。旨在为WEB应用提供可护展的高性能数据存储解决方案。常用于高流量网站,在线游戏网站和搜索引擎的大规模数据管理和分类。

该版本主要新亮点:

* Journaling
* Sharding performance improvements
* Replica set enhancements, including support for authentication
* Spherical geo search
* Covered and sparse indexes
* B-tree index self-compaction
* New map/reduce options for incremental updates
* Tab completion in the shell
* mongostat –discover

详细更新日志: http://jira.mongodb.org/secure/IssueNavigator.jspa?requestId=10128

下载地址: http://www.mongodb.org/downloads

mongodb小结

用了一阵子mongodb,作一些小结,作为将来的参考。按照以往的习惯,先作一个总览,然后再挑出一些自己比较关注的几个点,作为珠玑,加以串联阐述。

mongodb由C++写就,其名字来自humongous这个单词的中间部分,从名字可见其野心所在就是海量数据的处理。关于它的一个最简洁描述为:scalable, high-performance, open source, schema-free, document-oriented database。我对于文档型数据库有一些个人的偏好,这种偏好是从半年前研究couchdb而来的,因为我觉得用它来描述一个具有个性化特征的实体对象正合适,比如网站上的用户或商品书籍之类的条目。

一些概念:

跟mysqld一样,一个mongod服务可以有建立多个数据库,每个数据库可以有多张表,这里的表名叫collection,每个collection可以存放多个文档(document),每个文档都以BSON(binary json)的形式存放于硬盘中。跟关系型数据库不一样的地方是,它是的以单文档为单位存储的,你可以任意给一个或一批文档新增或删除字段,而不会对其它文档造成影响,这就是所谓的schema-free,这也是文档型数据库最主要的优点。跟一般的key-value数据库不一样的是,它的value中存储了结构信息,所以你又可以像关系型数据库那样对某些域进行读写、统计等操作。可以说是兼备了key-value数据库的方便高效与关系型数据库的强大功能。

索引

跟关系型数据库类似,mongodb可以对某个字段建立索引,可以建立组合索引、唯一索引,也可以删除索引。当然建立索引就意味着增加空间开销,我的建议是,如果你能把一个文档作为一个对象的来考虑,在线上应用中,你通常只要对对象ID建立一个索引即可,根据ID取出对象某些数据放在memcache即可。如果是后台的分析需要,响应要求不高,查询非索引的字段即便直接扫表也费不了太多时间。如果还受不了,就再建一个索引得了。

默认情况下每个表都会有一个唯一索引:_id,如果插入数据时没有指定_id,服务会自动生成一个_id,为了充分利用已有索引,减少空间开销,最好是自己指定一个unique的key为_id,通常用对象的ID比较合适,比如商品的ID。

capped collection

capped collection是一种特殊的表,它的建表命令为:

db.createCollection("mycoll", {capped:true, size:100000})

允许在建表之初就指定一定的空间大小,接下来的插入操作会不断地按顺序APPEND数据在这个预分配好空间的文件中,如果已经超出空间大小,则回到文件头覆盖原来的数据继续插入。这种结构保证了插入和查询的高效性,它不允许删除单个记录,更新的也有限制:不能超过原有记录的大小。这种表效率很高,它适用于一些暂时保存数据的场合,比如网站中登录用户的session信息,又比如一些程序的监控日志,都是属于过了一定的时间就可以被覆盖的数据。

复制与分片

mongodb的复制架构跟mysql也很类似,除了包括master-slave构型和master-master构型之外,还有一个Replica pairs构型,这种构型在平常可以像master-slave那样工作,一但master出现问题,应用会自动了连接slave。要做复制也很简单,我自己使用过master-slave构型,只要在某一个服务启动时加上–master参数,而另一个服务加上–slave与–source参数,即可实现同步。

分片是个很头疼的问题,数据量大了肯定要分片,mysql下的分片正是成为无数DBA的噩梦。在mongodb下,文档数据库类似key-value数据库那样的易分布特性就显现出来了,无论构造分片服务,新增节点还是删除节点都非常容易实现。但mongodb在这方面做还不足够成熟,现在分片的工作还只做到alpha2版本(mongodb v1.1),估计还有很多问题要解决,所以只能期待,就不多说了。

性能

在我的使用场合下,千万级别的文档对象,近10G的数据,对有索引的ID的查询不会比mysql慢,而对非索引字段的查询,则是全面胜出。mysql实际无法胜任大数据量下任意字段的查询,而mongodb的查询性能实在让我惊讶。写入性能同样很令人满意,同样写入百万级别的数据,mongodb比我以前试用过的couchdb要快得多,基本10分钟以下可以解决。补上一句,观察过程中mongodb都远算不上是CPU杀手。

GridFS

gridfs是mongodb一个很有趣的类似文件系统的东西,它可以用一大块文件空间来存放大量的小文件,这个对于存储web2.0网站中常见的大量小文件(如大量的用户头像)特别有效。使用起来也很方便,基本上跟一般的文件系统类似。

用合适的数据库做适合的事情

mongodb的文档里提到的user case包括实时分析、logging、全文搜索,国内也有人使用mongodb来存储分析网站日志,但我认为mongodb用来处理有一定规模的网站日志其实并不合适,最主要的就是它占空间过于虚高,原来1G的日志数据它可以存成几个G,如此下去,一个硬盘也存不了几天的日志。另一方面,数据量大了肯定要考虑sharding,而mongodb的sharding到现在为止仍不太成熟。由于日志的不可更新性的,往往只需APPEND即可,又因为对日志的操作往往只集中于一两列,所以最合适作为日志分析的还是列存储型的数据库,特别是像infobright那样的为数据仓库而设计的列存储数据库。

由于mongodb不支持事务操作,所以事务要求严格的系统(如果银行系统)肯定不能用它。

mongodb占用空间过大的原因,在官方的FAQ中,提到有如下几个方面:

1、空间的预分配:为避免形成过多的硬盘碎片,mongodb每次空间不足时都会申请生成一大块的硬盘空间,而且申请的量从64M、128M、256M那样的指数递增,直到2G为单个文件的最大体积。随着数据量的增加,你可以在其数据目录里看到这些整块生成容量不断递增的文件。

2、字段名所占用的空间:为了保持每个记录内的结构信息用于查询,mongodb需要把每个字段的key-value都以BSON的形式存储,如果value域相对于key域并不大,比如存放数值型的数据,则数据的overhead是最大的。一种减少空间占用的方法是把字段名尽量取短一些,这样占用空间就小了,但这就要求在易读性与空间占用上作为权衡了。我曾建议作者把字段名作个index,每个字段名用一个字节表示,这样就不用担心字段名取多长了。但作者的担忧也不无道理,这种索引方式需要每次查询得到结果后把索引值跟原值作一个替换,再发送到客户端,这个替换也是挺耗费时间的。现在的实现算是拿空间来换取时间吧。

3、删除记录不释放空间:这很容易理解,为避免记录删除后的数据的大规模挪动,原记录空间不删除,只标记“已删除”即可,以后还可以重复利用。

4、可以定期运行db.repairDatabase()来整理记录,但这个过程会比较缓慢。

因为官方文档中对各方面的内容已经有很详细的叙述,所以我并没有再过多的引用原文与代码,只是结合自己的使用归纳一些心得,有兴趣的朋友不妨直接去翻文档中自己感兴趣的问题,超群的博客上有一个很好的入门介绍。

最后总结一句,文档型数据库有点像波粒二象性,总能在适当的时候表现出它作为关系型数据库或key-value数据库的优势来。

实战案例:

昨天我访问mongodb的python程序开始出错,经常抛出AssertionError异常,经查证只是master查询异常,slave正常,可判断为master的数据出了问题。

修复过程:

1、在master做db.repairDatabase(),不起作用;

2、停止slave的同步;

3、对slave作mongodump,备份数据;

4、对master作mongostore,把备份数据恢复,使用–drop参数可以先把原表删除。

5、恢复slave的同步。