Mongo 代理程序實現-複製集搭建及抓包篇
如標題所述,本系列教程是教你如何手擼一個 mongo 代理程序。教程分為兩篇,複製集搭建及抓包篇和代碼實戰篇。
Q: 這程序有什麼卵用?
A: mongo 代理程序主要是為了給後端 mongo 資料庫加一層保護層,防止資料庫因請求過於頻繁而被打爆。
為了更加貼近實際生產環境,我會從資料庫複製集搭建,wireshark 抓取解析 mongo 包以及實際 go 邏輯編寫,三個部分進行詳細的講解,最終目標是為了實現一個具備讀寫分離,自動主備切換的稍微健壯一點的 mongo 代理程序。
文章可能有點長,請耐心閱讀。
複製集搭建
複製集架構我選擇了一主 (Primary),一備 (Secondary),一仲裁 (Arbiter):
- 主節點 (Primary): 只能有一個,用於接收所有的寫操作,並將寫入的數據同步到其它備份節點上
- 備份節點 (Secondary): 可以有多個,用於備份主節點的數據,可以參與複製集選舉
- 仲裁節點 (Arbiter): 不參與選舉,不同步主節點數據,當主節點掛了後,自動從備份節點中選舉一個作為主節點
閑話不多說,直接上配置文件吧:
## mgo_conf1.yamlprocessManagement: fork: true #mongod以守護進程的方式在後台執行net: bindIp: 127.0.0.1 port: 21000storage: 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 strictconst 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 代理,敬請期待!
推薦閱讀: