1. 程式人生 > 資料庫 >聊聊MongoDB(二):MongoDB的叢集高可用

聊聊MongoDB(二):MongoDB的叢集高可用

MongoDB的叢集高可用

前言

本節主要介紹下MongoDB的高可用叢集如何搭建

MongoDB主從複製架構原理和缺陷

master-slave架構中master節點負責資料的讀寫,slave沒有寫入許可權只負責讀取資料。
在這裡插入圖片描述
在主從結構中,主節點的操作記錄成為oplog(operation log)。oplog儲存在系統資料庫local的oplog.$main集合中,這個集合的每個文件都代表主節點上執行的一個操作。從伺服器會定期從主伺服器中獲取oplog記錄,然後在本機上執行!對於儲存oplog的集合,MongoDB採用的是固定集合,也就是說隨著操作過多,新的操作會覆蓋舊的操作!

主從結構沒有自動故障轉移功能,需要指定master和slave端,不推薦在生產中使用。
mongodb4.0後不再支援主從複製!

複製集replica sets

什麼是複製集

在這裡插入圖片描述
複製集是由一組擁有相同資料集的mongod例項做組成的叢集。
複製集是一個叢集,它是2臺及2臺以上的伺服器組成,以及複製整合員包括Primary主節點,secondary從節點和投票節點。
複製集提供了資料的冗餘備份,並在多個伺服器上儲存資料副本,提高了資料的可用性,保證資料的安全性。

為什麼要使用複製集

1.高可用
防止裝置(伺服器、網路)故障。
提供自動failover 功能。
技術來保證高可用
2.災難恢復
當發生故障時,可以從其他節點恢復 用於備份。

3.功能隔離
我們可以在備節點上執行讀操作,減少主節點的壓力
比如:用於分析、報表,資料探勘,系統任務等。

複製集叢集架構原理

一個複製集中Primary節點上能夠完成讀寫操作,Secondary節點僅能用於讀操作。Primary節點需要記錄所有改變資料庫狀態的操作,這些記錄儲存在 oplog 中,這個檔案儲存在 local 資料庫,各個Secondary節點通過此 oplog 來複制資料並應用於本地,保持本地的資料與主節點的一致。oplog 具有冪等性,即無論執行幾次其結果一致,這個比 mysql 的二進位制日誌更好用。

{
"ts" : Timestamp(1446011584, 2),
"h" : NumberLong("1687359108795812092"),
"v" : 2,
"op" : "i",
"ns" : "test.nosql",
"o" : { "_id" : ObjectId("563062c0b085733f34ab4129"), "name" : "mongodb",
"score" : "10"}
}
ts:操作時間,當前timestamp + 計數器,計數器每秒都被重置
h:操作的全域性唯一標識
v:oplog版本資訊
op:操作型別
 i:插入操作
 u:更新操作
 d:刪除操作
 c:執行命令(如createDatabase,dropDatabase)
n:空操作,特殊用途
ns:操作針對的集合
o:操作內容
o2:更新查詢條件,僅update操作包含該欄位

複製集資料同步分為初始化同步和keep複製同步。初始化同步指全量從主節點同步資料,如果Primary節點資料量比較大同步時間會比較長。而keep複製指初始化同步過後,節點之間的實時同步一般是增量同步。
初始化同步有以下兩種情況會觸發:
(1) Secondary第一次加入。
(2) Secondary落後的資料量超過了oplog的大小,這樣也會被全量複製。

MongoDB的Primary節點選舉基於心跳觸發。一個複製集N個節點中的任意兩個節點維持心跳,每個節點維護其他N-1個節點的狀態。
在這裡插入圖片描述
心跳檢測:
整個叢集需要保持一定的通訊才能知道哪些節點活著哪些節點掛掉。mongodb節點會向副本集中的其他節點每2秒就會發送一次pings包,如果其他節點在10秒鐘之內沒有返回就標示為不能訪問。每個節點內部都會維護一個狀態對映表,表明當前每個節點是什麼角色、日誌時間戳等關鍵資訊。如果主節點發現自己無法與大部分節點通訊則把自己降級為secondary只讀節點。

主節點選舉觸發的時機:
第一次初始化一個複製集
Secondary節點權重比Primary節點高時,發起替換選舉
Secondary節點發現叢集中沒有Primary時,發起選舉
Primary節點不能訪問到大部分(Majority)成員時主動降級

當觸發選舉時,Secondary節點嘗試將自身選舉為Primary。主節點選舉是一個二階段過程+多數派協議。

第一階段:
檢測自身是否有被選舉的資格 如果符合資格會向其它節點發起本節點是否有選舉資格的FreshnessCheck,進行同僚仲裁
第二階段:
發起者向叢集中存活節點發送Elect(選舉)請求,仲裁者收到請求的節點會執行一系列合法性檢查,如果檢查通過,則仲裁者(一個複製集中最多50個節點 其中只有7個具有投票權)給發起者投一票。
pv0通過30秒選舉鎖防止一次選舉中兩次投票。
pv1使用了terms(一個單調遞增的選舉計數器)來防止在一次選舉中投兩次票的情況。
多數派協議:
發起者如果獲得超過半數的投票,則選舉通過,自身成為Primary節點。獲得低於半數選票的原因,除了常見的網路問題外,相同優先順序的節點同時通過第一階段的同僚仲裁併進入第二階段也是一個原因。因此,當選票不足時,會sleep[0,1]秒內的隨機時間,之後再次嘗試選舉。

複製集搭建

在這裡插入圖片描述
主節點配置 mongo_37017.conf

# 主節點配置 
dbpath=/data/mongo/data/server1
bind_ip=0.0.0.0
port=37017
fork=true
logpath=/data/mongo/logs/server1.log
replSet=testCluster

從節點1配置 mongo_37018.conf

dbpath=/data/mongo/data/server2
bind_ip=0.0.0.0
port=37018
fork=true
logpath=/data/mongo/logs/server2.log
replSet=testCluster

從節點2配置 mongo_37019.conf

dbpath=/data/mongo/data/server3
bind_ip=0.0.0.0
port=37019
fork=true
logpath=/data/mongo/logs/server3.log
replSet=testCluster

初始化節點配置
啟動三個節點 然後進入任意一個節點 執行如下命令:

var cfg ={"_id":"testsCluster",
     "protocolVersion" : 1,
"members":[
{"_id":1,"host":"192.168.211.133:37017","priority":10},
{"_id":2,"host":"192.168.211.133:37018"}
]
}
rs.initiate(cfg)
rs.status()

節點的動態增刪

增加節點
rs.add("192.168.211.133:37019")
刪除slave 節點
rs.remove("192.168.211.133:37019")
增加仲裁節點
rs.addArb("192.168.211.133:37020")

節點說明:
PRIMARY 節點: 可以查詢和新增資料
SECONDARY 節點:只能查詢 不能新增 基於priority 權重可以被選為主節點
ARBITER 節點: 不能查詢資料 和新增資料 ,不能變成主節點

複製整合員的配置引數

在這裡插入圖片描述

分片叢集 Shard Cluster

  • 什麼是分片
    分片(sharding)是MongoDB用來將大型集合水平分割到不同伺服器(或者複製集)上所採用的方法。
    不需要功能強大的大型計算機就可以儲存更多的資料,處理更大的負載。
  • 為什麼要分片
    1.儲存容量需求超出單機磁碟容量。
    2.活躍的資料集超出單機記憶體容量,導致很多請求都要從磁碟讀取資料,影響效能。
    3.IOPS超出單個MongoDB節點的服務能力,隨著資料的增長,單機例項的瓶頸會越來越明顯。
    4.副本集具有節點數量限制。
    垂直擴充套件:增加更多的CPU和儲存資源來擴充套件容量。
    水平擴充套件:將資料集分佈在多個伺服器上。水平擴充套件即分片。
  • 分片的工作原理
    在這裡插入圖片描述
    分片叢集由以下3個服務組成:
    Shards Server: 每個shard由一個或多個mongod程序組成,用於儲存資料。
    Router Server: 資料庫叢集的請求入口,所有請求都通過Router(mongos)進行協調,不需要在應用程
    序新增一個路由選擇器,Router(mongos)就是一個請求分發中心它負責把應用程式的請求轉發到對應的Shard伺服器上。
    Config Server: 配置伺服器。儲存所有資料庫元資訊(路由、分片)的配置。
  • 片鍵(shard key)
    為了在資料集合中分配文件,MongoDB使用分片主鍵分割集合
  • 區塊(chunk)
    在一個shard server內部,MongoDB還是會把資料分為chunks,每個chunk代表這個shardserver內部一部分資料。MongoDB分割分片資料到區塊,每一個區塊包含基於分片主鍵的左閉右開的區間範圍。
  • 分片策略
    • 範圍分片(Range based sharding)
      在這裡插入圖片描述
      範圍分片是基於分片主鍵的值切分資料,每一個區塊將會分配到一個範圍。
      範圍分片適合滿足在一定範圍內的查詢,例如查詢X的值在[20,30)之間的資料,mongo 路由根據Config server中儲存的元資料,可以直接定位到指定的shard的Chunk中。
      缺點: 如果shard key有明顯遞增(或者遞減)趨勢,則新插入的文件多會分佈到同一個chunk,無法擴充套件寫的能力
    • hash分片(Hash based sharding)
      在這裡插入圖片描述
      Hash分片是計算一個分片主鍵的hash值,每一個區塊將分配一個範圍的hash值。
      Hash分片與範圍分片互補,能將文件隨機的分散到各個chunk,充分的擴充套件寫能力,彌補了範圍分片的不足,缺點是不能高效的服務範圍查詢,所有的範圍查詢要分發到後端所有的Shard才能找出滿足條件的文件。
    • 組合片鍵 A + B(雜湊思想 不能是直接hash)
      資料庫中沒有比較合適的片鍵供選擇,或者是打算使用的片鍵基數太小(即變化少如星期只有7天可變化),可以選另一個欄位使用組合片鍵,甚至可以新增冗餘欄位來組合。一般是粗粒度+細粒度進行組合。

合理的選擇shard key
無非從兩個方面考慮,資料的查詢和寫入,最好的效果就是資料查詢時能命中更少的分片,資料寫入時能夠隨機的寫入每個分片,關鍵在於如何權衡效能和負載。

分片叢集的搭建過程

配置 並啟動config 節點叢集
節點1 config-17017.conf

# 資料庫檔案位置
dbpath=config/config1
#日誌檔案位置
logpath=config/logs/config1.log
# 以追加方式寫入日誌
logappend=true
# 是否以守護程序方式執行
fork = true
bind_ip=0.0.0.0
port = 17017
# 表示是一個配置伺服器
configsvr=true
#配置伺服器副本集名稱
replSet=configsvr

節點2 config-17018.conf

dbpath=config/config2
#日誌檔案位置
logpath=config/logs/config.log
# 以追加方式寫入日誌
logappend=true
# 是否以守護程序方式執行
fork = true
bind_ip=0.0.0.0
port = 17018
# 表示是一個配置伺服器
configsvr=true
#配置伺服器副本集名稱
replSet=configsvr

節點3 config-17019.conf’

# 資料庫檔案位置
dbpath=config/config3
#日誌檔案位置
logpath=config/logs/config3.log
# 以追加方式寫入日誌
logappend=true
# 是否以守護程序方式執行
fork = true
bind_ip=0.0.0.0
port = 17019
# 表示是一個配置伺服器
configsvr=true
#配置伺服器副本集名稱
replSet=configsvr

啟動配置節點

./bin/mongod -f config/config-17017.conf
./bin/mongod -f config/config-17018.conf
./bin/mongod -f config/config-17019.conf

進入任意節點的mongo shell 並新增 配置節點叢集 注意use admin

./bin/mongo  --port 17017
use admin
var cfg ={"_id":"configsvr",
"members":[
{"_id":1,"host":"192.168.211.133:17017"},
{"_id":2,"host":"192.168.211.133:17018"},
{"_id":3,"host":"192.168.211.133:17019"}]
};
rs.initiate(cfg)

配置shard叢集
shard1叢集搭建37017到37019

dbpath=shard/shard1/shard1-37017
bind_ip=0.0.0.0
port=37017
fork=true
logpath=shard/shard1/shard1-37017.log
replSet=shard1
shardsvr=true
dbpath=shard/shard1/shard1-37018
bind_ip=0.0.0.0
port=37018
fork=true
logpath=shard/shard1/logs/shard1-37018.log
replSet=shard1
shardsvr=true 
dbpath=shard/shard1/shard1-37019
bind_ip=0.0.0.0
port=37019
fork=true
logpath=shard/shard1/logs/shard1-37019.log
replSet=shard1
shardsvr=true

啟動每個mongod 然後進入其中一個進行叢集配置
var cfg ={"_id":"shard1",
"protocolVersion" : 1,
"members":[
{"_id":1,"host":"192.168.211.133:37017"},
{"_id":2,"host":"192.168.211.133:37018"},
{"_id":3,"host":"192.168.211.133:37019"}
]
};
rs.initiate(cfg)
rs.status()

shard2叢集搭建47017到47019

dbpath=shard/shard2/shard2-47017
bind_ip=0.0.0.0
port=47017
fork=true
logpath=shard/shard2/logs/shard2-47017.log
replSet=shard2
shardsvr=true 
dbpath=shard/shard2/shard2-47018
bind_ip=0.0.0.0
port=47018
fork=true
logpath=shard/shard2/logs/shard2-47018.log
replSet=shard2
shardsvr=true 
dbpath=shard/shard2/shard2-47019
bind_ip=0.0.0.0
port=47019
fork=true
logpath=shard/shard2/logs/shard2-47019.log
replSet=shard2
shardsvr=true
啟動每個mongod 然後進入其中一個進行叢集配置
var cfg ={"_id":"shard2",
"protocolVersion" : 1,
"members":[
{"_id":1,"host":"192.168.211.133:47017"},
{"_id":2,"host":"192.168.211.133:47018"},
{"_id":3,"host":"192.168.211.133:47019"}
]
};
rs.initiate(cfg)
rs.status()

配置和啟動 路由節點
route-27017.conf

port=27017
bind_ip=0.0.0.0
fork=true
logpath=route/logs/route.log
configdb=configsvr/192.168.211.133:17017,192.168.211.133:17018,192.168.211.133:1
7019

啟動路由節點使用 mongos (注意不是mongod)

./bin/mongos -f route/route-27017.conf

mongos(路由)中新增分片節點
進入路由mongos

mongo  --port 27017
sh.status()   
sh.addShard("shard1/192.168.211.133:37017,192.168.211.133:37018,192.168.211.133:
37019");
sh.addShard("shard2/192.168.211.133:47017,192.168.211.133:47018,192.168.211.133:
47019");
sh.status()

開啟資料庫和集合分片(指定片鍵)
繼續使用mongos完成分片開啟和分片大小設定

為資料庫開啟分片功能
sh.enableSharding("test_resume")
為指定集合開啟分片功能
sh.shardCollection("test_resume.test_resume_datas",{"片鍵欄位名如 name":索引說
明})

MongoDB安全認證

安全認證概述

MongoDB 預設是沒有賬號的,可以直接連線,無須身份驗證。實際專案中肯定是要許可權驗證的,否則後果不堪設想。從2016年開始 發生了多起MongoDB黑客贖金事件,大部分MongoDB安全問題 暴露出了安全問題的短板其實是使用者,首先使用者對於資料庫的安全不重視,其次使用者在使用過程中可能沒有養成定期備份的好習慣,最後是企業可能缺乏有經驗和技術的專業人員。所以對MongoDB進行安全認證是必須要做的。

使用者相關操作

  • 切換到admin資料庫對使用者的新增
    use admin;
    db.createUser(userDocument):用於建立 MongoDB 登入使用者以及分配許可權的方法
db.createUser(
{
 user: "賬號",
 pwd: "密碼",
 roles: [
 { role: "角色", db: "安全認證的資料庫" },
 { role: "角色", db: "安全認證的資料庫" }
 ]
}
)

user:建立的使用者名稱稱,如 admin、root
pwd:使用者登入的密碼
roles:為使用者分配的角色,不同的角色擁有不同的許可權,引數是陣列,可以同時設定多個
role:角色,MonngoDB 已經約定好的角色,不同的角色對應不同的許可權 後面會對role做詳細解釋
db:資料庫例項名稱,如 MongoDB 4.0.2 預設自帶的有 admin、local、config、test 等,即為哪個資料庫例項 設定使用者
舉例:

db.createUser(
{
user:"root",
pwd:"123321",
roles:[{role:"root",db:"admin"}]
}
)
  • 以auth 方向啟動mongod
    ./bin/mongod -f conf/mongo.conf --auth
    (也可以在mongo.conf 中新增auth=true 引數)
  • 驗證使用者
    db.auth(“賬號”,“密碼”)

角色

資料庫內建的角色

read:允許使用者讀取指定資料庫
readWrite:允許使用者讀寫指定資料庫
dbAdmin:允許使用者在指定資料庫中執行管理函式,如索引建立、刪除,檢視統計或訪問
system.profile
userAdmin:允許使用者向system.users集合寫入,可以找指定資料庫裡建立、刪除和管理使用者
clusterAdmin:只在admin資料庫中可用,賦予使用者所有分片和複製集相關函式的管理許可權
readAnyDatabase:只在admin資料庫中可用,賦予使用者所有資料庫的讀許可權
readWriteAnyDatabase:只在admin資料庫中可用,賦予使用者所有資料庫的讀寫許可權
userAdminAnyDatabase:只在admin資料庫中可用,賦予使用者所有資料庫的userAdmin許可權
dbAdminAnyDatabase:只在admin資料庫中可用,賦予使用者所有資料庫的dbAdmin許可權
root:只在admin資料庫中可用。超級賬號,超級許可權
dbOwner:庫擁有者許可權,即readWrite、dbAdmin、userAdmin角色的合體

各個型別使用者對應的角色

資料庫使用者角色:read、readWrite
資料庫管理角色:dbAdmin、dbOwner、userAdmin
叢集管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager
備份恢復角色:backup、restore;
所有資料庫角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、 dbAdminAnyDatabase
超級使用者角色:root
這裡還有幾個角色間接或直接提供了系統超級使用者的訪問(dbOwner 、userAdmin、userAdminAnyDatabase

單機安全認證實現流程

建立 mydb1 資料庫並建立了兩個使用者,zhangsan 擁有讀寫許可權,lisi 擁有隻讀許可權測試這兩個賬戶的許可權。
以超級管理員登入測試許可權

  1. 建立管理員
    MongoDB 服務端開啟安全檢查之前,至少需要有一個管理員賬號,admin 資料庫中的使用者都被視為管理員
    如果 admin 庫沒有任何使用者的話,即使在其他資料庫中建立了使用者,啟用身份驗證,預設的連線方式依然會有超級許可權,即仍然可以不驗證賬號密碼照樣能進行 CRUD,安全認證相當於無效。
>use admin
switched to db admin
> db
admin
> db.createUser(
... {
... user:"root",
... pwd:"123456",
... roles:[{role:"root",db:"admin"}]
... })
  1. 建立普通使用者
    如下所示 mydb1 是自己新建的資料庫,沒安全認證之前可以隨意 CRUD,其餘的都是 mongoDB4.0.2 自帶的資料庫
>show dbs
admin  0.000GB
config 0.000GB
local  0.000GB
mydb1  0.000GB
> use mydb1
switched to db mydb1
> db.c1.insert({name:"testdb1"})
> db.c1.insert({name:"testdb1"})
> show tables
c1
c2
> db.c1.find()

為 admin 庫建立管理員之後,現在來為 普通資料庫建立普通使用者,以 mydb1 為例,方式與建立管理員一致,切換到指定資料庫進行建立即可。
如下所示,為 mydb1 資料庫建立了兩個使用者,zhangsan 擁有讀寫許可權,lisi 擁有隻讀許可權,密碼都是 123456.

use mydb1
switched to db mydb1
> db
mydb1
> db.createUser({
... user:"zhangsan",
... pwd:"123456",
... roles:[{role:"readWrite",db:"mydb1"}]
... })
> db.createUser({
... user:"lisi",
... pwd:"123456",
... roles:[{role:"read",db:"mydb1"}]
... })

接著從客戶端關閉 MongoDB 服務端,之後服務端會以安全認證方式進行啟動
3. MongoDB 安全認證方式啟動
mongod --dbpath=資料庫路徑 --port=埠 --auth
也可以在配置檔案中 加入 auth=true
4. 分別以普通使用者登入驗證許可權
普通使用者現在仍然像以前一樣進行登入,如下所示直接登入進入 mydb1 資料庫中,登入是成功的,只是登入後日志少了很多東西,而且執行 show dbs 命令,以及 show tables 等命令都是失敗的,即使沒有被安全認證的資料庫,使用者同樣操作不了,這都是因為許可權不足,一句話:使用者只能在自己許可權範圍內的資料庫中進行操作

mongo localhost:57017/mydb1
> show dbs

如下所示,登入之後必須使用 db.auth(“賬號”,“密碼”) 方法進行安全認證,認證通過,才能進行許可權範圍內的操作

> db.auth("zhangsan","123456")
1
> show dbs
mydb1 0.000GB
> show tables
c1
c2
  1. 以管理員登入驗證許可權
    客戶端管理員登入如下所示 管理員 root 登入,安全認證通過後,擁有對所有資料庫的所有許可權。
mongo localhost:57017
> use admin
switched to db admin
> db.auth("root","root")
1
> show dbs

分片叢集安全認證

  1. 開啟安全認證之前 進入路由建立管理員和普通使用者.。參考單機安全認證
  2. 關閉所有的配置節點 分片節點 和 路由節點
安裝psmisc 
yum install psmisc
安裝完之後可以使用killall 命令 快速關閉多個程序
killall mongod
  1. 生成金鑰檔案 並修改許可權
openssl rand -base64 756 > data/mongodb/testKeyFile.file
chmod 600 data/mongodb/keyfile/testKeyFile.file
  1. 配置節點叢集和分片節點叢集開啟安全認證和指定金鑰檔案
auth=true
keyFile=data/mongodb/testKeyFile.file
  1. 在路由配置檔案中 設定金鑰檔案
keyFile=data/mongodb/testKeyFile.file
  1. 啟動所有的配置節點 分片節點 和 路由節點 使用路由進行許可權驗證
    可以編寫一個shell 指令碼 批量啟動
./bin/mongod -f config/config-17017.conf
./bin/mongod -f config/config-17018.conf
./bin/mongod -f config/config-17019.conf
./bin/mongod -f shard/shard1/shard1-37017.conf
./bin/mongod -f shard/shard1/shard1-37018.conf
./bin/mongod -f shard/shard1/shard1-37019.conf
./bin/mongod -f shard/shard2/shard2-47017.conf
./bin/mongod -f shard/shard2/shard2-47018.conf
./bin/mongod -f shard/shard2/shard2-47019.conf
./bin/mongos -f route/route-27017.conf
  1. Spring boot 連線安全認證的分片叢集
spring.data.mongodb.username=賬號
spring.data.mongodb.password=密碼
#spring.data.mongodb.uri=mongodb://賬號:密碼@IP:埠/資料庫名

總結

本節主要介紹了MongoDB的高可用叢集搭建以及它的選舉思想