MongoDB的數據複製和數據切片

MongoDB簡介node

MongoDB由C++開發,是NoSQL中比較接近關係型數據庫的一種。MongoDB中的數據以相似於json的格式存儲,性能很是優越,且支持大量的數據存儲。可是MongoDB不支持事務性的操做,使得其適用場景受到限制。mysql


MongoDB副本集sql

MongoDB的數據複製有兩種類型:mongodb

    1)master/slaveshell

    2)replica set數據庫

第一種爲相似於MySQL的主從複製模型,第二種爲副本集複製方式。如今主要應用的爲副本集複製模型。結構圖以下:json

wKioL1X8Bu_yV2TKAADZZe3s4mc862.jpg

一個副本集即爲服務於同一數據集的多個MongoDB實例,其中一個爲主節點,其他的都爲從節點。主節點上可以完成讀寫操做,從節點僅能用於讀操做。主節點須要記錄全部改變數據庫狀態的操做,這些記錄保存在oplog中,這個文件存儲在local數據庫,各個從節點經過此oplog來複制數據並應用於本地,保持本地的數據與主節點的一致。oplog具備冪等性,即不管執行幾回其結果一致,這個比mysql的二進制日誌更好用。vim

集羣中的各節點還會經過傳遞心跳信息來檢測各自的健康情況。當主節點故障時,多個從節點會觸發一次新的選舉操做,並選舉其中的一個成爲新的主節點(一般誰的優先級更高,誰就是新的主節點),心跳信息默認每2秒傳遞一次。bash

wKiom1X8B7fAPvs9AAEGEPe6ZGs672.jpg


實現過程服務器

副本集的實現至少須要三個節點,且應該爲奇數個節點,可使用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/):

[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

配置文件信息:

[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:預取全部索引。這個預取操做只能定義在從節點上。


在各節點上建立數據存放目錄,而後啓動服務:

[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)配置集羣的成員

查看集羣信息(此時沒有任何節點)

[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。

> 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
}

查看各節點的狀態信息:

> 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種方式:

一、db.runCommand( { replSetInitiate : <config_object> } )

二、rs.initiate(<config_object>)

三、rs.initiate()      #先在其中一個節點上初始化,再經過rs.add添加另外的節點

這裏採用的是第二種方式,<config_object>即爲上述中的cfg文件,對該文件的修改使用replSetInitiate命令。


3)訪問測試

在主節點上添加數據(192.168.1.132):

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 })


此時在從節點上訪問數據會報以下錯誤:

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()後,數據纔可讀。

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"
}


當主節點故障時,從節點會從新投票選舉出主節點,繼續提供服務,避免單點故障。

主節點上關閉服務:

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

從節點上查看狀態信息:

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個步驟:

   一、初始同步(initial sync)

   二、回滾後追趕(post-rollback catch-up)

   三、切分塊遷移(sharding chunk migrations)

添加從節點(在主節點上):

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

查看狀態:

{
	"_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),過程以下:

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        #對應優先級
		}
	]
}

此時會馬上進行選舉,優先級最高的爲主節點,以下圖所示:

wKiom1X8JhvQh263AAGlZskmgeY459.jpg


MongoDB數據分片

隨着數據集的擴大和吞吐量的提高,單個MongoDB服務器可能在cpu,內存或IO這些資源上出現瓶頸,這是須要對MongoDB進行擴展,比較經濟的方式是水平擴展,將數據集分佈到多個節點上來分散訪問壓力。這裏的每一個節點也稱做分片,每一個分片都是一個獨立的數據庫。全部的分片組合在一塊兒纔是一個完整的數據庫。

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

1)Query Routers:路由

2)Config servers:元數據服務器

3)Shards:數據節點

wKiom1X8sn2CxAEVAAHVuIfF0fU943.jpg

工做機制: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上更改配置文件信息:

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

配置完成後啓動服務:

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

wKioL1X8vufjHWc5AACTHeTt6wk436.jpg

能夠看到對應的服務監聽在27019上。


2)配置Query Routers

Query Routers節點只須要安裝mongodb-org-mongos便可,無需安裝其餘的軟甲包。

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

默認狀況下,mongos監聽於27017端口,在啓動mongos是須要指定config服務器的地址。

啓動mongos:

[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一致,若是不是副本集,把如下兩項註銷。

#replSet=rs0
#replIndexPrefetch = _id_only


在兩個shard節點上啓動mongod服務:

[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 來鏈接。

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


添加shard節點:

mongos> sh.addShard("192.168.1.127")
{ "shardAdded" : "shard0000", "ok" : 1 }
mongos> sh.addShard("192.168.1.138")
{ "shardAdded" : "shard0001", "ok" : 1 }

wKiom1X8wTmC8hG1AAH10IXARKY625.jpg

能夠看到節點已經添加成功,只是上面尚未數據,沒有進行分片。


5)啓用sharding功能

啓用指定數據庫的sharding功能:

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及索引:

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 })


查看集羣的狀態信息:

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的簡單應用。.................^_^

相關文章
相關標籤/搜索