0%

mongoDB 基礎3


Authentication and Authorization

Authentication Authorization
識別對資料庫來說有效的使用者 識別有效的使用者中他們能做什麼操作
比喻: 例如一家公司的員工被允許進入辦公室 比喻: 公司的員工帳號允許進入辦公室並處理訂單
  • 每個user有專屬的roles,roles包含了一個或多個privileges,而privileges定義了該user能進入那些database和進行那些操作
  • Privileges
    • Resources: 能進入的database、collections
    • Actions: 能進行的操作,例如insert()
  • 資料庫的使用者可以有不同的分工 (Roles),被給予的權限也不一樣(可以避免意外的發生)
Administrator Developer/Your app Data Scientist
能管理資料庫的設定,新增使用者 需要進行新增、刪除、閱讀等操作(CRUD) 需要能夠抓取資料
不需要新增或取用資料 不需要新增使用者或管理資料庫設定 不需要進行新增、刪除等操作

開啟使用者認證功能的方式啟動MongoDB

1
2
3
sudo mongod --auth
//然後進入mongo shell
mongo
1
2
3
4
5
//兩種使用者登入mongo server的方式
//第一種是在mongo shell外面
mongo -u "username" -p "password"
//第二種進入mongo shell後
db.auth("username","password")
  • 在沒有建立任何一個user的情況下連接mongodb時,mongodb允許在localhost連接的狀態下新增user,且該user擁有完整的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//必須先進入amin database
use admin
db.createUser({user: "god", pwd: "god", roles: ["userAdminAnyDatabase"]})
//接著必須登入
db.auth('god', 'god')
//如果我們跳出shell直接在外面登入,必須表明該帳號在哪個database被建立,否則會登入失敗
mongo -u god -p god --authenticationDatabase admin

//接著可以進入別的database建立其他user
use shop
db.createUser({user: "user1", pwd: "user1", roles: ["readWrite"]})
//接著登入,要注意的是目前user1只能在shop database裡進行"readWrite"所給予的權限
db.auth({'user1', 'user1'})
//接著嘗試新增一個document會出現錯誤,mongo shell會告訴你因為有多個user被認證,因為剛剛的god沒有先登入
//可以先登出god再登入user1
db.logout()
//或是直接跳出mongo shell登入
mongo -u user1 -p user1 --authenticationDatabase shop

有哪些roles可以設定以及該roles有哪些權限可以參考官方文件

  • 有時一個帳號需要可以操作兩個database,可以使用updateUser去設定user權限
  • 有個重要的原則是,該user是在哪個database被建立的,就必須要在該database登入和登出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //要設定user權限必須登入最高權限的user
    use admin
    db.auth('god', 'god')
    use shop
    db.updateUser("user1", {roles: ["readWrite", {role: "readWrite", db: "blog"}]})
    //如此一來user1也可以有blog的readWrite權限,可以使用getUser()來確認
    db.getUser("user1")
    //可以取得以下資訊
    "_id" : "shop.user1",
    "userId" : UUID("20ef4181-c13b-4985-ab2f-c731ed577cb5"),
    "user" : "user1",
    "db" : "shop",
    "roles" : [
    {
    "role" : "readWrite",
    "db" : "shop"
    },
    {
    "role" : "readWrite",
    "db" : "blog"
    }
    ],
    "mechanisms" : [
    "SCRAM-SHA-1",
    "SCRAM-SHA-256"
    ]
    }
    //必須回到admin才可以登出god,再回到shop登入user1
    use admin
    db.logout()
    use shop
    db.auth("user1", "user1")
    use blog
    //進行操作

Encryption

  • mongodb使用TLS/SSL加密方式建立mongodb與mongodb driver之間的連接
  • 一旦加密建立,沒有特殊key的話就不能隨意與mongodb連線(在cmd下mongo會被擋住),也可以防止外部的惡意攻擊者的違法連線
  • 詳細SSL的相關設定參考官網

其他參考文件
Official “Encryption at Rest” Docs連結
Official Security Checklist連結

Perfromance, Fault Tolerancy&Deployment

Capped collection

  • capped collection是一種特殊的collection,可以設定他的最大儲存大小和儲存的document數,以確保該collection保持在一個簡潔的狀態下
  • capped collection裡的資料會保證一定按照新增的順序所排序(一般的collection則不保證)
1
2
3
//第一個參數為collection的名稱,第二個參數為一個物件,size是要求選項max則不是
//此範例的size設為最大不會超過10000byte和最大三個document,如果超過,最舊的資料會被刪除,新的資料會被插在最末尾
db.createCollection("capped", {capped: true, size: 10000, max: 3})

Replica Sets

  • 當我們寫入資料進資料庫時,實際上是先透過mongo shell傳達給mongodb server,mongodb server在傳達給primary node寫入資料(node其實也是mongodb server)
  • 我們可以增加更多的node,這些node被稱為Secondary node,所有的node集合稱為replica sets
  • 在一般的情況下,當insert、write等操作時都是跟primary node溝通(自動如此),但primary node會異步的把資料複製給secondary node
  • 在我們read資料時,如果primary node因為一些原因離線了,可以直接連接到secondary node,並把該node視為新的primary node來讀取資料,透過primary node就可以不只是read也可以write資料(write一定要透過primary node)
  • 這個機制提供了server的容錯率
  • 透過使用者主動設定,可以在read時直接跳過primary node與secondary node溝通,這樣可以確保在read資料時能以最快的速度達成目的,假設每秒要處理幾千個read就可以透過複數的node來達成

參考資料

Sharding (Horizontal Scaling)

  • 為了提高mongo server的效能,可以增加複數個server,這些server不會儲存相同的資料,而是為把資料切割分開儲存,這些資料會透過shard來分配
  • query(insert, write, delete等)就必須透過這些server(或正確的server),每個server管理不同的資料部位
  • 對mongo server來說,每個server都會有自己的shard,這些shard會是彼此的副本,這些server和client中間還有一個角色稱為mongos(Router)
  • 這個mongos有責任把inserts, read等operation發送給正確的shard,資料必須存在哪個server以及那些server必須提供查找的資料
  • 為了達成任務必須透過所謂的shard key,shard key存在於所有document,可以把他是為document的其中一個field,server就可以透過他知道該document是屬於誰
  • 使用find找資料時mongos有兩個選項能找到資料
    • 如果使用的條件跟不包含shard key,mongos就會向所有的server廣播,讓server交出需要的資料,並把這些資料組合起來再提供給client
    • 或是直接使用的條件包含shard key,mongos就可以直接跟負責的server索取資料
  • 所以如果有些find是要被頻繁執行的話,必須要有良好的設計,讓shard key被包含在搜尋的條件裡來提供效率

參考資料

Deploy mongoDB server

  • 要把mongodb部屬到雲端上而不是在本地端,必須要管理很多任務。必須管理shard, replica set, secure user, encryption, regular backup protect web server/network, update software等複雜的任務
  • 對大部分的開發者來說這些任務超出了他們的能力範圍,這時候可以透過Atlas來幫我們輕鬆達成上述的任務

Transaction

  • transaction必須要有replica set且mongo 4.0以上才可以運作
  • 假設有兩個collections user和post,user可以撰寫post,所以post會有一個field對應到是哪個user撰寫的(1對多),如果我們把其中一個user刪除了,他所建立的post應該也要一併刪除,但是可能會有user成功刪除了,但post沒有順利全部刪除,而transaction可以幫助我們解決這個問題
  • 有關連的這些document要不是一起成功刪除,不然就一起刪除失敗
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use blog
db.users.insertOne({_id: 1,user: "Max"})
db.post.insertMany([{title: "First post", userId: 1}, {title: "Second post", userId: 1}])
//雖然可以使用delete把_id為1的user刪除再把userId為1的post刪除,但是不能保證百分之百每次都成功,所以需要transaction
//以下操作必須在Atlas執行(要有replica set)
const session = db.getMongo().startSession()
//該session可以把所有指令綁在一起然後一起執行(這個session本質也是個object)
//通常下完指令server就會忘記執行者是誰,所以需要session讓server知道這些指令都是屬於該操作者的
const postsColl = session.getDatabase("blog").posts
const usersColl = session.getDatabase("blog").users
session.startTransaction()
usersColl.deleteOne({_id: 1})//執行這行後shell會顯示成功的訊息但實際上資料還在,可以把它看成是即將完成的任務
postsColl.deleteMany({userId: 1})
session.commitTransaction()//執行後才會真正把document刪除,如果其中一個動作出錯,server會回復成原本的狀態(一起成功或一起失敗)

//也可以終止
session.abortTransaction()

Transaction參考資料

From shell to driver

  • 有些任務必須分工合作,driver適合處理跟應用程式有關的邏輯處理
    • shell: Configure Database, Create Collection, Create Indexes
    • driver: CRUD operations, Aggregation Pipelines

使用Node.js連接Mongodb範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const mongodb = require('mongodb');

const MongoClient = mongodb.MongoClient;
const mongoDbUrl =
'mongodb+srv://maximilian:hqG9VedJmagiKhKo@cluster0-ntrwp.mongodb.net/shop?retryWrites=true';

let _db;

const initDb = callback => {
if (_db) {
console.log('Database is already initialized!');
return callback(null, _db);
}
MongoClient.connect(mongoDbUrl)
.then(client => {
_db = client;
callback(null, _db);
})
.catch(err => {
callback(err);
});
};

const getDb = () => {
if (!_db) {
throw Error('Database not initialzed');
}
return _db;
};

module.exports = {
initDb,
getDb
};
  • 藉由此段程式碼就可以在其他檔案連接資料庫而不重複與mongodb連線的程式碼
  • 透過共用同一個連線,由於driver預設提供的connection pooling,可以同時處理多個要求

透過driver來操作mongoDB 文件