1、圖狀結構數據普遍存在算法
字節跳動的全部產品的大部分業務數據,幾乎均可以納入到如下三種:數據庫
-
用戶信息、用戶和用戶的關係(關注、好友等);編程
-
內容(視頻、文章、廣告等);緩存
-
用戶和內容的聯繫(點贊、評論、轉發、點擊廣告等)。安全
這三種數據關聯在一塊兒,造成圖狀(Graph)結構數據。性能優化
爲了知足 social graph 的在線增刪改查場景,字節跳動自研了分佈式圖存儲系統——ByteGraph。針對上述圖狀結構數據,ByteGraph 支持有向屬性圖數據模型,支持 Gremlin 查詢語言,支持靈活豐富的寫入和查詢接口,讀寫吞吐可擴展到千萬 QPS,延遲毫秒級。目前,ByteGraph 支持了頭條、抖音、 TikTok、西瓜、火山等幾乎字節跳動所有產品線,遍及全球機房。在這篇文章中,將從適用場景、內部架構、關鍵問題分析幾個方面做深刻介紹。網絡
ByteGraph 主要用於在線 OLTP 場景,而在離線場景下,圖數據的分析和計算需求也逐漸顯現。2019 年年初,Gartner 數據與分析峯會上將圖列爲 2019 年十大數據和分析趨勢之一,預計全球圖分析應用將以每一年 100% 的速度迅猛增加,2020 年將達到 80 億美圓。所以,咱們團隊同時也開啓了在離線圖計算場景的支持和實踐。架構
下面會從圖數據庫和圖計算兩個部分,分別來介紹字節跳動在這方面的一些工做。併發
2、自研圖數據庫(ByteGraph)介紹app
從數據模型角度看,圖數據庫內部數據是有向屬性圖,其基本元素是 Graph 中的點(Vertex)、邊(Edge)以及其上附着的屬性;做爲一個工具,圖數據對外提供的接口都是圍繞這些元素展開。
圖數據庫本質也是一個存儲系統,它和常見的 KV 存儲系統、MySQL 存儲系統的相比主要區別在於目標數據的邏輯關係不一樣和訪問模式不一樣,對於數據內在關係是圖模型以及在圖上游走類和模式匹配類的查詢,好比社交關係查詢,圖數據庫會有更大的性能優點和更加簡潔高效的接口。
一、爲何不選擇開源圖數據庫
圖數據庫在 90 年代出現,直到最近幾年在數據爆炸的大趨勢下快速發展,百花齊放;但目前比較成熟的大部分都是面對傳統行業較小的數據集和較低的訪問吞吐場景,好比開源的 Neo4j 是單機架構;所以,在互聯網場景下,一般都是基於已有的基礎設施定製系統:好比 Facebook 基於 MySQL 系統封裝了 Social Graph 系統 TAO,幾乎承載了 Facebook 全部數據邏輯;Linkedln 在 KV 之上構建了 Social Graph 服務;微博是基於 Redis 構建了粉絲和關注關係。
字節跳動的 Graph 在線存儲場景, 其需求也是有自身特色的,能夠總結爲:
-
海量數據存儲:百億點、萬億邊的數據規模;而且圖符合冪律分佈,好比少許大 V 粉絲達到幾千萬;
-
海量吞吐:最大集羣 QPS 達到數千萬;
-
低延遲:要求訪問延遲 pct99 須要限制在毫秒級;
-
讀多寫少:讀流量是寫流量的接近百倍之多;
-
輕量查詢多,重量查詢少:90%查詢是圖上二度之內查詢;
-
容災架構演進:要能支持字節跳動城域網、廣域網、洲際網絡之間主備容災、異地多活等不一樣容災部署方案。
事實上,咱們調研過了不少業界系統, 這個主題能夠再單獨分享一篇文章。可是,面對字節跳動世界級的海量數據和海量併發請求,用萬億級分佈式存儲、千萬高併發、低延遲、穩定可控這三個條件一塊兒去篩選,業界在線上被驗證穩定可信賴的開源圖存儲系統基本沒有知足的了;另外,對於一個承載公司核心數據的重要的基礎設施,是值得長期投入而且深度掌控的。
所以,咱們在 18 年 8 月份,開始從第一行代碼開始踏上圖數據庫的漫漫征程,從解決一個最核心的抖音社交關係問題入手,逐漸演變爲支持有向屬性圖數據模型、支持寫入原子性、部分 Gremlin 圖查詢語言的通用圖數據庫系統,在公司全部產品體系落地,咱們稱之爲 ByteGraph。下面,會從數據模型、系統架構等幾個部分,由淺入深和你們分享咱們的工做。
二、ByteGraph 的數據模型和 API
1)數據模型
就像咱們在使用 SQL 數據庫時,先要完成數據庫 Schema 以及範式設計同樣,ByteGraph 也須要用戶完成相似的數據模型抽象,但圖的數據抽象更加簡單,基本上是把數據之間的關係「翻譯」成有向屬性圖,咱們稱之爲「構圖」過程。
好比在前面提到的,若是想把用戶關係存入 ByteGraph,第一步就是須要把用戶抽象爲點,第二步把"關注關係」、「好友關係」抽象爲邊就徹底搞定了。下面,咱們就從代碼層面介紹下點邊的數據類型。
① 點(Vertex)
點是圖數據庫的基本元素,一般反映的是靜態信息。在 ByteGraph 中,點包含如下字段:
-
點的id(uint64_t): 好比用戶id做爲一個點
-
點的type(uint32_t): 好比appID做爲點的type
-
點的屬性(KV 對):好比 'name': string,'age': int, 'gender': male,等自定義屬性
-
[id, type]惟必定義一個點
② 邊(Edge)
一條邊由兩個點和點之間的邊的類型組成,邊能夠描述點之間的關係,好比用戶 A 關注了用戶 B ,能夠用如下字段來描述:
-
兩個點(Vertex): 好比用戶A和用戶B
-
邊的類型(string): 好比「關注」
-
邊的時間戳(uint64_t):這個t值是業務自定義含義的,好比能夠用於記錄關注發生的時間戳
-
邊屬性(KV對):好比'ts_us': int64 描述關係建立時間的屬性,以及其餘用戶自定義屬性
③ 邊的方向
在 ByteGraph 的數據模型中,邊是有方向的,目前支持 3 種邊的方向:
-
正向邊:如 A 關注 B(A -> B)
-
反向邊:如 B 被 A 關注(B <- A)
-
雙向邊:如 A 與 B 是好友(A <-> B)
2)場景使用僞碼舉例
構圖完畢後,咱們就能夠把業務邏輯經過 Gremlin 查詢語言來實現了;爲便於你們理解,咱們列舉幾種典型的場景爲例。
場景一:記錄關注關係 A 關注 B
// 建立用戶A和B,可使用 .property('name', 'Alice') 語句添加用戶屬性
g.addV().property("type", A.type).property("id", A.id)
g.addV().property("type", B.type).property("id", B.id)
// 建立關注關係 A -> B,其中addE("關注")中指定了邊的類型信息,from和to分別指定起點和終點,
g.addE("關注").from(A.id, A.type).to(B.id, B.type).property("ts_us", now)
場景二:查詢 A 關注的且關注了 C 的全部用戶
用戶 A 進入用戶 C 的詳情頁面,想看看 A 和 C 之間的二度中間節點有哪些,好比 A->B,B->C,B 則爲中間節點。
// where()表示對於上一個step的每一個執行結果,執行子查詢過濾條件,只保留關注了C的用戶。
g.V().has("type", A.type).has("id", A.id).out("關注").where(out("關注").has("type", C.type).has("id", C.id).count().is(gte(1)))
場景三:查詢 A 的好友的好友(二度關係)
// both("好友")至關於in("好友")和out("好友")的合集
g.V().has("type", A.type).has("id", A.id).both("好友").both("好友").toSet()
三、系統架構
前面幾個章節,從用戶角度介紹了 ByteGraph 的適用場景和對外使用姿式。那 ByteGraph 架構是怎樣的,內部是如何工做的呢,這一節就來從內部實現來做進一步介紹。
下面這張圖展現了 ByteGraph 的內部架構,其中 bg 是 ByteGraph 的縮寫。
就像 MySQL 一般能夠分爲 SQL 層和引擎層兩層同樣,ByteGraph 自上而下分爲查詢層 (bgdb)、存儲/事務引擎層(bgkv)、磁盤存儲層三層,每層都是由多個進程實例組成。其中 bgdb 層與 bgkv 層混合部署,磁盤存儲層獨立部署,咱們詳細介紹每一層的關鍵設計。
1)查詢層(bgdb)
bgdb 層和 MySQL 的 SQL 層同樣,主要工做是作讀寫請求的解析和處理;其中,所謂「處理」能夠分爲如下三個步驟:
-
將客戶端發來的 Gremlin 查詢語句作語法解析,生成執行計劃;
-
並根據必定的路由規則(例如一致性哈希)找到目標數據所在的存儲節點(bgkv),將執行計劃中的讀寫請求發送給 多個 bgkv;
-
將 bgkv 讀寫結果彙總以及過濾處理,獲得最終結果,返回給客戶端。
bgdb 層沒有狀態,能夠水平擴容,用 Go 語言開發。
2)存儲/事務引擎層(bgkv)
bgkv 層是由多個進程實例組成,每一個實例管理整個集羣數據的一個子集(shard / partition)。
bgkv 層的實現和功能有點相似內存數據庫,提供高性能的數據讀寫功能,其特色是:
-
接口不一樣:只提供點邊讀寫接口;
-
支持算子下推:經過把計算(算子)移動到存儲(bgkv)上,可以有效提高讀性能;
-
舉例:好比某個大 V 最近一年一直在漲粉,bgkv 支持查詢最近的 100 個粉絲,則沒必要讀出全部的百萬粉絲。
-
緩存存儲有機結合:其做爲 KV store 的緩存層,提供緩存管理的功能,支持緩存加載、換出、緩存和磁盤同步異步 sync 等複雜功能。
從上述描述能夠看出,bgkv 的性能和內存使用效率是很是關鍵的,所以採用 C++ 編寫。
3)磁盤存儲層(KV Cluster)
爲了可以提供海量存儲空間和較高的可靠性、可用性,數據必須最終落入磁盤,咱們底層存儲是選擇了公司自研的分佈式 KV store。
4)如何把圖存儲在 KV 數據庫中
上一小節,只是介紹了 ByteGraph 內部三層的關係,細心的讀者可能已經發現,ByteGraph 外部是圖接口,底層是依賴 KV 存儲,那麼問題來了:如何把動輒百萬粉絲的圖數據存儲在一個 KV 系統上呢?
在字節跳動的業務場景中,存在不少訪問熱度和「數據密度」極高的場景,好比抖音的大 V、熱門的文章等,其粉絲數或者點贊數會超過千萬級別;但做爲 KV store,但願業務方的 KV 對的大小(Byte 數)是控制在 KB 量級的,且最好是大小均勻的:對於太大的 value,是會瞬間打滿 I/O 路徑的,沒法保證線上穩定性;對於特別小的 value,則存儲效率比較低。事實上,數據大小不均勻這個問題困擾了不少業務團隊,在線上也會常常爆出事故。
對於一個有千萬粉絲的抖音大 V,至關於圖中的某個點有千萬條邊的出度,不只要能存儲下來,並且要能知足線上毫秒級的增刪查改,那麼 ByteGraph 是如何解決這個問題的呢?
思路其實很簡單,總結來講,就是採用靈活的邊聚合方式,使得 KV store 中的 value 大小是均勻的,具體能夠用如下四條來描述:
① 一個點(Vertex)和其全部相連的邊組成了一數據組(Group);不一樣的起點和及其終點是屬於不一樣的 Group,是存儲在不一樣的 KV 對的;好比用戶 A 的粉絲和用戶 B 的粉絲,就是分紅不一樣 KV 存儲;
② 對於某一個點的及其出邊,當出度數量比較小(KB 級別),將其全部出度即全部終點序列化爲一個 KV 對,咱們稱之爲一級存儲方式(後面會展開描述);
③ 當一個點的出度逐漸增多,好比一個普通用戶逐漸成長爲抖音大 V,咱們則採用分佈式 B-Tree 組織這百萬粉絲,咱們稱之爲二級存儲;
④ 一級存儲和二級存儲之間能夠在線併發安全的互相切換;
一級存儲格式
一級存儲格式中,只有一個 KV 對,key 和 value 的編碼:
-
key: 某個起點 id + 起點 type + 邊 type
-
value: 此起點的全部出邊(Edge)及其邊上屬性聚合做爲 value,但不包括終點的屬性
二級存儲(點的出度大於閾值)
若是一個大 V 瘋狂漲粉,則存儲粉絲的 value 就會愈來愈大,解決這個問題的思路也很樸素:拆成多個 KV 對。
但如何拆呢?ByteGraph 的方式就是把全部出度和終點拆成多個 KV 對,全部 KV 對造成一棵邏輯上的分佈式 B-Tree,之因此說「邏輯上的」,是由於樹中的節點關係是靠 KV 中 key 來指向的,並不是內存指針;B-Tree 是分佈式的,是指構成這棵樹的各級節點是分佈在集羣多個實例上的,並非單機索引關係。具體關係以下圖所示:
其中,整棵 B-Tree 由多組 KV 對組成,按照關係能夠分爲三種數據:
-
根節點:根節點本質是一個 KV 系統中的一個 key,其編碼方式和一級存儲中的 key 相同
-
Meta 數據:
-
Meta 數據本質是一個 KV 中的 value,和根節點組成了 KV 對;
-
Meta 內部存儲了多個 PartKey,其中每一個 PartKey 都是一個 KV 對中的 key,其對應的 value 數據就是下面介紹的 Part 數據;
-
Part 數據
-
對於二級存儲格式,存在多個 Part,每一個 Part 存儲部分出邊的屬性和終點 ID
-
每一個 Part 都是一個 KV 對的 value,其對應的 key 存儲在 Meta 中。
從上述描述能夠看出,對於一個出度不少的點和其邊的數據(好比大 V 和其粉絲),在 ByteGraph 中,是存儲爲多個 KV 的,面對增刪查改的需求,都須要在 B-Tree 上作二分查找。相比於一條邊一個 KV 對或者全部邊存儲成一個 KV 對的方式,B-Tree 的組織方式可以有效的在讀放大和寫放大之間作一些動態調整。
但在實際業務場景下,粉絲會處於動態變化之中:新誕生的大 V 會快速新增粉絲,有些大 V 會持續掉粉;所以,存儲方式會在一級存儲和二級存儲之間轉換,而且 B-Tree 會持續的分裂或者合併;這就會引起分佈式的併發增刪查改以及分裂合併等複雜的問題,有機會能夠再單獨分享下這個有趣的設計。
ByteGraph 和 KV store 的關係,相似文件系統和塊設備的關係,塊設備負責將存儲資源池化並提供 Low Level 的讀寫接口,文件系統在塊設備上把元數據和數據組織成各類樹的索引結構,並封裝豐富的 POSIX 接口,便於外部使用。
四、一些問題深刻探討
第三節介紹了 ByteGraph 的內在架構,如今咱們更進一步,來看看一個分佈式存儲系統,在面對字節跳動萬億數據上億併發的業務場景下兩個問題的分析。
1)熱點數據讀寫解決
熱點數據在字節跳動的線上業務中普遍存在:熱點視頻、熱點文章、大 V 用戶、熱點廣告等等;熱點數據可能會出現瞬時出現大量讀寫。ByteGraph 在線上業務的實踐中,打磨出一整套應對性方案。
2)熱點讀
熱點讀的場景隨處可見,好比線上實際場景:某個熱點視頻被頻繁刷新,查看點贊數量等。在這種場景下,意味着訪問有很強的數據局部性,緩存命中率會很高,所以,咱們設計實現了多級的 Query Cache 機制以及熱點請求轉發機制;在 bgdb 查詢層緩存查詢結果, bgdb 單節點緩存命中讀性能 20w QPS 以上,並且多個 bgdb 能夠併發處理同一個熱點的讀請求,則系統總體應對熱點度的「彈性」是很是充足的。
3)熱點寫
熱點讀和熱點寫一般是相伴而生的,熱點寫的例子也是隨處可見,好比:熱點新聞被瘋狂轉發, 熱點視頻被瘋狂點贊等等。對於數據庫而言,熱點寫入致使的性能退化的背後緣由一般有兩個:行鎖衝突高或者磁盤寫入 IOPS 被打滿,咱們分別來分析:
① 行鎖衝突高:目前 ByteGraph 是單行事務模型,只有內存結構鎖,這個鎖的併發量是每秒千萬級,基本不會構成寫入瓶頸;
② 磁盤 IOPS 被打滿:
-
IOPS(I/O Count Per Second)的概念:磁盤每秒的寫入請求數量是有上限的,不一樣型號的固態硬盤的 IOPS 各異,但都有一個上限,當上遊寫入流量超過這個閾值時候,請求就會排隊,形成整個數據通路堵塞,延遲就會呈現指數上漲最終服務變成不可用。
-
Group Commit 解決方案:Group Commit 是數據庫中的一個成熟的技術方案,簡單來說,就是多個寫請求在 bgkv 內存中匯聚起來,聚成一個 Batch 寫入 KV store,則對外體現的寫入速率就是 BatchSize * IOPS。
對於某個獨立數據源來講,通常熱點寫的請求比熱點讀會少不少,通常不會超過 10K QPS,目前 ByteGraph 線上尚未出現過熱點寫問題問題。
4)圖的索引
就像關係型數據庫同樣,圖數據庫也能夠構建索引。默認狀況下,對於同一個起點,咱們會採用邊上的屬性(時間戳)做爲主鍵索引;但爲了加速查詢,咱們也支持其餘元素(終點、其餘屬性)來構建二級的聚簇索引,這樣不少查找就從所有遍歷優化成了二分查找,使得查詢速度大幅提高。
ByteGraph 默認按照邊上的時間戳(ts)來排序存儲,所以對於如下請求,查詢效率很高:
-
查詢最近的若干個點贊
-
查詢某個指定時間範圍窗口內加的好友
方向的索引可能有些費解,舉個例子說明下:給定兩個用戶來查詢是否存在粉絲關係,其中一個用戶是大 V,另外一個是普通用戶,大 V 的粉絲可達千萬,但普通用戶的關注者通常不會不少;所以,若是用普通用戶做爲起點大 V 做爲終點,查詢代價就會低不少。其實,不少場景下,咱們還須要用戶可以根據任意一個屬性來構建索引,這個也是咱們正在支持的重要功能之一。
五、將來探索
過去的一年半時間裏,ByteGraph 都是在有限的人力狀況下,優先知足業務需求,在系統能力構建方面仍是有些薄弱的,有大量問題都須要在將來突破解決:
-
從圖存儲到圖數據庫:對於一個數據庫系統,是否支持 ACID 的事務,是一個核心問題,目前 ByteGraph 只解決了原子性和一致性,對於最複雜的隔離性還徹底沒有觸碰,這是一個很是複雜的問題;另外,中國信通院發佈了國內圖數據庫功能白皮書,以此標準,若是想作好一個功能完備的「數據庫」系統,咱們面對的仍是星辰大海;
-
標準的圖查詢語言:目前,圖數據庫的查詢語言業界還未造成標準(GQL 即將在 2020 年發佈),ByteGraph 選擇 Apache、AWS 、阿里雲的 Gremlin 語言體系,但目前也只是支持了一個子集,更多的語法支持、更深刻的查詢優化還未開展;
-
Cloud Native 存儲架構演進:如今 ByteGraph 仍是構建與 KV 存儲之上,獨佔物理機所有資源;從資源彈性部署、運維託管等角度是否有其餘架構演進的探索可能,從查詢到事務再到磁盤存儲是否有深度垂直整合優化的空間,也是一個沒有被回答的問題;
-
如今 ByteGraph 是在 OLTP 場景下承載了大量線上數據,這些數據同時也會應用到推薦、風控等複雜分析和圖計算場景,如何把 TP 和輕量 AP 查詢融合在一塊兒,具有部分 HTAP 能力,也是一個空間廣闊的藍海領域。
3、圖計算系統介紹與實踐
一、圖計算技術背景
1)圖計算簡介
圖數據庫重點面對 OLTP 場景,以事務爲核心,強調增刪查改並重,而且一個查詢每每只是涉及到圖中的少許數據;而圖計算與之不一樣,是解決大規模圖數據處理的方法,面對 OLAP 場景,是對整個圖作分析計算,下圖(引用自 VLDB 2019 keynote 《Graph Processing: A Panaromic View and Some Open Problems》)描述了圖計算和圖數據庫的一些領域區分。
舉個圖計算的簡單例子,在咱們比較熟悉的 Google 的搜索場景中,須要基於網頁連接關係計算每一個網頁的 PageRank 值,用來對網頁進行排序。網頁連接關係其實就是一張圖,而基於網頁連接關係的 PageRank 計算,其實就是在這張圖上運行圖算法,也就是圖計算。
對於小規模的圖,咱們能夠用單機來進行計算。但隨着數據量的增大,通常須要引入分佈式的計算系統來解決,而且要可以高效地運行各類類型的圖算法。
2)批處理系統
大規模數據處理咱們直接想到的就是使用 MapReduce / Spark 等批處理系統,字節跳動在初期也有很多業務使用 MapReduce / Spark 來實現圖算法。得益於批處理系統的普遍使用,業務同窗可以快速實現並上線本身的算法邏輯。
批處理系統自己是爲了處理行式數據而設計的,其可以輕易地將工做負載分散在不一樣的機器上,並行地處理大量的數據。不過圖數據比較特殊,自然具備關聯性,沒法像行式數據同樣直接切割。若是用批處理系統來運行圖算法,就可能會引入大量的 Shuffle 來實現關係的鏈接,而 Shuffle 是一項很重的操做,不只會致使任務運行時間長,而且會浪費不少計算資源。
3)圖計算系統
圖計算系統是針對圖算法的特色而衍生出的專用計算設施,可以高效地運行圖算法。所以隨着業務的發展,咱們迫切須要引入圖計算系統來解決圖數據處理的問題。圖計算也是比較成熟的領域,在學術界和工業界已有大量的系統,這些系統在不一樣場景,也各有優劣勢。
因爲面向不一樣的數據特徵、不一樣的算法特性等,圖計算系統在平臺架構、計算模型、圖劃分、執行模型、通訊模型等方面各有取捨。下面,咱們從不一樣角度對圖計算的一些現有技術作些分類分析。
① 分佈架構
按照分佈架構,圖計算能夠分爲單機或分佈式、全內存或使用外存幾種,常見的各類圖計算系統以下圖所示。單機架構的優點在於無需考慮分佈式的通訊開銷,但一般難以快速處理大規模的圖數據;分佈式則經過通訊或分佈式共享內存將可處理的數據規模擴大,但一般也會引入巨大的額外開銷。
② 計算模型
按照計算對象,圖數據計算模型能夠分爲節點中心計算模型、邊中心計算模型、子圖中心計算模型等。
大部分圖計算系統都採用了節點中心計算模型(這裏的節點指圖上的一個點),該模型來自 Google 的 Pregel,核心思想是用戶編程過程當中,以圖中一個節點及其鄰邊做爲輸入來進行運算,具備編程簡單的優點。典型的節點中心計算模型包括 Pregel 提出的 Pregel API 、 PowerGraph 提出的 GAS API 以及其餘一些 API。
Pregel 創新性地提出了 "think like a vertex" 的思想,用戶只需編寫處理一個節點的邏輯,便可被拓展到整張圖進行迭代運算,使用 Pregel 描述的 PageRank 以下圖所示:
def pagerank(vertex_id, msgs):
// 計算收到消息的值之和
msg_sum = sum(msgs)
// 更新當前PR值
pr = 0.15 + 0.85 * msg_sum
// 用新計算的PR值發送消息
for nr in out_neighbor(vertex_id):
msg = pr / out_degree(vertex_id)
send_msg(nr, msg)
// 檢查是否收斂
if converged(pr):
vote_halt(vertex_id)
GAS API 則是 PowerGraph 爲了解決冪律圖(一小部分節點的度數很是高)的問題,將對一個節點的處理邏輯,拆分爲了 Gather、Apply、Scatter 三階段。在計算知足交換律和結合律的狀況下,經過使用 GAS 模型,通訊成本從 |E| 下降到了 |V|,使用 GAS 描述的 PageRank 以下圖所示:
def gather(msg_a, msg_b):
// 匯聚消息
return msg_a + msg_b
def apply(vertex_id, msg_sum):
// 更新PR值
pr = 0.15 + 0.85 * msg_sum
// 判斷是否收斂
if converged(pr):
vote_halt(vertex_id)
def scatter(vertex_id, nr):
// 發送消息
return pr / out_degree(vertex_id)
③ 圖劃分
對於單機沒法處理的超級大圖,則須要將圖數據劃分紅幾個子圖,採用分佈式計算方式,所以,會涉及到圖劃分的問題,即如何將一整張圖切割成子圖,並分配給不一樣的機器進行分佈式地計算。常見的圖劃分方式有切邊法(Edge-Cut)和切點法(Vertex-Cut),其示意圖以下所示:
切邊法顧名思義,會從一條邊中間切開,兩邊的節點會分佈在不一樣的圖分區,每一個節點全局只會出現一次,但切邊法可能會致使一條邊在全局出現兩次。如上左圖所示,節點 A 與節點 B 之間有一條邊,切邊法會在 A 和 B 中間切開,A 屬於圖分區 1,B 屬於圖分區 2。
切點法則是將一個節點切開,該節點上不一樣的邊會分佈在不一樣的圖分區,每條邊全局只會出現一次,但切點法會致使一個節點在全局出現屢次。如上圖右圖所示,節點 A 被切分爲 3 份,其中邊 AB 屬於分區 2,邊 AD 屬於圖分區 3。
圖劃分還會涉及到分圖策略,好比切點法會有各類策略的切法:按邊隨機哈希、Edge1D、Edge2D 等等。有些策略是可全局並行執行分圖的,速度快,但負載均衡和計算時的通訊效率不理想;有些是須要串行執行的但負載均衡、通訊效率會更好,各類策略須要根據不一樣的業務場景進行選擇。
④ 執行模型
執行模型解決的是不一樣的節點在迭代過程當中,如何協調迭代進度的問題。圖計算一般是全圖多輪迭代的計算,好比 PageRank 算法,須要持續迭代直至全圖全部節點收斂纔會結束。
在圖劃分完成後,每一個子圖會被分配到對應的機器進行處理,因爲不一樣機器間運算環境、計算負載的不一樣,不一樣機器的運算速度是不一樣的,致使圖上不一樣節點間的迭代速度也是不一樣的。爲了應對不一樣節點間迭代速度的不一樣,有同步計算、異步計算、以及半同步計算三種執行模型。
同步計算是全圖全部節點完成一輪迭代以後,纔開啓下一輪迭代,由於一般每一個節點都會依賴其餘節點在上一輪迭代產生的結果,所以同步計算的結果是正確的。
異步計算則是每一個節點不等待其餘節點的迭代進度,在本身計算完一輪迭代後直接開啓下一輪迭代,因此就會致使不少節點尚未徹底拿到上一輪的結果就開始了下一輪計算。
半同步計算是二者的綜合,其思想是容許必定的不一樣步,但當計算最快的節點與計算最慢的節點相差必定迭代輪數時,最快的節點會進行等待。同步計算和異步計算的示意圖以下圖:
同步計算和異步計算各有優劣,其對好比下表所示,半同步是二者折中。多數圖計算系統都採用了同步計算模型,雖然計算效率比異步計算弱一些,但它具備易於理解、計算穩定、結果準確、可解釋性強等多個重要的優勢。
⑤ 通訊模型
爲了實現拓展性,圖計算採用了不一樣的通訊模型,大體可分爲分佈式共享內存、Push 以及 Pull。分佈式共享內存將數據存儲在共享內存中,經過直接操做共享內存完成信息交互;Push 模型是沿着出邊方向主動推送消息;Pull 則是沿着入邊方向主動收消息。三者優劣對好比下表格所示:
二、技術選型
因爲字節跳動要處理的是世界級的超大規模圖,同時還對計算任務運行時長有要求,所以主要考慮高性能、可拓展性強的圖計算系統。工業界使用比較多的系統主要有如下幾類:
1)Pregel & Giraph
Google 提出了 Pregel 來解決圖算法在 MapReduce 上運行低效的問題,但沒有開源。Facebook 根據 Pregel 的思路發展了開源系統 Giraph,但 Giraph 有兩個問題:一是 Giraph 的社區不是很活躍;二是現實生活中的圖都是符合冪律分佈的圖,即有一小部分點的邊數很是多,這些點在 Pregel 的計算模式下很容易拖慢整個計算任務。
2)GraphX
GraphX 是基於 Spark 構建的圖計算系統,融合了不少 PowerGraph 的思想,並對 Spark 在運行圖算法過程當中的多餘 Shuffle 進行了優化。GraphX 對比原生 Spark 在性能方面有很大優點,但 GraphX 很是費內存,Shuffle 效率也不是很高,致使運行時間也比較長。
3)Gemini
Gemini 是 16 年發表再在 OSDI 的一篇圖計算系統論文,結合了多種圖計算系統的優點,而且有開源實現,做爲最快的圖計算引擎之一,獲得了業界的廣泛承認。
正如《Scalability! But at what COST? 》一文指出,多數的圖計算系統爲了拓展性,忽視了單機的性能,加之分佈式帶來的巨大通訊開銷,致使多機環境下的計算性能有時甚至反而不如單機環境。針對這些問題,Gemini 的作了針對性優化設計,簡單總結爲:
-
圖存儲格式優化內存開銷:採用 CSC 和 CSR 的方式存儲圖,並對 CSC/CSR 進一步創建索引下降內存佔用;
-
Hierarchical Chunk-Based Partitioning:經過在 Node、Numa、Socket 多個維度作區域感知的圖切分,減小通訊開銷;
-
自適應的 Push / Pull 計算:採用了雙模式通訊策略,能根據當前活躍節點的數量動態地切換到稠密或稀疏模式。
-
兼顧單機性能和擴展性,使得 Gemini 處於圖計算性能最前沿,同時,Gemini 團隊也成立了商業公司專一圖數據的處理。
Tencent Plato 是基於 Gemini 思想的開源圖計算系統,採用了 Gemini 的核心設計思路,但相比 Gemini 的開源版本有更加完善的工程實現,咱們基於此,作了大量重構和二次開發,將其應用到生成環境中,這裏分享下咱們的實踐。
1)更大數據規模的探索
開源實現中有個很是關鍵的假設:一張圖中的點的數量不能超過 40 億個;但字節跳動部分業務場景的數據規模遠超出了這個數額。爲了支持千億萬億點的規模,咱們將產生內存瓶頸的單機處理模塊,重構爲分佈式實現。
① 點 ID 的編碼
Gemini 的一個重要創新就是提出了基於 Chunk 的圖分區方法。這種圖分區方法須要將點 id 從 0 開始連續遞增編碼,但輸入的圖數據中,點 id 是隨機生成的,所以須要對點 id 進行一次映射,保證其連續遞增。具體實現方法是,在計算任務開始以前將原始的業務 id 轉換爲從零開始的遞增 id,計算結束後再將 id 映射回去,以下圖所示:
在開源實現中,是假設圖中點的數量不可超過 40 億,40 億的 id 數據是能夠存儲在單機內存中,所以採用比較簡單的實現方式:分佈式計算集羣中的每臺機器冗餘存儲了全部點 id 的映射關係。然而,當點的數量從 40 億到千億級別,每臺機器僅 id 映射表就須要數百 GB 的內存,單機存儲方案就變得再也不可行,所以須要將映射表分紅 shard 分佈式地存儲,具體實現方式以下:
咱們經過哈希將原始業務點 id 打散在不一樣的機器,並行地分配全局從 0 開始連續遞增的 id。生成 id 映射關係後,每臺機器都會存有 id 映射表的一部分。隨後再將邊數據分別按起點和終點哈希,發送到對應的機器進行編碼,最終獲得的數據即爲可用於計算的數據。當計算運行結束後,須要數據須要映射回業務 id,其過程和上述也是相似的。
上面描述的僅僅是圖編碼部分,40 億點的值域限制還普遍存在於構圖和實際計算過程當中,咱們都對此作了重構。另外在咱們的規模下,也碰到了一些任務負載不均,不夠穩定,計算效率不高等問題,咱們對此都作了部分優化和重構。
經過對開源實現的改造,字節跳動的圖計算系統已經在線上支撐了多條產品線的計算任務,最大規模達到數萬億邊、數千億點的世界級超大圖,這是業內罕見的。同時,面對不斷增加的業務,而且咱們還在持續擴大系統的邊界,來應對更大規模的挑戰。
2)自定義算法實現
在常見圖計算算法以外,字節跳動多元的業務中,有大量的其餘圖算法需求以及現有算法的改造需求,好比須要實現更適合二分圖的 LPA 算法,須要改造 PageRank 算法使之更容易收斂。
因爲當前圖計算系統暴露的 API 尚未很是好的封裝,使得編寫算法的用戶會直接感知到底層的內部機制,好比不一樣的通訊模式、圖表示方式等,這當然方便了作圖計算算法實現的調優,但也致使業務同窗有必定成本;另外,由於涉及超大規模數據的高性能計算,一個細節(好比 hotpath 上的一個虛函數調用,一次線程同步)可能就對性能有相當重要的影響,須要業務同窗對計算機體系結構有必定了解。基於上述兩個緣由,目前算法是圖計算引擎同窗和圖計算用戶一塊兒開發,但長期來看,咱們會封裝經常使用計算算子並暴露 Python Binding ,或者引入 DSL 來下降業務的學習成本。
四、將來展望
面對字節跳動的超大規模圖處理場景,咱們在半年內快速開啓了圖計算方向,支持了搜索、風控等多個業務的大規模圖計算需求,取得了不錯的進展,但還有衆多須要咱們探索的問題:
1)從全內存計算到混合存儲計算:爲了支持更大規模的數據量,提供更加低成本的計算能力,咱們將探索新型存儲硬件,包括 AEP / NVMe 等內存或外存設備,擴大系統能力;
2)動態圖計算:目前的系統只支持靜態圖計算,即對完整圖的全量數據進行計算。實際業務中的圖每時每刻都是在變化的,所以使用現有系統必須在每次計算都提供整張圖。而動態圖計算可以比較好地處理增量的數據,無需對已經處理過的數據進行重複計算,所以咱們將在一些場景探索動態圖計算;
3)異構計算:圖計算系統屬於計算密集型系統,在部分場景對計算性能有極高的要求。所以咱們會嘗試異構計算,包括使用 GPU / FPGA 等硬件對計算進行加速,以追求卓越的計算性能;
4)圖計算語言:業務直接接觸底層計算引擎有不少弊端,好比業務邏輯與計算引擎強耦合,沒法更靈活地對不一樣算法進行性能優化。而經過圖計算語言對算法進行描述,再對其編譯生成計算引擎的執行代碼,能夠將業務邏輯與計算引擎解耦,能更好地對不一樣算法進行自動地調優,將性能發揮到極致。
4、總結
隨着字節跳動業務量級的飛速增加和業務需求的不斷豐富,咱們在短期內構建了圖存儲系統和圖計算系統,在實際生產系統中解決了大量的問題,但同時仍面臨着巨大的技術挑戰,咱們將持續演進,打造業界頂尖的一棧式圖解決方案。將來已來,空間廣闊,但願更多有興趣的同窗加入進來,用有趣的分佈式技術來影響幾億人的互聯網生活。
>>>>
參考資料
-
Bronson, Nathan, et al. "{TAO}: Facebook’s distributed data store for the social graph." Presented as part of the 2013 {USENIX} Annual Technical Conference ({USENIX}{ATC} 13). 2013.
-
Malewicz, Grzegorz, et al. "Pregel: a system for large-scale graph processing." Proceedings of the 2010 ACM SIGMOD International Conference on Management of data. 2010.
-
Low, Yucheng, et al. "Distributed graphlab: A framework for machine learning in the cloud." arXiv preprint arXiv:1204.6078 (2012).
-
Gonzalez, Joseph E., et al. "Powergraph: Distributed graph-parallel computation on natural graphs." Presented as part of the 10th {USENIX} Symposium on Operating Systems Design and Implementation ({OSDI} 12). 2012.
-
Gonzalez, Joseph E., et al. "Graphx: Graph processing in a distributed dataflow framework." 11th {USENIX} Symposium on Operating Systems Design and Implementation ({OSDI} 14). 2014.
-
Zhu, Xiaowei, et al. "Gemini: A computation-centric distributed graph processing system." 12th {USENIX} Symposium on Operating Systems Design and Implementation ({OSDI} 16). 2016.
-
Kyrola, Aapo, Guy Blelloch, and Carlos Guestrin. "Graphchi: Large-scale graph computation on just a {PC}." Presented as part of the 10th {USENIX} Symposium on Operating Systems Design and Implementation ({OSDI} 12). 2012.
-
Roy, Amitabha, Ivo Mihailovic, and Willy Zwaenepoel. "X-stream: Edge-centric graph processing using streaming partitions." Proceedings of the Twenty-Fourth ACM Symposium on Operating Systems Principles. 2013.
-
Shun, Julian, and Guy E. Blelloch. "Ligra: a lightweight graph processing framework for shared memory." Proceedings of the 18th ACM SIGPLAN symposium on Principles and practice of parallel programming. 2013.
-
McSherry, Frank, Michael Isard, and Derek G. Murray. "Scalability! But at what {COST}?." 15th Workshop on Hot Topics in Operating Systems (HotOS {XV}). 2015.
-
Aditya Auradkar, Chavdar Botev, Shirshanka Das. "Data Infrastructure at LinkedIn "2012 IEEE 28th International Conference on Data Engineering
做者丨字節跳動技術團隊 技術架構團隊 來源丨字節跳動技術團隊(ID:toutiaotechblog) dbaplus社羣歡迎廣大技術人員投稿,投稿郵箱: editor@dbaplus.cn