DataLeap數據資產實戰:如何實現存儲優化?
背景
- DataLeap 作為一站式數據中臺套件,匯集了字節內部多年積累的數據集成、開發、運維、治理、資產、安全等全套數據中臺建設的經驗,助力企業客戶提升數據研發治理效率、降低管理成本。
- Data Catalog 是一種元數據管理的服務,會收集技術元數據,并在其基礎上提供更豐富的業務上下文與語義,通常支持元數據編目、查找、詳情瀏覽等功能。目前 Data Catalog 作為火山引擎大數據研發治理套件 DataLeap 產品的核心功能之一,經過多年打磨,服務于字節跳動內部幾乎所有核心業務線,解決了數據生產者和消費者對于元數據和資產管理的各項核心需求。
- Data Catalog 系統的存儲層,依賴 Apache Atlas,傳遞依賴 JanusGraph。JanusGraph 的存儲后端,通常是一個 Key-Column-Value 模型的系統,本文主要講述了使用 MySQL 作為 JanusGraph 存儲后端時,在設計上面的思考,以及在實際過程中遇到的一些問題。
起因
實際生產環境,我們使用的存儲系統維護成本較高,有一定的運維壓力,于是想要尋求替代方案。在這個過程中,我們試驗了很多存儲系統,其中 MySQL 是重點投入調研和開發的備選之一。
另一方面,除了字節內部外,在 ToB 場景,MySQL 的運維成本也會明顯小于其他大數據組件,如果 MySQL 的方案跑通,我們可以在 ToB 場景多一種選擇。
基于以上兩點,我們投入了一定的人力調研和實現基于 MySQL 的存儲后端。
方案評估
在設計上,JanusGraph 的存儲后端是可插拔的,只要做對應的適配即可,并且官方已經支持了一批存儲系統。結合字節的技術棧以及我們的訴求,做了以下的評估。
各類存儲系統比較
因投入成本過高,我們不接受自己運維有狀態集群,排除了 HBase 和 Cassandra;
- 從當前數據量與將來的可擴展性考慮,單機方案不可選,排除了 BerkeleyDB;
- 同樣因為人力成本,需要做極大量開發改造的方案暫時不考慮,排除了 Redis。
最終我們挑選了 MySQL 來推進到下一步。
MySQL 的理論可行性
- 可以支持 Key-Value(后續簡稱 KV 模型)或者 Key-Column-Value(后續簡稱 KCV 模型)的存儲模型,聚集索引 B+ 樹排序訪問,支持基于 Key 或者 Key-Column 的 Range Query,所有查詢都走索引,且避免內存中重排序,效率初步判斷可接受。
- 中臺內的其他系統,最大的 MySQL 單表已經到達億級別,且 MySQL 有成熟的分庫分表解決方案,判斷數據量可以支持。
- 在具體使用場景中,對于寫入的效率要求不高,因為大量的數據都是離線任務完成,判斷 MySQL 在寫入上的效率不會成為瓶頸。
總體設計
- 維護一張 Meta 表做 lookup 用,Meta 表中存儲租戶與 DataSource(庫)之間的映射關系,以及 Shards 等租戶級別的配置信息。
- StoreManager 作為入口,在 openTransaction 的時候將租戶信息注入到 StoreTransaction 中,并返回租戶級別的 DataSource。
- StoreManager 中以 name 為 Key,維護一組 Store,Store 與存儲的數據類型有關,具有跨租戶能力
- 常見的 Store 有?
?system_properies?
??,??tx_log?
??,??graphindex?
??,??edgestore?
?等
- 對于 MySQL 最終的讀寫,都收斂在 Store,方法簽名中傳入 StoreTransaction,Store 從中取出租戶信息和數據庫連接,進行數據讀寫。
- 對于單租戶來說,數據可以分表(shards),對于某個特定的 key 來說,存儲和讀取某個 shard,是根據 ShardManager 來決定
- 典型的 ShardManager 邏輯,是根據總 shard 數對 key 做 hash 決定,默認單分片。
- 對于每個 Store,表結構是 4 列(id, g_key, g_column, g_value),除自增 ID 外,對應 key-column-value model 的數據模型,key+column 是一個聚集索引。
- Context 中的租戶信息,需要在操作某個租戶數據之前設置,并在操作之后清除掉。
細節設計與疑難問題
細節設計
存儲模型
JanusGraph 要求 column-family 類型存儲(如 Cassandra, HBase),也就是說,數據存儲由一系列行組成,每行都由一個鍵(key)唯一標識,每行由多個列值(column-value)對組成,也會對列進行排序和過濾,如果是非 column-family 的類型存儲,則需要另行適配,適配時數據模型有兩種方式:Key-Column-Value 和 Key-Value。
KCV 模型:
- 會將key\column\value在存儲中區分開來。
- 對應的接口為:?
?KeyColumnValueStoreManager?
?。
KV 模型:
- 在存儲中僅有 key 和 value 兩部分,此處的 key 相當于 KVC 模型中的 key+column;
- 如果要根據 column 進行過濾,需要額外的適配工作;
- 對應的接口為:?
?KeyValueStoreManager?
??,該接口有子類??OrderedKeyValueStoreManager?
?,提供了保證查詢結果有序性的接口; - 同時提供了?
?OrderedKeyValueStoreManagerAdapter?
?接口,用于對 Key-Column-Value 模型進行適配,將其轉化為 Key-Value 模型。
MySQL 的存儲實現采用了 KCV 模型,每個表會有 4 列,一個自增的 ID 列,作為主鍵,同時還有 3 列分別對應模型中的 key\column\value,數據庫中的一條記錄相當于一個獨立的 KCV 結構,多行數據庫記錄代表一個點或者邊。
表中 key 和 column 這兩列會組成聯合索引,既保證了根據 key 進行查詢時的效率,也支持了對 column 的排序以及條件過濾。
多租戶
存儲層面:默認情況下,JanusGraph 會需要存儲??edgestore?
??, ??graphindex?
??, ??system_properties?
??, ??txlog?
??等多種數據類型,每個類型在 MySQL 中都有各自對的表,且表名使用租戶名作為前綴,如??tenantA_edgestore?
?,這樣即使不同租戶的數據在同一個數據庫,在存儲層面租戶之間的數據也進行了隔離,減少了相互影響,方便日常運維。(理論上每個租戶可以單獨分配一個數據庫)
具體實現:每個租戶都會有各自的 MySQL 連接配置,啟動之后會為各個租戶分別初始化數據庫連接,所有和 JanusGraph 的請求都會通過 Context 傳遞租戶信息,以便在操作數據庫時選擇該租戶對應的連接。
具體代碼:
- Mysql KcvTx:實現了?
?AbstractStoreTransaction?
??,對具體的 MySQL 連接進行了封裝,負責和數據庫的交互,它的??commit?
??和??rollback?
?方法由封裝的 MySQL 連接真正完成。 - MysqlKcvStore:實現了?
?KeyColumnValueStore?
??,是具體執行讀寫操作的入口,每一個類型的 Store 對應一個??MysqlKcvStore?
??實例,??MysqlKcvStore?
??處理讀寫邏輯時,根據租戶信息完全自主組裝 SQL 語句,SQL 語句會由??MysqlKcvTx?
?真正執行。 - MysqlKcvStoreManager:實現了?
?KeyColumnValueStoreManager?
??,作為管理所有 MySQL 連接和租戶的入口,也維護了所有 Store 和??MysqlKcvStore?
??對象的映射關系。在處理不同租戶對不同 Store 的讀寫請求時,根據租戶信息,創建??MysqlKcvTx?
??對象,并將其分配給對應的??MysqlKcvStore?
?去執行。
事務
幾乎所有與 JanusGraph 的交互都會開啟事務,而且事務對于多個線程并發使用是安全的,但是 JanusGraph 的事務并不都支持 ACID,是否支持會取決于底層存儲組件,對于某些存儲組件來說,提供可序列化隔離機制或者多行原子寫入代價會比較大。
JanusGraph 中的每個圖形操作都發生在事務的上下文中,根據 TinkerPop 的事務規范,每個線程執行圖形上的第一個操作時便會打開針對圖形數據庫的事務,所有圖形元素都與檢索或者創建它們的事務范圍相關聯,在使用??commit?
??或者??rollback?
?方法顯式的關閉事務之后,與該事務關聯的圖形元素都將過時且不可用。
JanusGraph 提供了??AbstractStoreTransaction?
??接口,該接口包含??commit?
??和??rollback?
??的操作入口,在 MySQL 存儲的實現中,??MysqlKcvTx?
??實現了??AbstractStoreTransaction?
??,對具體的 MySQL 連接進行了封裝,在其??commit?
??和??rollback?
??方法中調用 SQL 連接的??commit?
??和??rollback?
?方法,以此實現對于 JanusGraph 事務的支持。
數據庫連接池
Hikari 是 SpringBoot 內置的數據庫連接池,快速、簡單,做了很多優化,如使用 FastList 替換 ArrayList,自行研發無所集合類 ConcurrentBag,字節碼精簡等,在性能測試中表現的也比其他競品要好。
Druid 是另一個也非常優秀的數據庫連接池,為監控而生,內置強大的監控功能,監控特性不影響性能。功能強大,能防 SQL 注入,內置 Loging 能診斷 Hack 應用行為。
關于兩者的對比很多,此處不再贅述,雖然 Hikari 的性能號稱要優于 Druid,但是考慮到 Hikari 監控功能比較弱,最終在實現的時候還是選擇了 Druid。
疑難問題
連接超時
現象:在進行數據導入測試時,服務報錯" The last packet successfully received from the server was X milliseconds ago",導致數據寫入失敗。
原因:存在超大 table(有 8000 甚至 10000 列),這些 table 的元數據處理非常耗時(10000 列的可能需要 30 分鐘),而且在處理過程中有很長一段時間和數據庫并沒有交互,數據庫連接一直空閑。
解決辦法:
- 調整 mysql server 端的 wait_timeout 參數,已調整到 3600s。
- 調整 client 端數據庫配置中連接的最小空閑時間,已調整到 2400s。
分析過程:
- 懷疑是 mysql client 端沒有增加空閑清理或者保活機制,conneciton 在線程池中長時間沒有使用,mysql 服務端已經關閉該鏈接導致。嘗試修改客戶端 connection 空閑時間,增加 validationQuery 等常見措施,無果;
- 根據打點發現單條消息處理耗時過高,疑似線程卡死;
- 新增打點發現線程沒卡死,只是在執行一些非常耗時的邏輯,這時候已經獲取到了數據庫連接,但是在執行那些耗時邏輯的過程中和數據庫沒有任何交互,長時間沒有使用數據庫連接,最終導致連接被回收;
- 調高了 MySQL server 端的 wait_timeout,以及 client 端的最小空閑時間,問題解決。
并行寫入死鎖
現象:線程 thread-p-3-a-0 和線程 thread-p-7-a-0 在執行過程中都出現 Deadlock。
具體日志如下:
原因:
- 結合日志分析,兩個線程并發執行,需要對同樣的多個記錄加鎖,但是順序不一致,進而導致了死鎖。
- ?
?55A0?
?這個 column 對應的 property 是"__modificationTimestamp",該屬性是atlas的系統屬性,當對圖庫中的點或者邊有更新時,對應點或者邊的"__modificationTimestamp"屬性會被更新。在并發導入數據的時候,加劇了資源競爭,所以會偶發死鎖問題。
解決辦法:
業務中并沒有用到"__modificationTimestamp"這個屬性,通過修改 Atlas 代碼,僅在創建點和邊的時候為該屬性賦值,后續更新時不再更新該屬性,問題得到解決。
性能測試
環境搭建
在字節內部 JanusGraph 主要用作 Data Catalog 服務的存儲層,關于 MySQL 作為存儲的性能測試并沒有在 JanusGraph 層面進行,而是模擬 Data Catalog 服務的業務使用場景和數據,使用業務接口進行測試,主要會關注接口的響應時間。
接口邏輯有所裁剪,在不影響核心讀寫流程的情況下,屏蔽掉對其他服務的依賴。
模擬單租戶表單 分片情況下,庫表元數據創建、更新、查詢,表之間血緣關系的創建、查詢,以此反映在圖庫單次讀寫和多次讀寫情況下 MySQL 的表現。
整個測試環境搭建在火山引擎上,總共使用 6 臺 8C32G 的機器,硬件條件如下:
測試場景如下:
測試結論
總計 10 萬個表(庫數量為個位數,可忽略)
在 10 萬個表且模擬了表之間血緣關系的情況下,??graphindex?
??表的數據量已有 7000 萬,??edgestore?
?表的數據量已有 1 億 3000 萬,業務接口的響應時間基本在預期范圍內,可滿足中小規模 Data Catalog 服務的存儲要求。
總結
MySQL 作為 JanusGraph 的存儲,有部署簡單,方便運維等優勢,也能保持良好的擴展性,在中小規模的 Data Catalog 存儲服務中也能保持較好的性能水準,可以作為一個存儲選擇。
市面上也有比較成熟的 MySQL 分庫分表方案,未來可以考慮將其引入,以滿足更大規模的存儲需求。
火山引擎 Data Catalog 產品是基于字節跳動內部平臺,經過多年業務場景和產品能力打磨,在公有云進行部署和發布,期望幫助更多外部客戶創造數據價值。目前公有云產品已包含內部成熟的產品功能同時擴展若干 ToB 核心功能,正在逐步對齊業界領先 Data Catalog 云產品各項能力。