背景來源:FunData作為電競資料平臺,v1.0 beta版本主要提供由Valve公司出品的頂級MOBA類遊戲DOTA2相關資料介面(詳情:open.varena.com)。資料對比賽的觀賞性和專業性的提高起到至關重要的作用。本文由IT大咖說(微信id:itdakashuo)整理,經投稿者與嘉賓審閱授權釋出。
閱讀字數:4822 | 13分鐘閱讀
摘要
本文將介紹FunData的架構演進中的設計思路及其涉及的相關技術,包括大資料流處理方案、結構化儲存轉非結構化儲存方案和資料API服務設計等。
無論是對於觀賽使用者,還是比賽使用者,電競資料的豐富程度與實時要求得到越來越多的關注。
電競資料的豐富性從受眾角度來看,可分為賽事、戰隊和玩家資料;從遊戲角度來看,維度可由英雄、戰鬥、道具以及技能等組成;電競資料的實時性包括賽前兩支戰隊的歷史交戰記錄、賽中的實時比分、勝率預測、賽後比賽分析和英雄對比等。
因此多維度的資料支援,TB到PB級別的海量資料儲存與實時分析都對底層系統的架構設計有著更高的要求,亦帶來了更嚴峻的挑戰。
1.0架構
專案發展初期,依照MVP理論(最小化可行產品),我們迅速推出FunData的第一版系統(架構圖如圖1)。系統主要有兩個模組:Master與Slave。
Master模組功能如下:
-
定時呼叫Steam介面獲取比賽ID與基礎資訊
-
通過In-Memory的訊息佇列分發比賽分析任務到Slave節點
-
記錄比賽分析進度,並探測各Slave節點狀態
Slave模組功能如下:
-
監聽佇列訊息並獲取任務(任務主要為錄影分析,錄影分析參考github專案clarity(https://github.com/skadistats/clarity)與manta(https://github.com/dotabuff/manta))
-
分析資料入庫
系統上線初期執行相對穩定,各維度的資料都可快速拉取。然而隨著資料量的增多,資料與系統的可維護性問題卻日益突出。
-
新增資料欄位需要重新構建DB索引,資料錶行數過億構建時間太長且造成長時間鎖表。
-
系統耦合度高,不易於維護,Master節點的更新重啟後,Slave無重連機制需要全部重啟;同時In-Memory訊息佇列有丟訊息的風險。
-
系統可擴充套件性低,Slave節點擴容時需要頻繁的製作虛擬機器映象,配置無統一管理,維護成本高。
-
DB為主從模式且儲存空間有限,導致資料API層需要定製邏輯來分庫讀取資料做聚合分析。
-
節點粒度大,Slave可能承載的多個分析任務,故障時影響面大。
圖1 1.0 ETL 架構圖
在開始2.0架構設計與改造前,我們嘗試使用冷儲存方法,通過遷移資料的方式來減輕系統壓力(架構設計如圖2)。由於資料表資料量太大,併發多個資料遷移任務需要大量時間,清理資料的過程同樣會觸發重新構建索引,方案的上線並沒有根本性地解決問題。
圖2 冷儲存方案
2.0架構
吸取1.0系統的經驗,在2.0架構設計中,我們從維護性、擴充套件性和穩定性三個方面來考慮新資料系統架構應該具備的基本特性:
-
資料處理任務粒度細化,且支援高併發處理(全球一天DOTA2比賽的場次在120萬場,錄影分析相對耗時,序列處理會導致任務堆積嚴重)
-
資料分散式儲存
-
系統解耦,各節點可優雅重啟與更新
圖3 2.0ETL總架構圖
2.0系統選擇Google Cloud Platform來構建整個資料ETL系統,利用PubSub(類似Kafka)作為訊息匯流排,任務被細化成多個Topic進行監聽,由不同的Worker進行處理。這樣一方面減少了不同任務的耦合度,防止一個任務處理異常導致其他任務中斷;另一方面,任務基於訊息匯流排傳遞,不同的資料任務擴充套件性變得更好,效能不足時可快速橫向擴充套件。
任務粒度細化
從任務粒度上看(如圖3),資料處理分為基礎資料處理與高階資料處理兩部分。基礎資料,即比賽的詳情資訊(KDA、傷害與補刀等資料)和錄影分析資料(Roshan擊殺資料、傷害型別與英雄分路熱力圖等資料)由Supervisor獲取Steam資料觸發,經過worker的清理後存入Google Bigtable;高階資料,即多維度的統計資料(如英雄、道具和團戰等資料),在錄影分析後觸發,並通過GCP的Dataflow和自建的分析節點(worker)聚合,最終存入MongoDB與Google Bigtable。
從Leauge-ETL的細化架構看(如圖4),原有的單個Slave節點被拆分成4個子模組,分別是聯賽資料分析模組、聯賽錄影分析模組、分析/挖掘資料DB代理模組和聯賽分析監控模組。
-
聯賽資料分析模組負責錄影檔案的拉取(salt、meta檔案與replay檔案的獲取)與比賽基本資料分析
-
聯賽錄影分析模組負責比賽錄影解析並將分析後資料推送至PubSub
-
分析/挖掘資料DB代理負責接收錄影分析資料並批量寫入Bigtable
-
聯賽分析監控模組負責監控各任務進度情況並實時告警
同時所有的模組選擇Golang重構,利用其“天生”的併發能力,提高整個系統資料探勘和資料處理的效能。
圖4 League-ETL架構
分散式儲存
如上文提到,1.0架構中我們使用MySQL儲存大量比賽資料及錄影分析資料。MySQL在大資料高併發的場景下,整體應用的開發變得越來越複雜,如無法支援schema經常變化,架構設計上需要合理地考慮分庫分表的時機,子庫的資料到一定量級時面臨的擴充套件性問題。
參考Google的Bigtable(詳情見Big table: A Distributed Storage System for Structured Data)及Hadoop生態的HBase(圖5),作為一種分散式的、可伸縮的大資料儲存系統,Bigtable與HBase能很好的支援資料隨機與實時讀寫訪問,更適合FunData資料系統的資料量級和複雜度。
圖5 Hadoop生態
在資料模型上,Bigtable與HBase通過RowKey、列簇列名及時間戳來定位一塊資料(Cell)。(如圖6)
(rowkey:string,columnfamily:columnstring,timestamp:int64)→value:string複製程式碼
圖6 資料索引
例如,在FunData資料系統中,比賽資料的RowKey以hash_key+match_id的方式構建,因為DOTA2的match_id是順序增大的(數值自增量不唯一),每個match_id前加入一致性雜湊演算法算出的hash_key,可以防止在分散式儲存中出現單機熱點的問題,提升整個儲存系統的資料負載均衡能力,做到更好的分片(Sharding),保障後續DataNode的擴充套件性。
(如圖7) 我們在hash環上先預設多個key值作為RowKey的字首,當獲取到match_id時,通過一致性雜湊演算法得到match_id對應在hash環節點的key值,最後通過key值與match_id拼接構建RowKey。
RowKey=Hash(MatchID)+MatchID=Key_n+MatchID複製程式碼
圖7 一致性hash構建RowKey
時間戳的使用方便我們在聚合資料時對同一個RowKey和Column的資料重複寫入,HBase/Bigtable內部有自定的GC策略,對於過期的時間戳資料會做清理,讀取時取最新時間節點的資料即可。
這裡大家可能會有個疑問,Bigtable與HBase只能做一級索引,RowKey加上hash_key之後,是無法使用row_range的方式批量讀或者根據時間為維度進行批量查詢的。在使用Bigtable與HBase的過程中,二級索引需要業務上自定義。在實際場景裡,我們的worker在處理每個比賽資料時,同時會對時間戳-RowKey構建一次索引並存入MySQL,當需要基於時間批量查詢時,先查詢索引表拉取RowKey的列表,再獲取對應的資料列表。
在資料讀寫上,Bigtable/HBase與MySQL也有很大的不同。一般MySQL使用查詢快取,schema更新時快取會失效,另外查詢快取是依賴全域性鎖保護,快取大量資料時,如果查詢快取失效,會導致表鎖死。
如圖8,以HBase為例,讀取資料時,client先通過zookeeper定位到RowKey所在的RegionServer,讀取請求達到RegionServer後,由RegionServer來組織Scan的操作並最終歸併查詢結果返回資料。因為一次查詢操作可能包括多個RegionServer和多個Region,資料的查詢是併發執行的且HBase的LRUBlockCache,資料的查詢不會出現全部鎖死的情況。
圖8 HBase架構
基於新的儲存架構,我們的資料維度從單局比賽擴充套件到了玩家、英雄、聯賽等。(如圖9)
圖9 資料維度
系統解耦
上文我們提到1.0架構中使用In-Memory的訊息佇列做資料傳遞,由於記憶體中佇列資料沒有持久化儲存並與Master模組強耦合,Master節點更新或者異常Panic後會導致資料丟失,且恢復時間冗長。因此,在2.0架構中採用了第三方的訊息佇列作為訊息匯流排,保障系統“上下游”節點解耦,可多次迭代更新,歷史訊息變得可追蹤,基於雲平臺訊息堆積也變得視覺化(如圖10)。
圖10 資料監控
資料API層
1.0系統的資料API層為實現快速上線,在架構上未做太多的設計與優化,採用域名的方式實現負載均衡,並使用開源的DreamFactory搭建的ORM層,利用其RESTful的介面做資料訪問。該架構在開發和使用過程中遇到許多問題:
-
API層部署在國內阿里雲上,資料訪問需要跨洋
-
ORM層提供的API獲取表的全欄位資料,資料粒度大
-
無快取,應對大流量場景(如17年震中杯與ESL)經常出現服務不可用
-
多DB的資料聚合放在了API層,效能不足
-
服務更新維護成本高,每次更新需要從域名中先剔除機器
針對上述問題,我們從兩個方面重構了1.0資料API層。(如圖11)
圖11 資料API新架構
鏈路的穩定性
全球鏈路上,我們使用CDN動態加速保證訪問的穩定性。同時利用多雲廠商CDN做備份容災,做到秒級切換。
在排程能力和恢復能力上,我們搭建了自己的灰度系統,將不同維度的資料請求排程到不同的資料API,減少不同維度資料請求量對系統的影響;藉助灰度系統,API服務更新的風險和異常時的影響面也被有效控制。依賴雲平臺可用區的特性,灰度系統也能方便地實現後端API服務跨可用區,做到物理架構上的容災。
另外,為保證內部跨洋訪問鏈路的穩定性,我們在阿里雲的北美機房搭建資料代理層,利用海外專線來提升訪問速度。
資料高可用性
接入分散式儲存系統後,對外資料API層也根據擴充套件的資料維度進行拆分,由多個資料API對外提供服務,例如比賽資料和聯賽賽程等資料訪問量大,應該與英雄、個人及道具資料分開,防止比賽/賽事介面異常影響所有資料不可訪問。
針對熱點資料,內部Cache層會做定時寫入快取的操作,資料的更新也會觸發Cache的重寫,保障賽事期間的資料可用。
結語
在本篇的技術分享中,我們介紹了FunData資料平臺架構演進的過程,分析了舊系統在架構上的缺點,以及在新系統中通過什麼技術和架構手段來解決。FunData自4月10日上線以來,已有300多位技術開發者申請了API-KEY。我們也在著力於新資料點的快速迭代開發,如聯賽統計資料,比賽實時資料等。