MongoDB的数据复制和数据切片

MongoDB简介

MongoDB由C++开发,是NoSQL中比较接近关系型数据库的一种。MongoDB中的数据以类似于json的格式存储,性能非常优越,且支持大量的数据存储。但是MongoDB不支持事务性的操作,使得其适用场景受到限制。


MongoDB副本集

MongoDB的数据复制有两种类型:

    1)master/slave

    2)replica set

第一种为类似于MySQL的主从复制模型,第二种为副本集复制方式。现在主要应用的为副本集复制模型。结构图如下:

MongoDB的数据复制和数据切片_第1张图片

一个副本集即为服务于同一数据集的多个MongoDB实例,其中一个为主节点,其余的都为从节点。主节点上能够完成读写操作,从节点仅能用于读操作。主节点需要记录所有改变数据库状态的操作,这些记录保存在oplog中,这个文件存储在local数据库,各个从节点通过此oplog来复制数据并应用于本地,保持本地的数据与主节点的一致。oplog具有幂等性,即无论执行几次其结果一致,这个比mysql的二进制日志更好用。

集群中的各节点还会通过传递心跳信息来检测各自的健康状况。当主节点故障时,多个从节点会触发一次新的选举操作,并选举其中的一个成为新的主节点(通常谁的优先级更高,谁就是新的主节点),心跳信息默认每2秒传递一次。

MongoDB的数据复制和数据切片_第2张图片


实现过程

副本集的实现至少需要三个节点,且应该为奇数个节点,可以使用arbiter(仲裁节点)来参与选举。

实验环境:

主节点:192.168.1.132

从节点:192.168.1.139,192.168.1.140


1)安装配置MongoDB

在各个节点上安装MongoDB服务器端需要的rpm包(安装包的下载地址:http://downloads-distro.mongodb.org/repo/redhat/os/):

1
[root@mongo1 mongodb-2.6.5] # yum install -y mongodb-org-server-2.6.5-1.x86_64.rpm mongodb-org-tools-2.6.5-1.x86_64.rpm mongodb-org-shell-2.6.5-1.x86_64.rpm

配置文件信息:

1
2
3
4
5
6
7
8
9
10
11
[root@mongo1 ~] # vim /etc/mongod.conf 
logpath= /var/log/mongodb/mongod .log 
logappend= true 
fork= true
dbpath= /mongodb/data
pidfilepath= /var/run/mongodb/mongod .pid
bind_ip=0.0.0.0
httpinterface= true
rest= true
replSet=rs0
replIndexPrefetch = _id_only

replSet指定副本集的名称,这个至关重要,这个决定了对应的每一个节点加入的是哪一个副本集的集群。

replIndexPrefetch指定副本集的索引预取,如果有预取功能可以让复制过程更为高效,有3个值none,_id_only,all。none:不预取任何索引,_id_only:预取ID索引,all:预取所有索引。这个预取操作只能定义在从节点上。


在各节点上创建数据存放目录,然后启动服务:

1
2
3
4
5
6
7
[root@mongo1 ~] # mkdir -pv /mongodb/data
mkdir : created directory ` /mongodb '
mkdir : created directory ` /mongodb/data '
[root@mongo1 ~] # chown -R mongod.mongod /mongodb
 
[root@mongo1 ~] # service mongod start
Starting mongod:                                           [  OK  ]


2)配置集群的成员

查看集群信息(此时没有任何节点)

1
2
3
4
5
6
7
8
9
10
[root@mongo1 ~] # mongo --host 192.168.1.132
MongoDB shell version: 2.6.5
connecting to: 192.168.1.132:27017 /test
> rs.status()
{
     "startupStatus"  : 3,
     "info"  "run rs.initiate(...) if not yet done for the set" ,
     "ok"  : 0,
     "errmsg"  "can't get local.system.replset config from self or any seed (EMPTYCONFIG)"
}

添加集群成员,首先配置cfg定义集群信息,然后执行rs.initiate(cfg)完成节点的添加。在定义集群时,需要指定每一个节点的属性信息,例如_id,host。还有很多属性字段,常见的有priority,votes,arbiterOnly.....  具体的信息可以参考官方网站http://docs.mongodb.org/manual/reference/command/replSetGetConfig/#replsetgetconfig-output。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
> cfg={_id: 'rs0' ,members:[   
... ... {_id:0,host: '192.168.1.132:27017' },  
... ... {_id:1,host: '192.168.1.139:27017' },
... ... {_id:2,host: '192.168.1.140:27017' }]  
... ... }
{
     "_id"  "rs0" ,
     "members"  : [
         {
             "_id"  : 0,
             "host"  "192.168.1.132:27017"
         },
         {
             "_id"  : 1,
             "host"  "192.168.1.139:27017"
         },
         {
             "_id"  : 2,
             "host"  "192.168.1.140:27017"
         }
     ]
}
#################################
> rs.initiate(cfg)
{
     "info"  "Config now saved locally.  Should come online in about a minute." ,
     "ok"  : 1
}

查看各节点的状态信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
> rs.status()
{
     "set"  "rs0" ,
     "date"  : ISODate( "2015-09-04T23:02:13Z" ),
     "myState"  : 1,
     "members"  : [                                                        #显示副本集的所有成员信息
         {
             "_id"  : 0,                                           #节点的标识符
             "name"  "192.168.1.132:27017" ,                      #节点名称    
             "health"  : 1,                                        #节点的健康状态            
             "state"  : 1,                                                                                
             "stateStr"  "PRIMARY" ,                              #该节点为主节点                                
             "uptime"  : 1750,                                     #运行时长                   
             "optime"  : Timestamp(1441407002, 1),                 #oplog最后一次操作的时间戳                                                     
             "optimeDate"  : ISODate( "2015-09-04T22:50:02Z" ),      #oplog最后一次操作的时间                                
             "electionTime"  : Timestamp(1441407011, 1),           #选举时间                       
             "electionDate"  : ISODate( "2015-09-04T22:50:11Z" ),    #选举日期                                  
             "self"  true                                        #表示是否为当前节点
         },
         {
             "_id"  : 1,
             "name"  "192.168.1.139:27017" ,
             "health"  : 1,
             "state"  : 2,
             "stateStr"  "SECONDARY" ,                             #从节点
             "uptime"  : 730,
             "optime"  : Timestamp(1441407002, 1),
             "optimeDate"  : ISODate( "2015-09-04T22:50:02Z" ),
             "lastHeartbeat"  : ISODate( "2015-09-04T23:02:13Z" ),
             "lastHeartbeatRecv"  : ISODate( "2015-09-04T23:02:12Z" ),
             "pingMs"  : 0,
             "syncingTo"  "192.168.1.132:27017"                   #指向的主节点
         },
         {
             "_id"  : 2,
             "name"  "192.168.1.140:27017" ,
             "health"  : 1,
             "state"  : 2,
             "stateStr"  "SECONDARY" ,
             "uptime"  : 730,
             "optime"  : Timestamp(1441407002, 1),
             "optimeDate"  : ISODate( "2015-09-04T22:50:02Z" ),
             "lastHeartbeat"  : ISODate( "2015-09-04T23:02:13Z" ),
             "lastHeartbeatRecv"  : ISODate( "2015-09-04T23:02:12Z" ),
             "pingMs"  : 0,
             "syncingTo"  "192.168.1.132:27017"
         }
     ],
     "ok"  : 1
}


在创建副本集时,有3种方式:

1、db.runCommand( { replSetInitiate : } )

2、rs.initiate()

3、rs.initiate()      #先在其中一个节点上初始化,再通过rs.add添加另外的节点

这里采用的是第二种方式,即为上述中的cfg文件,对该文件的修改使用replSetInitiate命令。


3)访问测试

在主节点上添加数据(192.168.1.132):

1
2
3
4
rs0:PRIMARY> use student_db
switched to db student_db
rs0:PRIMARY>  for  (i=1;i<=100000;i++) db.students.insert({name: "student" +i,age:(i%120),address: "china_nb" });
WriteResult({  "nInserted"  : 1 })


此时在从节点上访问数据会报如下错误:

1
2
3
4
rs0:SECONDARY> use student_db
switched to db student_db
rs0:SECONDARY> db.students.findOne()
2015-09-04T19:28:10.730-0400 error: {  "$err"  "not master and slaveOk=false" "code"  : 13435 } at src /mongo/shell/query .js:131

执行rs.slaveOk()后,数据才可读。

1
2
3
4
5
6
7
8
9
10
rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> db.student.findOne()
null
rs0:SECONDARY> db.students.findOne()
{
     "_id"  : ObjectId( "55ea287ce476f31ac766a383" ),
     "name"  "student1" ,
     "age"  : 1,
     "address"  "china_nb"
}


当主节点故障时,从节点会重新投票选举出主节点,继续提供服务,避免单点故障。

主节点上关闭服务:

1
2
[root@mongo1 ~] # service mongod stop
Stopping mongod:                                           [  OK  ]

从节点上查看状态信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
rs0:SECONDARY> rs.status()
{
     "set"  "rs0" ,
     "date"  : ISODate( "2015-09-04T23:31:49Z" ),
     "myState"  : 1,
     "members"  : [
         {
             "_id"  : 0,
             "name"  "192.168.1.132:27017" ,
             "health"  : 0,                        #主节点已经下线
             "state"  : 8,
             "stateStr"  "(not reachable/healthy)" ,
                          ................
         },
         {
             "_id"  : 1,
             "name"  "192.168.1.139:27017" ,
             "health"  : 1,
             "state"  : 1,
             "stateStr"  "PRIMARY" ,               #新选出的主节点
             ............
         },
         {
             "_id"  : 2,
             "name"  "192.168.1.140:27017" ,
             "health"  : 1,
             "state"  : 2,
             "stateStr"  "SECONDARY" ,
             .........
         }
     ],
     "ok"  : 1
}

可以看到原来的主节点已经下线(health为0),重新选举的主节点为192.168.1.139。可以通过rs.isMaster()查看当前节点是否为主节点。


4)添加一个从节点

数据库运行一段时间后,可能需要再次添加节点来分散压力。通过rs.add命令添加从节点。添加完成后,该节点需要和主节点同步数据,同步过程有3个步骤:

   1、初始同步(initial sync)

   2、回滚后追赶(post-rollback catch-up)

   3、切分块迁移(sharding chunk migrations)

添加从节点(在主节点上):

1
2
rs0:PRIMARY> rs.add( "192.168.1.138" )
"ok"  : 1 }

查看状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
     "_id"  : 2,
     "name"  "192.168.1.127:27017" ,
     "health"  : 1,
         .....................
     "lastHeartbeatMessage"  "still initializing"         #正在初始化
}
#######################
{
     "_id"  : 1,
     "name"  "192.168.1.138:27017" ,
     "health"  : 1,
         ........
     "lastHeartbeatMessage"  "initial sync need a member to be primary or secondary to do our initial sync"     #同步数据
}
#######################
{
     "_id"  : 1,
     "name"  "192.168.1.138:27017" ,
     "health"  : 1,
         ........
     "syncingTo"  "node1.xiaoxiao.com:27017"                  #同步完成
}

执行rs.slaveOk()后,即可实现访问。


5)更改某个节点的优先级

若某个从节点的硬件配置不错,可以对应的调高其优先级,使其在选举过程中能够优先被选举为主节点。例如设置第3个节点的优先级为2(默认均为1),过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rs0:PRIMARY> cfg=rs.conf()
rs0:PRIMARY> cfg.members[2].priority=2         #节点的标识符为2
rs0:PRIMARY> rs.reconfig(cfg)                  #更新配置
################
rs0:SECONDARY> rs.config()   
{
                 ................
         {
             "_id"  : 2,
             "host"  "192.168.1.140:27017" ,
             "priority"  : 2         #对应优先级
         }
     ]
}

此时会立刻进行选举,优先级最高的为主节点,如下图所示:

MongoDB的数据复制和数据切片_第3张图片


MongoDB数据分片

随着数据集的扩大和吞吐量的提升,单个MongoDB服务器可能在cpu,内存或IO这些资源上出现瓶颈,这是需要对MongoDB进行扩展,比较经济的方式是水平扩展,将数据集分布到多个节点上来分散访问压力。这里的每个节点也称作分片,每个分片都是一个独立的数据库。所有的分片组合在一起才是一个完整的数据库。

MongoDB的分片框架中有3个角色:

1)Query Routers:路由

2)Config servers:元数据服务器

3)Shards:数据节点

MongoDB的数据复制和数据切片_第4张图片

工作机制:Query Routers用于接收用户的请求,将请求路由到对应的分片上(shards)执行,然后将结果返回给客户端。Config servers存储服务器集群的元数据,Query Routers通过使用这些元数据将请求定位至特定的shard节点。Shards节点存储数据,为了提供高可用性和数据一致性,每个shard都可以是一个副本集。在生产环境中,为了避免单点故障,Query Routers和Config servers往往有多个节点。


实现过程

实验环境:

Config server:192.168.1.106

Query Routers:192.168.1.131

Shared:192.168.1.138,192.168.1.127


1)配置config server

在192.168.1.106上更改配置文件信息:

1
2
3
4
[root@node1 ~] # vim /etc/mongod.conf 
#replSet=rs0
#replIndexPrefetch = _id_only
configsvr =  true

配置完成后启动服务:

1
2
[root@node1 ~] # service mongod start
Starting mongod:                                           [  OK  ]

wKioL1X8vufjHWc5AACTHeTt6wk436.jpg

可以看到对应的服务监听在27019上。


2)配置Query Routers

Query Routers节点只需要安装mongodb-org-mongos即可,无需安装其他的软甲包。

1
[root@node4 mongodb-2.6.5] # yum install mongodb-org-mongos-2.6.5-1.x86_64.rpm

默认情况下,mongos监听于27017端口,在启动mongos是需要指定config服务器的地址。

启动mongos:

1
2
[root@node4 ~] # mkdir /var/log/mongodb
[root@node4 ~] # mongos --configdb=192.168.1.106 --fork --logpath=/var/log/mongodb/mongo.log

也可以直接编辑配置文件:

1)注释dbpath指令

2)添加configdb指令,并指定config服务器的地址

3)启动mongos,命令:mongos -f /etc/mongod.conf


3)配置shard节点

shard(数据节点)的配置与配置mongodb一致,如果不是副本集,把以下两项注销。

1
2
#replSet=rs0
#replIndexPrefetch = _id_only


在两个shard节点上启动mongod服务:

1
2
3
4
5
[root@node2 ~] # service mongod start
Starting mongod:                                           [  OK  ]
###################
[root@node3 ~] # service mongod start
Starting mongod:                                           [  OK  ]


4)向分区集群中添加各shard服务器或副本集

连接mongos节点,添加shard。由于在mongos节点上仅安装了mongos的包,没有mongo命令,可以在其他节点上使用mongo --host 来连接。

1
2
3
4
[root@node1 ~] # mongo --host 192.168.1.131
MongoDB shell version: 2.6.5
connecting to: 192.168.1.131:27017 /test
mongos>


添加shard节点:

1
2
3
4
mongos> sh.addShard( "192.168.1.127" )
"shardAdded"  "shard0000" "ok"  : 1 }
mongos> sh.addShard( "192.168.1.138" )
"shardAdded"  "shard0001" "ok"  : 1 }

MongoDB的数据复制和数据切片_第5张图片

可以看到节点已经添加成功,只是上面还没有数据,没有进行分片。


5)启用sharding功能

启用指定数据库的sharding功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mongos> sh.enableSharding( "student_db" )
"ok"  : 1 }
################################
mongos> sh.status()
--- Sharding Status --- 
   sharding version: {
     "_id"  : 1,
     "version"  : 4,
     "minCompatibleVersion"  : 4,
     "currentVersion"  : 5,
     "clusterId"  : ObjectId( "55e89a17f0cf218cb7edd0c5" )
}
   shards:
     {   "_id"  "shard0000" ,   "host"  "192.168.1.127:27017"  }
     {   "_id"  "shard0001" ,   "host"  "192.168.1.138:27017"  }
   databases:
     {   "_id"  "admin" ,   "partitioned"  false ,   "primary"  "config"  }
     {   "_id"  "student_db" ,   "partitioned"  true ,   "primary"  "shard0000"  }     #显示该数据库已经支持分片

最后一行显示student_db数据库的partition为true,已支持数据分片功能。


指定需要分片的Collection及索引:

1
2
3
4
5
6
mongos> sh.shardCollection( "student_db.students" ,{ "age" :1})
"collectionsharded"  "student_db.student" "ok"  : 1 }
#################################
#插入数据
mongos>  for  (i=1;i<=100000;i++) db.students.insert({name: "student" +i,age:(i%120),address: "china_nb" });
WriteResult({  "nInserted"  : 1 })


查看集群的状态信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mongos> sh.status()
--- Sharding Status --- 
   sharding version: {
     "_id"  : 1,
     "version"  : 4,
     "minCompatibleVersion"  : 4,
     "currentVersion"  : 5,
     "clusterId"  : ObjectId( "55e89a17f0cf218cb7edd0c5" )
}
   shards:
     {   "_id"  "shard0000" ,   "host"  "192.168.1.127:27017"  }
     {   "_id"  "shard0001" ,   "host"  "192.168.1.138:27017"  }
   databases:
     {   "_id"  "admin" ,   "partitioned"  false ,   "primary"  "config"  }
     {   "_id"  "student_db" ,   "partitioned"  true ,   "primary"  "shard0000"  }
         student_db.students
             shard key: {  "age"  : 1 }
             chunks:
                 shard0001   1
                 shard0000   2
             "age"  : {  "$minKey"  : 1 } } -->> {  "age"  : 1 } on : shard0001 Timestamp(2, 0) 
             "age"  : 1 } -->> {  "age"  : 119 } on : shard0000 Timestamp(2, 1) 
             "age"  : 119 } -->> {  "age"  : {  "$maxKey"  : 1 } } on : shard0000 Timestamp(1, 4)

可以看到数据已经分别存储在不同的shard上。

若需要分片时,数据已经存在,则需要对collection中的某一字段先创建索引,然后才能够分片。以上是MongoDB的简单应用。.................^_^


转自:http://ljbaby.blog.51cto.com/10002758/1696180


你可能感兴趣的:(mongodb)