Mongo 代理程序實現-復制集搭建及抓包篇
如標題所述,本系列教程是教你如何手擼一個 mongo 代理程序。教程分為兩篇, 復制集搭建及抓包篇 和 代碼實戰篇 。
Q: 這程序有什么卵用?
為了更加貼近實際生產環境,我會從數據庫復制集搭建,wireshark 抓取解析 mongo 包以及實際 go 邏輯編寫,三個部分進行詳細的講解,最終目標是為了實現一個具備讀寫分離,自動主備切換的稍微健壯一點的 mongo 代理程序。
文章可能有點長,請耐心閱讀。
復制集搭建
復制集架構我選擇了一主 (Primary),一備 (Secondary),一仲裁 (Arbiter):
- 主節點 (Primary): 只能有一個,用于接收所有的寫操作,并將寫入的數據同步到其它備份節點上
- 備份節點 (Secondary): 可以有多個,用于備份主節點的數據,可以參與復制集選舉
- 仲裁節點 (Arbiter): 不參與選舉,不同步主節點數據,當主節點掛了后,自動從備份節點中選舉一個作為主節點
閑話不多說,直接上配置文件吧:
## mgo_conf1.yaml
processManagement:
fork: true #mongod以守護進程的方式在后臺執行
net:
bindIp: 127.0.0.1
port: 21000
storage:
dbPath: /data/db1 #mongo數據文件存儲地址
systemLog:
destination: file
path: "/data/db1/log/mongo.log" #系統日志存儲路徑
logAppend: true #日志以追加的方式寫入文件
storage:
journal:
enabled: true #日志優先
replication:
replSetName: mongo_dal #復制集名
#security:
# keyFile: /Users/geemo/etc/mongo/keyfile #用于復制集成員間安全驗證,指定這個字段相當于同時開啟了數據庫權限驗證
根據上面文件配置三份,注意 net.port, storage.dbPath, systemLog.path 不要重復啦。
接下來根據創建好的三份配置來啟動 mongod 守護進程:
$ mongod -f mgo_conf1.yaml
啟動失敗的話,請查看下是否是端口占用啦,或者是否是指定的存儲路徑根本就沒有創建。
全部啟動成功后,用 mongo client 連上第一個節點。
$ mongo 127.0.0.1:21000
接下來需要進行復制集初始化工作:
> rs.initiate({
_id: 'mongo_dal', //復制集名
members: [
{ _id: 0, host: '127.0.0.1:21000' }
]
})
添加備份節點以及裁節點:
> rs.add('127.0.0.1:22000')
> rs.addArb('127.0.0.1:23000')
所有都成功后,重新用 mongo client 連接第一個節點,會發現命令行提示符變成了 mongo_dal:PRIMARY> 。此時我們可以用 rs.status() 查看復制集狀態。
自此,一個簡單的復制集就搭建完畢了。
wireshark 抓取解析 mongo 協議包
代理程序按理來說只要將客戶端發送的數據以及服務端響應的數據透傳給對方,不管數據是否加密,通過代理連接的客戶端和服務端還是能相互通信的。那為什么還需要費力的去解析 mongo 的協議包呢?
文章開頭我們說了,我需要實現一個具備讀寫分離,自動主備切換的 mongo 代理程序,簡版的透傳方式實現的 mongo 代理程序僅僅是連接了主節點 (Primary),讀寫操作全部依賴主節點。那么當主節點掛了,我們的代理程序沒法知道誰是新的主節點,后續依然把客戶端操作發給已經掛了的主節點,最終導致客戶端操作超時斷連,因此解析 mongo 協議包是必要的。
好在高版本 wireshark 支持解析 mongo 協議,接下來我們來抓個包試試:
以上截圖是我通過 mongo 客戶端直連復制集節點得到的抓包結果,我們發現只有 query ( Opcode 2004) 和 reply ( Opcode 1) 兩條數據被完整的解析了,后續我無論進行查詢,插入或刪除操作,結果都是 Unknown ( Opcode 為 2010 或 2011 的操作) 無法被解析。
這和官網介紹的那么多種 Opcode 完全不一樣啊,此時我相信你有一句 mmp 不知當講不當講。不過仔細想想,我們是通過 mongo client 命令行操作的,它把我們的操作及返回結果封進 Opcode 2010 (command) 和 Opcode 2011 (command reply) 數據包里也屬正常,證據如下圖所示:
不過即使知道了這個,不能直觀的解析也是一件很蛋疼的事。那我們難道就沒辦法直接解析出官網協議指出的那些數據了嗎?不,我們還沒試過 mongo 驅動發上來的包是否能解析。
以 mongo node 驅動為例,快速編寫一個測試用例:
'use strict'
const MongoClient = require('mongodb').MongoClient;
(async () => {
let db = await MongoClient.connect('mongodb://127.0.0.1:21000/test');
let coll = db.collection('cats');
await coll.insert({name: 'yuyuan'});
let res = await coll.find({}).toArray()
console.log("res: ", res);
await coll.remove({})
})();
執行后的結果如下:
通過以上抓包截圖我們發現,無論是 mongo client 直連,還是 mongo node driver 方式連接,握手成功后發的第一個包都是 isMaster 數據包。這個請求數據包是用來首次連接獲取復制集狀態信息的。之所以上面的截圖發現 isMaster 包并不是第一個抓取的包,第一個抓取的包是 Unknown,是因為那些 Unknown 包有很大一部分是復制集成員內部通信的 心跳包 。
其次,通過 node driver 方式連接操作后,我們不斷開連接,driver 會每隔一段時間發一個 ismaster 心跳包用來保活連接。注意,這個 ismaster 心跳包不同于首次發送的 isMaster ,并不會攜帶客戶端meta 信息。
自此,復制集搭建及抓包教程已完成,下一篇我會介紹如何用 golang 實現 mongo 代理,敬請期待!
來自:https://zhuanlan.zhihu.com/p/28736944