mongodb: 協議分析
測試環境:php-mongodb-driver_V1.2.5,mongodb_V3.2,thinkphp_V5.0.8,think-mongo_V1.7,PHP_V7.1,wireshark_V2.2.6 。怎麼搭環境就不贅述了,以前也寫了相關的文章,需要的可以翻看我以前的文章,或網上搜索一下。話不多說直接開始,首先我們先大概了解一下mongodb協議的消息頭結構:
struct MsgHeader { int32 messageLength; // 消息的總長度(byte),包括該消息頭的長度 //當客戶端向mongodb伺服器發送請求時會生成一個requestID, //作為此次請求的唯一標識。當mongodb伺服器向客戶端返回信息, //回設置responseTo,responseTo的值就是之前客戶端發過來的requestID int32 requestID; int32 responseTo; int32 opCode; //表示請求的類型,有 QUERY,INSERT,UPDATE ...等等}opCode 的詳細信息可以參考下面的表
這一次我們主要關心OP_REPLY,OP_QUERY 和 OP_GET_MORE。下面我們分別查看一下這幾個opCode的結構
struct OP_QUERY { MsgHeader header; // 這個我們上面已經介紹過 int32 flags; // 位向量,後面會詳細介紹 cstring fullCollectionName ; //集合的全名 dbname.collectionname int32 numberToSkip; //從第幾個開始跳過,即忽略的文檔數 // 這個可以用來限定mongodb服務端返迴文檔的數量, //但現在使用的驅動版本是將這個值寫死為 -1 , 可以通過batchSize和limit // 實現相同的功能,更詳細的信息可以查看文檔 int32 numberToReturn; //包含一個elements數組,其中包括操作名,過濾的條件filter, //查詢的選項如 limit, skip 等 document query; [ document returnFieldsSelector; ] // 可以限制返回的欄位}
flags 位向量:
TailableCursor : 表示在返回最後一條數據後,是否關閉當前 cursor。只能應用於固定集合SlaveOk: 這個和複製集有關,表示是否允許讀取slave結點。OplogReplay: 這個也和複製集有關,內部使用。客戶端驅動不需要關心NoCursorTimeout: mongodb為了節約內存會將在10分鐘(默認)內無操作的cursor關閉,可以通過該變數來關閉這個機制
AwaitData: 設置該值表示在數據尾部時不會直接告訴客戶端沒有數據,而是阻塞一會直到超時還沒有數據再返回。需要配合TailableCursor來使用。可以參考這篇文章 MongoDB Tailable Cursors深入剖析 Exhaust: 這個我也沒搞懂幹啥使的......Partial: 這個和mongodb的分片有關,如果有一些shards掛掉了,則從 mongos 那裡返回部分結果,而不是直接拋出異常OP_GET_MORE 和 OP_QUERY類似,當客戶端發起QUERY操作後,如果符合條件的數據沒能一次性返回回來(默認第一次QUERY返回101條數據),則客戶端會發起GET_MORE的請求,
struct { MsgHeader header; int32 ZERO; // 0 - 保留欄位 cstring fullCollectionName; int32 numberToReturn; int64 cursorID; // 返回的游標的ID}
OP_REPLY用於伺服器向客戶端返回信息,其結構如下:
struct { MsgHeader header; int32 responseFlags; // 位向量,後面會詳細介紹 int64 cursorID; //返回的游標的ID int32 startingFrom; //游標中的起始位置 // 返回的文檔數,這個版本的驅動返回都是1。包含一個Elements數組 int32 numberReturned; document* documents; // 返回的文檔存儲在Elements數組中}responseFlags 位向量:CursorNotFound: 當處理getMore請求時,如果伺服器上當前游標失效,則會設置該值。QueryFailure: 查詢失敗時,會設置該值,並在返回的document中包含一個 "$err" 欄位給出錯誤信息。ShardConfigStale: 和分片相關,客戶端驅動不用關心這個值AwaitCapable: 如果伺服器支持AwaitData查詢選項時,AwaitCapable可以設置為true。
了解了消息頭的基本結構後我們就可以開始抓包分析了。我們寫了一個簡單的查詢:
$cursor = Db::name(Disaster100w)->fetchCursor()->limit(250)->select();$disasterList = $cursor->toArray();
在獲取真正的數據之前,客戶端需要先發送一個請求獲取伺服器的基本信息:
我們可以通過limit限制服務端返回的數據量,防止數據過大把內存爆掉。可以適當調高batchSize的值,來減少客戶端和伺服器的交互次數。例如我們將代碼改為:
$cursor = Db::name(Disaster100w)->fetchCursor()->batchSize(250)->limit(250)->select(); $disasterList = $cursor->toArray();
推薦閱讀:
※wireshark怎麼捕捉lacp報文?
※《Wireshark的簡單使用》
※簡明 Wireshark 和 TCP 入門指南
※wireshark找不到介面?
※chrome timing中的queueing代表什麼?