問題:對于超大的 string V8不能支持
問題背景
在 Nodejs 計算服務中,對端上上報的記憶體資訊二進制資料進行預處理+快取時,遇到了一個奇怪的報錯:RangeError: Invalid string length ,根據該報錯資訊,查找得知是字串長度超過了 node.js 的限制,即 2^29-1 (約 5 億+)個字符,整體流程如圖所示,
關于 node.js string 的長度上限,主要和 V8 引擎「壓縮指標」技術有關,按個人理解,其通過壓縮指向變數的地址(64 位)中固定的 32 位的方式,從而減少引擎的記憶體占用,
代碼細節
由于需要快速訪問某地址,因此快取的資料結構必須是個物件,即 INodeGraph,具體結構如下:
type IAddr = string;
// 記憶體圖譜
declare interface INodeGraph {
[addr: IAddr]: IParsedNode;
}
// 記憶體節點資訊
declare interface IParsedNode {
addr: IAddr;
// size, nodeType 等輔助資訊
parentNodeAddr: IAddr[]; // addr
childNodeAddr: string[]; // addr
edgeMap: {
[addr: IAddr]: {
// 當前節點與父子節點之間的邊(關系)的資訊
};
};
}
|
我們目的很明確,就是實作這樣一個 js 大物件的持久化存盤,并且能夠方便快速的轉回 js object,為解決此問題,首先想到的能否利用 protobuf 替代 JSON 實作持久化,可惜的是 protobuf 并不適用于動態 key 的場景,它適用于處理陣列中存盤多個相似結構物件的資料結構,
隨后嘗試了減少物件中不必要的資訊,即縮短物件的固定 key,例如用「pNode」取代冗長的「parentNodeAddr」,對于一個百萬個鍵值對的 object 而言,雖然犧牲了代碼的可讀性,但在實際的 case 中,能承載的鍵值對數量大約多了 20%,
事實上回過頭來看,更好的處理方式或許是用另外的 Map 存盤物件的 key,例如 : 將 nodeGraph.parentNodeAddr 這個 key 最大程度縮短為 nodeGraph.p
宣告 const GraphKey = { parentNodeAddr: 'p' } 保存一個 key 的映射,需要訪問某屬性時,使用nodeGraph[GraphKey.parentNodeAddr]
更進一步
上述手段只是治標不治本,對于 key 更多的大物件并不能徹底解決問題,因此在不改變專案整體架構的前提下(如使用圖資料庫/改用 go 開發等),提出以下兩個最終方案:
方案 1:借助 Node.js C++ Addons 的能力,繞開 js string 的限制,將相關序列化邏輯交給 C++ 處理,并直接將處理好的參考樹 js object 進行后續處理,
- 優勢:如果能實作,性能會獲得優先提升;擴展了 Node.js 的能力
- 劣勢:實作難度大;維護可能是個問題
方案 2:生成參考樹快取時,拆分為多個較小的物件,分別進行序列化和存盤,使用時再合并為一個大物件,
- 優勢:無需 C++ 側開發,難度更小;維護方便
- 劣勢:合并物件需要額外的時間,這一步驟可能會讓未命中快取時的首次請求更慢

你要覺得這篇文章比較好,記得點推薦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/543630.html
標籤:其他

