MongoDB Replica Set設置,採用MongoDB 3.4

安裝MongoDB 3.4 for CentOS 7

vi /etc/yum.repos.d/mongodb.repo
mongodb-org-3.4]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.4/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-3.4.asc

然後在執行

yum install -y mongodb-org

另外,需要將selinux(/etc/selinux/config)設定為SELINUX=Passive或Disable,詳細可參考:
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/

MongoDB Replica Set介紹

MongoDB 算是一個很發展非常成熟的專案,大多數的系統裝起 MongoDB 應該都不會有太大的困難,今天介紹一下進階一點點的用法,Replica Set。資料庫系統做高可用 Replication (副本) 模式算是很常見的架構,在大陸稱為「主備模式」主要提供一份資料庫即時映射的副本,好讓主要的服務資料庫發生異常時可以接手工作。同時,另一個優勢就是可以進行資料備援,或者用來進行查詢。此外,當資料庫大到一定的程度,傳統的 dump 工具將很難完成任務,備援的方法就可以靠 Replication 來完成。

現在的 Open Source 系統發展得很快,官方提供的版本就早已經支援 Replication,想想以前的 PostgreSQL 在 5.x 的時候還要自己 Patch 第三方 Project 進行編譯,比起來真的省事多了。

官方有提供三種 Replica Set 架構 (Pattern),如下:

  • Three Member Replica Sets
  • Replica Sets with Four or More Members
  • Geographically Distributed Replica Sets

第一種 Three Member Replica Sets 是最常用的架構,由三個 MongoDB 節點組成,每個節點其實都是 Mongod 服務。第二種由更多的節點組成,備援能力更強,但我沒有研究,無從介紹。最後一種 Distributed Replica Sets 主要滿足異地備援的需求,這個聽起來不錯。

今天來介紹 MongoDB 最典型的副本架構,主要由三個節點構成,如下:

正常情況對 Primary 節點進行讀寫,資料會自動複製 (Replication) 到另外兩組 Secondary 節點,這些節點彼此透過 Heartbeat (每隔一段時間試探節點是否存活的方式) 檢測狀態,正常的情況每 2 秒會送出 Heartbeat,若是超過 10 秒沒有回覆,那就是 GG 了。如下:

當主要連線的節點發生故障時,會選出一台 Secondary 成為 Primary 接手工作,這樣的能力稱為 Automatic Failover,如下:

安裝與設定MongoDB Replica Set

大概知道工作原理以後,那麼我們來看看如何設定 Replica Set 架構。

開始之前我們先在 /etc/hosts 定義這三台要安裝的 hostname 與 IP Address,當然如果您想透過 DNS 服務來管理也是可以的。

vi /etc/hosts
10.21.1.45 mongsh1
10.21.1.46 mongsh2
10.21.1.44 mongsh3 

建立一個讓 Replica Set 專用的全新資料庫目錄

mkdir -p /home/mongod
chown -R mongodb:mongodb /home/mongod

三台伺服器皆需要做此設定,以方便確認資料庫的目錄以及位址。

設定 Replica Set 服務的 Mongod 設定檔(先不啟用驗證模式)

vi /etc/mongod.co
systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log

storage:
  dbPath: /home/mongod
  journal:
    enabled: true

processManagement:
  fork: true  
  pidFilePath: /var/run/mongodb/mongod.pid  # location of pidfile

net:
  port: 27019
  bindIp: 0.0.0.0  

這裡我們將 Replica Set 服務設定在 27019 Port,主要是因為之後會繼續建立 Sharding 架構,刻意避開標準的 27017 Port,保留 27017 Port 給之後的 Mongo Router 使用。如果您不打算建構 Sharding,那就直接使用 27017 即可,讓應用程式端不需要修改就可以使用。修改完設定檔後記得重新啟動 mongod 服務:

systemctl start mongod

先連線進到第一台 mongod 設定管理者帳密,上述可先在第一台伺服器設置(primary)

mongo mongsh1:27019
use admin
db.createUser( {
    user: "myUserAdmin",
    pwd: "",
    roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
  });
db.createUser( {
    user: "siteRootAdmin",
    pwd: "",
    roles: [ { role: "root", db: "admin" } ]
  });

也可以多新增一個admin帳號

db.createUser( {
    user: "admin",
    pwd: "",
    roles: [ { role: "root", db: "admin" } ]
  });

接著,透過以下命令建立一個 Key,並且複製到每一台機器 (mongsh1, mongsh2, mongsh3),之後每個 Mongod 實體會透過這把 Key 進行資料同步,接下來的動作比較複雜,順序不可以搞錯。很多人在這裡搞不定驗證功能,索性關閉驗證服務,這樣其實比較沒有保障,乖乖啟用驗證還是比較好的。

openssl rand -base64 741 > /home/mongod/mongodb-keyfile
chmod 600 /home/mongod/mongodb-keyfile
chown mongodb.mongodb /home/mongod/mongodb-keyfile

記得把這把 Key 放到每一台機器的 /home/mongodb-keyfile 檔案中。接下來我們就要啟用 Replica Set 與認證模式囉。再度編輯 /etc/mongod.conf 設定檔:

vim /etc/mongod.conf
security:
  keyFile: /var/lib/mongo/mongodb-keyfile
replication:
  replSetName: rs

在另外兩台伺服器可以修改設定檔為下

systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log
storage:
  dbPath: /home/mongod
  journal:
    enabled: true
processManagement:
  fork: true  
  pidFilePath: /var/run/mongodb/mongod.pid  # location of pidfile
net:
  port: 27019
  bindIp: 0.0.0.0  
security:
  keyFile: /home/mongod/mongodb-keyfile
replication:
 replSetName: bes

重新啟動 mongod 服務(另外兩台也要喔)

systemctl restart mongod

連線至剛剛的第一台 Mongod 並初始化 Replica Set,如下:

mongo mongsh1:27019
use admin
db.auth("siteRootAdmin", "");
rs.initiate()
rs.conf()

注意:正常完成時會有下面訊息出現:

接著新增另外兩台 Replica Set 節點,輸入以下命令:

rs.add("mongsh2:27019")
rs.add("mongsh3:27019")
rs.status()

注意:正常完成時會有下面訊息出現:

bes:PRIMARY> rs.status()
{
	"set" : "bes",
	"date" : ISODate("2017-09-09T14:51:02.381Z"),
	"myState" : 1,
	"term" : NumberLong(3),
	"heartbeatIntervalMillis" : NumberLong(2000),
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1504968659, 1),
			"t" : NumberLong(3)
		},
		"appliedOpTime" : {
			"ts" : Timestamp(1504968659, 1),
			"t" : NumberLong(3)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1504968659, 1),
			"t" : NumberLong(3)
		}
	},
	"members" : [
		{
			"_id" : 0,
			"name" : "mongsh1:27019",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 4051,
			"optime" : {
				"ts" : Timestamp(1504968659, 1),
				"t" : NumberLong(3)
			},
			"optimeDate" : ISODate("2017-09-09T14:50:59Z"),
			"electionTime" : Timestamp(1504964638, 1),
			"electionDate" : ISODate("2017-09-09T13:43:58Z"),
			"configVersion" : 67972,
			"self" : true
		},
		{
			"_id" : 1,
			"name" : "mongsh2:27019",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 2961,
			"optime" : {
				"ts" : Timestamp(1504968659, 1),
				"t" : NumberLong(3)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1504968659, 1),
				"t" : NumberLong(3)
			},
			"optimeDate" : ISODate("2017-09-09T14:50:59Z"),
			"optimeDurableDate" : ISODate("2017-09-09T14:50:59Z"),
			"lastHeartbeat" : ISODate("2017-09-09T14:51:00.676Z"),
			"lastHeartbeatRecv" : ISODate("2017-09-09T14:51:01.607Z"),
			"pingMs" : NumberLong(0),
			"syncingTo" : "mongsh1:27019",
			"configVersion" : 67972
		},
		{
			"_id" : 2,
			"name" : "mongsh3:27019",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 2961,
			"optime" : {
				"ts" : Timestamp(1504968659, 1),
				"t" : NumberLong(3)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1504968659, 1),
				"t" : NumberLong(3)
			},
			"optimeDate" : ISODate("2017-09-09T14:50:59Z"),
			"optimeDurableDate" : ISODate("2017-09-09T14:50:59Z"),
			"lastHeartbeat" : ISODate("2017-09-09T14:51:00.676Z"),
			"lastHeartbeatRecv" : ISODate("2017-09-09T14:51:01.606Z"),
			"pingMs" : NumberLong(0),
			"syncingTo" : "mongsh1:27019",
			"configVersion" : 67972
		}
	],
	"ok" : 1
}
bes:PRIMARY> 

這樣就完成了,但如果出現有Statup誒
如果有伺服器出現:
“stateStr” : “STARTUP”,

例如:

bes:PRIMARY> rs.status()
{
	"set" : "bes",
	"date" : ISODate("2017-09-09T14:51:02.381Z"),
	"myState" : 1,
	"term" : NumberLong(3),
	"heartbeatIntervalMillis" : NumberLong(2000),
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1504968659, 1),
			"t" : NumberLong(3)
		},
		"appliedOpTime" : {
			"ts" : Timestamp(1504968659, 1),
			"t" : NumberLong(3)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1504968659, 1),
			"t" : NumberLong(3)
		}
	},
	"members" : [
		{
			"_id" : 0,
			"name" : "mongsh1:27019",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 4051,
			"optime" : {
				"ts" : Timestamp(1504968659, 1),
				"t" : NumberLong(3)
			},
			"optimeDate" : ISODate("2017-09-09T14:50:59Z"),
			"electionTime" : Timestamp(1504964638, 1),
			"electionDate" : ISODate("2017-09-09T13:43:58Z"),
			"configVersion" : 67972,
			"self" : true
		},
		{
			"_id" : 1,
			"name" : "mongsh2:27019",
			"health" : 1,
			"state" : 0,
			"stateStr" : "STARTUP",
			"uptime" : 2961,
			"optime" : {
				"ts" : Timestamp(1504968659, 1),
				"t" : NumberLong(3)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1504968659, 1),
				"t" : NumberLong(3)
			},
			"optimeDate" : ISODate("2017-09-09T14:50:59Z"),
			"optimeDurableDate" : ISODate("2017-09-09T14:50:59Z"),
			"lastHeartbeat" : ISODate("2017-09-09T14:51:00.676Z"),
			"lastHeartbeatRecv" : ISODate("2017-09-09T14:51:01.607Z"),
			"pingMs" : NumberLong(0),
			"syncingTo" : "mongsh1:27019",
			"configVersion" : 67972
		},
		{
			"_id" : 2,
			"name" : "mongsh3:27019",
			"health" : 1,
			"state" : 0,
			"stateStr" : "STARTUP",
			"uptime" : 2961,
			"optime" : {
				"ts" : Timestamp(1504968659, 1),
				"t" : NumberLong(3)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1504968659, 1),
				"t" : NumberLong(3)
			},
			"optimeDate" : ISODate("2017-09-09T14:50:59Z"),
			"optimeDurableDate" : ISODate("2017-09-09T14:50:59Z"),
			"lastHeartbeat" : ISODate("2017-09-09T14:51:00.676Z"),
			"lastHeartbeatRecv" : ISODate("2017-09-09T14:51:01.606Z"),
			"pingMs" : NumberLong(0),
			"syncingTo" : "mongsh1:27019",
			"configVersion" : 67972
		}
	],
	"ok" : 1
}
#mongo localhost:27019
bes:PRIMARY> (有時候出現是bes:SECONDARY>,都可以設置)
bes:PRIMARY>use admin
bes:PRIMARY>db.auth("siteRootAdmin","")
bes:PRIMARY>var cfg={_id:"bes",members:[{_id:0,host:"mongsh1:27019"},{_id:1,host:"mongsh2:27019"},{_id:2,host:"mongsh3:27019"}]}
bes:PRIMARY>rs.reconfig(cfg)
bes:PRIMARY>rs.status()

這樣就大功告成了!!!

 

Reference:

MongoDB Replica Set 高可用性架構搭建

mongodb 副本集搭建