MongoDB簡介node
MongoDB由C++開發,是NoSQL中比較接近關係型數據庫的一種。MongoDB中的數據以相似於json的格式存儲,性能很是優越,且支持大量的數據存儲。可是MongoDB不支持事務性的操做,使得其適用場景受到限制。mysql
MongoDB副本集sql
MongoDB的數據複製有兩種類型:mongodb
1)master/slaveshell
2)replica set數據庫
第一種爲相似於MySQL的主從複製模型,第二種爲副本集複製方式。如今主要應用的爲副本集複製模型。結構圖以下:json
一個副本集即爲服務於同一數據集的多個MongoDB實例,其中一個爲主節點,其他的都爲從節點。主節點上可以完成讀寫操做,從節點僅能用於讀操做。主節點須要記錄全部改變數據庫狀態的操做,這些記錄保存在oplog中,這個文件存儲在local數據庫,各個從節點經過此oplog來複制數據並應用於本地,保持本地的數據與主節點的一致。oplog具備冪等性,即不管執行幾回其結果一致,這個比mysql的二進制日誌更好用。vim
集羣中的各節點還會經過傳遞心跳信息來檢測各自的健康情況。當主節點故障時,多個從節點會觸發一次新的選舉操做,並選舉其中的一個成爲新的主節點(一般誰的優先級更高,誰就是新的主節點),心跳信息默認每2秒傳遞一次。bash
實現過程服務器
副本集的實現至少須要三個節點,且應該爲奇數個節點,可使用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 #對應優先級 } ] }
此時會馬上進行選舉,優先級最高的爲主節點,以下圖所示:
MongoDB數據分片
隨着數據集的擴大和吞吐量的提高,單個MongoDB服務器可能在cpu,內存或IO這些資源上出現瓶頸,這是須要對MongoDB進行擴展,比較經濟的方式是水平擴展,將數據集分佈到多個節點上來分散訪問壓力。這裏的每一個節點也稱做分片,每一個分片都是一個獨立的數據庫。全部的分片組合在一塊兒纔是一個完整的數據庫。
MongoDB的分片框架中有3個角色:
1)Query Routers:路由
2)Config servers:元數據服務器
3)Shards:數據節點
工做機制: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 ]
能夠看到對應的服務監聽在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 }
能夠看到節點已經添加成功,只是上面尚未數據,沒有進行分片。
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的簡單應用。.................^_^