MongoDB是一個開源非關係文檔型數據庫。在MongoDB中的每一個記錄是一個文檔,文檔類似於JSON對象,它是一個由字段和值對組成的數據結構。
類似json的文檔存儲
MongoDB出現解決了傳統關係型數據庫對海量數據的處理難題。以傳統的MySQL爲代表的關係型數據庫,事務保證操作和數據的可靠性,但同時也限制了數據的擴展性和數據庫海量數據的處理能力。MongoDB的數據庫不支持事務,這使它突破了關係型數據庫的侷限性,得到了良好的擴展性。在實現上MongoDB借鑑了MySQL,在操作方式上和工作模式與MySQL類似。
以下通過MongoDB副本集複製和分片
,認識其對海量數據的處理和其原理:
MongoDB副本集複製
MongoDB在數據冗餘方面提供了兩種方案:
master/slave
主從複製replica set
副本集複製
master/slave
是和MySQL類似的一種複製方式,master端啓動一個I/O線程用於向slave端同步寫日誌文件,slave端啓動兩個線程,一個IO線程把日誌文件記錄在slave節點的中繼日誌中,SQL線程把中繼日誌進行回放完成備份,只是MySQL中的binlog
在MongoDB叫做OpLog
日誌文件。
這種複製方式最大的弊端在於:主節點成爲最大的單點所在,可能有人會說,給主節點做高可用,隨之而來是就是一堆問題:
- 兩個主節點數據不應該也一樣嗎?兩個主節點又互爲單點所在
- 當主節點A的宕機,A中的事務沒有執行完,B中的數據怎麼回滾?雙主節點事務難以同步
可以看出,事務在某些不必要的場景,反而帶來很多問題,於是MongoDB擺脫了事務的限制,提出了第二種方式replica set
:
副本集複製過程
工作方式如下:
在MongoDB中一個副本集即爲服務於同一數據集的多個MongoDB實例,其中一個爲主節點Primary
,其餘的都爲從節點Secondary
(主節點上能夠完成讀寫操作,從節點僅能用於讀操作)。主節點記錄所有改變數據庫狀態的操作,將這些記錄保存在oplog中,oplog存儲在local數據庫,各個從節點通過此oplog來複制數據並應用於本地,保持本地的數據與主節點的一致。oplog具有冪等性(無論執行幾次其結果一致),比mysql的二進制日誌更高效可靠。
集羣中的各節點通過傳遞心跳信息(默認每2秒傳遞一次)來檢測各自的健康狀況。當主節點故障時多個從節點會觸發一次新的選舉操作,並選舉其中的一個成爲新的主節點(通常誰的優先級更高,誰就是新的主節點)。
各個從節點傳遞心跳
沒有事務的限制,當主節點宕機時,每個從節點都可以作爲主節點。
實例
實驗環境
主機 | IP地址 |
---|---|
Primary | 192.168.80.5 |
Secondary | 192.168.80.8 |
Secondary | 192.168.80.9 |
其配置較爲簡單,分爲以下幾個步驟:
安裝配置MongoDB服務器端和客戶端
yum install -y mongodb-server mongodb
配置文件信息:
[root@mongo1 ~]# vim /etc/mongod.conf logpath=/var/log/mongodb/mongod.log logappend=true fork=true dbpath=/data/mongodb pidfilepath=/var/run/mongodb/mongod.pid bind_ip=0.0.0.0 # 服務監聽的地址 httpinterface=true rest=true replSet=rs0 # 指定了副本集名稱,多個副本集用於區別 replIndexPrefetch = _id_only #指定副本集的索引預取,如果有預取功能可以讓複製過程更爲高效, # 有3個值none,_id_only,all。 # none:不預取任何索引, # _id_only:預取ID索引, # all:預取所有索引。
創建數據目錄啓動服務
[root@mongo1 ~]# mkdir -pv /data/mongodb mkdir: created directory `/mongodb' mkdir: created directory `/data/mongodb' [root@mongo1 ~]# chown -R mongod.mongod /data/mongodb [root@mongo1 ~]# systemctl start mongod # 在每一個節點啓動服務
配置添加集羣成員
[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)" } >rs.initiate() #主節點初始化 >rs.status() { "set" : "testSet", "date" : ISODate("2017-10-13T08:25:57Z"), "myState" : 1, "members" : [ { "_id" : 0, "name" : "www.dearecho.me:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 234, "optime" : Timestamp(1507883148, 1), "optimeDate" : ISODate("2017-10-13T08:25:48Z"), "electionTime" : Timestamp(1507883149, 1), "electionDate" : ISODate("2017-10-13T08:25:49Z"), "self" : true } ], "ok" : 1 } rs0:PRIMARY> rs.add("192.168.80.8") # 發現前面的標識變爲了Primary,添加從節點 { "ok" : 1 } rs0:PRIMARY> rs.add("192.168.80.9") { "ok" : 1 } 在創建副本集時,有3種方式: db.runCommand( { replSetInitiate : <config_object> } ) rs.initiate(<config_object>) rs.initiate() #先在其中一個節點上初始化,再通過rs.add添加另外的節點 這裏採用的是第一種方式。
查看各節點信息
> rs.status() { "set" : "rs0", "date" : ISODate("2015-09-04T23:02:13Z"), "myState" : 1, "members" : [ #顯示副本集的所有成員信息 { "_id" : 0, #節點的標識符 "name" : "192.168.80.5:27017", #節點名稱 "health" : 1, #節點的健康狀態 "state" : 1, "stateStr" : "PRIMARY", #該節點爲主節點 "uptime" : 1750, #運行時長 "optime" : Timestamp(1507898695, 173), #oplog最後一次操作的時間戳 "optimeDate" : ISODate("2017-10-13T12:44:55Z"), "lastHeartbeat" : ISODate("2017-10-13T12:53:19Z"), "lastHeartbeatRecv" : ISODate("2017-10-13T12:53:19Z"), "pingMs" : 0, "electionTime" : Timestamp(1507899169, 1), #選舉日期 "self" : true #表示是否爲當前節點 }, { "_id" : 1, #節點的標識符 "name" : "192.168.80.8:27017", #節點名稱 "health" : 1, #節點的健康狀態 "state" : 1, "stateStr" : "SECONDARY", #從節點 "uptime" : 1750, #運行時長 "optime" : Timestamp(1507898695, 173), #oplog最後一次操作的時間戳 "optimeDate" : ISODate("2017-10-13T12:44:55Z"), "lastHeartbeat" : ISODate("2017-10-13T12:53:19Z"), "lastHeartbeatRecv" : ISODate("2017-10-13T12:53:19Z"), "pingMs" : 0, "syncingTo" : "192.168.80.5:27017" #指向的主節點 }, { "_id" : 2, "name" : "192.168.80.9:27017", "health" : 1, "state" : 1, "stateStr" : "SECONDARY", #從節點 "uptime" : 1750, "optime" : Timestamp(1507898695, 173), "optimeDate" : ISODate("2017-10-13T12:44:55Z"), "lastHeartbeat" : ISODate("2017-10-13T12:53:19Z"), "lastHeartbeatRecv" : ISODate("2017-10-13T12:53:19Z"), "pingMs" : 0, "syncingTo" : "192.168.80.5:27017" #指向的主節點 } ], "ok" : 1 }
測試
在主節點添加測試數據:
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> show collections
2017-10-13T20:46:55.346+0800 error: { "$err" : "not master and slaveOk=false", "code" : 13435 } at src/mongo/shell/query.js:131
rs0:SECONDARY> rs.slaveOk() # 需啓用從節點纔可查看
rs0:SECONDARY> db.students.findOne()
{
"_id" : ObjectId("59e0b54660703b86d071762f"),
"name" : "student1",
"age" : 1,
"address" : "china_nb"
}
主節點修改優先級
rs0:PRIMARY> cfg = rs.config()
{
"_id" : "test",
"version" : 3,
"members" : [
{
"_id" : 0,
"host" : "192.168.80.5:27017"
},
{
"_id" : 1,
"host" : "192.168.80.8:27017"
},
{
"_id" : 2,
"host" : "192.168.80.9:27017"
}
]
}
rs0:PRIMARY> cfg.members[1].priority=2 # 設置第二個節點優先級爲2
2
rs0:PRIMARY> rs.reconfig(cfg)
2017-10-13T21:13:01.255+0800 DBClientCursor::init call() failed
2017-10-13T21:13:01.269+0800 trying reconnect to 127.0.0.1:27017 (127.0.0.1) failed
2017-10-13T21:13:01.282+0800 reconnect 127.0.0.1:27017 (127.0.0.1) ok
reconnected to server after rs command (which is normal)
在192.168.80.8
查看狀態
rs0:PRIMARY> db.printReplicationInfo()
configured oplog size: 990MB
log length start to end: 1098secs (0.31hrs)
oplog first event time: Fri Oct 13 2017 20:26:37 GMT+0800 (CST)
oplog last event time: Fri Oct 13 2017 20:44:55 GMT+0800 (CST)
now: Fri Oct 13 2017 20:56:23 GMT+0800 (CST)
rs.status()
{
"_id" : 1, #節點2
"name" : "192.168.80.8:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY", #已搶佔爲主節點
"uptime" : 1602,
"optime" : Timestamp(1507898695, 173),
"optimeDate" : ISODate("2017-10-13T12:44:55Z"),
"lastHeartbeat" : ISODate("2017-10-13T12:53:19Z"),
"lastHeartbeatRecv" : ISODate("2017-10-13T12:53:19Z"),
"pingMs" : 0,
"electionTime" : Timestamp(1507899169, 1),
"electionDate" : ISODate("2017-10-13T12:52:49Z")
}
MongoDB數據分片
在Mongodb裏面存在另一種集羣,就是分片技術,可以滿足MongoDB數據量大量增長的需求。
當MongoDB存儲海量的數據時,一臺機器可能不足以存儲數據,也可能不足以提供可接受的讀寫吞吐量。這時,我們就可以通過在多臺機器上分割數據,使得數據庫系統能存儲和處理更多的數據。
MongoDB中使用分片集羣結構分佈:
上圖中主要有如下所述三個主要組件:
Query Routers
前端路由,客戶端由此接入,把客戶端的請求路由到合適的shared上。Config Server
實質爲mongod實例存儲了整個 ClusterMetadata,其中包括 chunk信息和索引信息。Shard
存儲實際的數據塊,實際生產環境中一個shard server角色可由幾臺機器組個一個replica set承擔,防止主機單點故障
分片過程:
把表上以某個字段爲例,字段創建爲索引,索引當做分片的元數據,而後把大數據切割成一個一個的chunk,把每個chunk分配到每個shared。在整個業務運行過程中,重新均衡,chunk在每個節點上挪來挪去。
實例
主機 | IP |
---|---|
Query Routers | 192.168.80.5 |
Config server | 192.168.80.7 |
Shared | 192.168.80.8, 192.168.80.9 |
實現步驟如下:
配置各個節點
配置config server
yum install -y mongodb-server mongodb vim /etc/mongod.conf configsvr = true
啓動其他節點:
yum install -y mongodb-server mongodb mongos --configdb=192.168.80.7 --fork --logpath=/var/log/mongodb/mongo.log # 啓動Query Routers 2017-10-14T10:32:40.251+0800 warning: running with 1 config server should be done only for testing purposes and is not recommended for production about to fork child process, waiting until server is ready for connections. forked process: 16035 child process started successfully, parent exiting [root@node ~]# ss -tnl LISTEN 0 128 *:27017 *:* # monogs監聽在27017 systemctl start mongod # 啓動shared
Query Routers
配置節點加入`Shard`: mongos> sh.addShard("192.168.80.8") { "shardAdded" : "shard0000", "ok" : 1 } mongos> sh.addShard("192.168.80.9") { "shardAdded" : "shard0001", "ok" : 1 } mongos> use student_db # 創建數據庫 switched to db student_db mongos> sh.enableSharding("student_db") # 數據庫啓用分片 { "ok" : 1 } mongos> sh.shardCollection("student_db.students",{"age":1}) # 創建collections,並指明索引 { "collectionsharded" : "student_db.students", "ok" : 1 } mongos> sh.status() --- Sharding Status --- sharding version: { "_id" : 1, "version" : 4, "minCompatibleVersion" : 4, "currentVersion" : 5, "clusterId" : ObjectId("59e17748c68473e873f81bc7") } shards: { "_id" : "shard0000", "host" : "192.168.80.8:27017" } { "_id" : "shard0001", "host" : "192.168.80.9:27017" } databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "test", "partitioned" : true, "primary" : "shard0001" } { "_id" : "student", "partitioned" : false, "primary" : "shard0000" } { "_id" : "student_db", "partitioned" : true, "primary" : "shard0000" } #數據庫分片已啓用
測試
#插入數據
mongos> for (i=1;i<=100000;i++) db.students.insert({name:"student"+i,age:(i%120),address:"china_nb"});
mongos>sh.status() # 查看數據庫分片狀態
student_db.students
shard key: { "age" : 1 }
chunks:
shard0000 2 # 數據被分佈在每一個shared上
shard0001 1
{ "age" : { "$minKey" : 1 } } -->> { "age" : 1 } on : shard0001 Timestamp(2, 0)
{ "age" : 1 } -->> { "age" : 119 } on : shard0000 Timestamp(2, 2)
{ "age" : 119 } -->> { "age" : { "$maxKey" : 1 } } on : shard0000 Timestamp3, 3)
總結
Mongodb在丟棄了關係型數據庫一些侷限性:事務
關係
。同時採用文檔形式對數據的存儲,加大數據存儲的同時加快數據的查詢效率。此外Mongodb還提供完備的HA解決方案和分片的分佈式策略。在不依賴於事務的大數據場景中,讓其作爲大數據處理有效的解決方案之一。