百億級圖數(shù)據(jù)JanusGraph遷移之旅

百億級圖數(shù)據(jù) JanusGraph 遷移之旅

1. 遷移背景介紹

目前我們的圖數(shù)據(jù)庫數(shù)據(jù)量為 頂點 20 億,邊 200 億的規(guī)模嘉蕾。在遷移之前我們使用的 AgensGraph 數(shù)據(jù)庫
一個主庫四個備庫贺奠,機器的配置都比較高,256G 內(nèi)存 SSD 的磁盤错忱,單機數(shù)據(jù)量為 3T左右儡率。
在數(shù)據(jù)量比較小的情況下 AgensGraph 表現(xiàn)非常穩(wěn)定優(yōu)異,我們之前一主一備的情況下支撐了很長一段時間航背。
但隨著公司業(yè)務(wù)的急速發(fā)展喉悴,圖越來越大棱貌,占用的磁盤越來越多玖媚,對應(yīng)的查詢量也越來越大,隨之這種方案的問題就暴露出來了

  • 單機的磁盤空間不夠婚脱,按理說可以一直添加磁盤今魔,但現(xiàn)實情況有很多限制
  • AgensGraph 不是分布式結(jié)構(gòu),每次查詢都需要單機處理障贸,單臺機器的處理能力總是有上限的错森,導(dǎo)致查詢耗時增加
  • 隨著查詢量的增加,機器磁盤篮洁,網(wǎng)絡(luò) IO 出現(xiàn)瓶頸涩维。按理說可以通過增加備庫來解決,但備庫要求高導(dǎo)致成本增高袁波,并且數(shù)據(jù)冗余嚴重

由于上面的原因?qū)е?AgensGraph 沒辦法繼續(xù)支撐業(yè)務(wù)高速發(fā)展帶來的性能要求瓦阐。AgensGraph 底層基于 PostgreSQL 數(shù)據(jù)庫使它在小數(shù)據(jù)量的情況下非常的穩(wěn)定并且查詢響應(yīng)非常的迅速,在此感謝 AgensGraph 陪我們度過業(yè)務(wù)快速成長階段篷牌。

為了尋找新的圖數(shù)據(jù)庫我們把目光投向了接受度和知名度都比較高的 JanusGraph睡蟋。當然還有收費的圖數(shù)據(jù)庫 TigerGraph,暫時不做考慮
在此貼一張我們圖的應(yīng)用場景枷颊,查詢用戶之間的關(guān)系

用戶關(guān)系

由于這不是一篇介紹 JanusGraph 文章戳杀,在此不對 JanusGraph 做過多的介紹该面,大家可以自行了解。這里主要列舉下它的優(yōu)點:

  • 分布式圖數(shù)據(jù)庫信卡,支持水平拓展
  • 底層存儲基于 Hbase/Cassandra 隔缀,技術(shù)成熟
  • 支持 OLAP 對圖進行批量處理,豐富圖的功能
  • 支持 TinkerPop Gremlin 查詢語句傍菇,能滿足更復(fù)雜的業(yè)務(wù)查詢需求

2. 數(shù)據(jù)導(dǎo)入方案探索

簡單介紹完 JanusGraph 的優(yōu)點蚕泽,就正式開始遷移數(shù)據(jù)了。不得不說我們嚴重低估的數(shù)據(jù)的遷移難度桥嗤,之前預(yù)估大概兩周就能搞定须妻,結(jié)果花了快兩個月的時間。
方案一:利用 GremlimServer 批量插入
我們最開始采用的數(shù)據(jù)導(dǎo)入方式是連接 GremlinServer 批量插入頂點泛领,然后再插入邊荒吏,在插入邊的同時需要檢索到關(guān)聯(lián)的頂點。
批量插入的優(yōu)化方案主要參考下面這篇 blog 渊鞋。批量插入頂點的時候還是比較慢 20億頂點花了一周才搞定绰更。這里說明下,我們底層存儲用的是 HBase 集群锡宋,80多臺機器儡湾。為了加快導(dǎo)入的速度我們的插入程序是用Spark 編寫的,導(dǎo)入數(shù)據(jù)存放在 HDFS 集群上徐钠。值得注意的地方是數(shù)據(jù)寫入需要使用同步方式尝丐,異步很快就會把GremlinServer 內(nèi)存寫滿爹袁,然后出現(xiàn)連接異常。
導(dǎo)入完頂點導(dǎo)入邊的時候才發(fā)現(xiàn)邊的導(dǎo)入非常的慢盹兢,按照當時的導(dǎo)入速度計算 200 億邊預(yù)計需要 3個月的時間才能導(dǎo)入完成蛤迎,這種速度是不能接受的替裆。
插入邊比較慢辆童,最主要的原因是每插入一條邊都需要檢索兩個頂點故黑。社區(qū)里面建議是維持 name 索引到頂點id的一個 map 存放到內(nèi)存中场晶,我們沒試過诗轻,主要感覺有兩方面問題,第一20億點的需要不少內(nèi)存恨樟,其次因為我們頂點是批量插入的劝术,構(gòu)建這個 map 不是很方便,于是就放棄了這個方案。
方案二:生成 Cassandra SSTable 文件
只能嘗試其他方案陈轿,嘗試過網(wǎng)上生成 Cassandra SSTable 文件的方式導(dǎo)入數(shù)據(jù),最后在建立索引的時候有問題潜秋,聯(lián)系上原作者說不建議這種方式罗售,說代碼有bug數(shù)據(jù)有丟失寨躁。也只能放棄這種方案
方案三:生成 HBase Hfile 文件
想過自己寫程序生成 HBase Hfile的形式快速導(dǎo)入數(shù)據(jù),最大的困難是 JanusGraph 對 Hbase 表結(jié)構(gòu)的介紹文檔基本找不到放钦,只能看源代碼最筒,這個在短時間內(nèi)是比較難的。我們這邊時間也不允許邢锯, AgensGraph 的磁盤很快就滿了丹擎,查詢壓力也越來越大蒂培。另外這個也需要對 Hbase 有深入了解,團隊中缺少這樣的技術(shù)專家媳荒,大家都停留在使用層面。所以這個方案最終也選擇放棄
最終方案:bulkLoader 方式
最終還是把目光放到了JanusGraph 官方提供的 bulkLoader 方式鱼炒。其實最開始想到的就是這個方案指蚁,但是這個方案對導(dǎo)入的數(shù)據(jù)有非常嚴格的要求欣舵,它需要每個頂點一行數(shù)據(jù),再把這個頂點關(guān)聯(lián)的所有邊都關(guān)聯(lián)到這一行糟把,中間用 tab 分隔,第一部分是頂點的屬性,第二部分是頂點的入邊辨液,第三部分是頂點的出邊。當時一看到這種結(jié)構(gòu)就很頭大燎悍,我們的頂點有3榜揖,4種關(guān)系举哟,處理成這種格式感覺不可能,完全不知道怎么處理威兜。以下就是 JanusGraph 官方提供的例子大家感受下

179,song,COSMIC CHARLIE,,0 followedBy,130,1|followedBy,82,1|followedBy,76,1|followedBy,101,1|followedBy,3,1|followedBy,25,1|followedBy,215,1 followedBy,178,1|followedBy,148,1|followedBy,76,1|followedBy,110,3|followedBy,101,1

和團隊成員討論,看有什么辦法能轉(zhuǎn)換成這種格式笔宿,同事提醒說 Spark 有個 cogroup 操作應(yīng)該可以達到這個目的迈勋。深入了解之后發(fā)現(xiàn)這就是我們要找的重归,cogroup 的核心思想就是將多個 RDD 根據(jù)相同的 key 可以 jion成一行,下面是個簡單的例子

val rdd1 = sc.parallelize(Array(("aa",1),("bb",2),("cc",6)))
val rdd2 = sc.parallelize(Array(("aa",3),("dd",4),("aa",5)))
rdd1.cogroup(rdd2).collect()

output:
(aa,(CompactBuffer(1),CompactBuffer(3, 5)))
(dd,(CompactBuffer(),CompactBuffer(4)))
(bb,(CompactBuffer(2),CompactBuffer()))
(cc,(CompactBuffer(6),CompactBuffer()))

我們轉(zhuǎn)換為目標導(dǎo)入格式的代碼已經(jīng)放到 github上了笨腥,大家可以參考士鸥,沒有封裝成通用的工具,大家借鑒按自己的數(shù)據(jù)結(jié)構(gòu)進行修改。

3. 數(shù)據(jù)導(dǎo)入過程

接下來就是按需要的格式生成導(dǎo)入數(shù)據(jù),這中間有個值得注意的地方就是確保頂點 ID 的唯一性窥突,確保數(shù)據(jù)沒有重復(fù)称近,不然會導(dǎo)入失敗煌茬。

我們還是低估了這種 bulkLoader 導(dǎo)入數(shù)據(jù)的難度眠屎,導(dǎo)入花了比較長的時間驯镊,最主要的問題分為兩部分冯乘,一部分是 Hbase 相關(guān)參數(shù)調(diào)整的問題姊氓,另外一部分是 Spark 任務(wù)的內(nèi)存優(yōu)化問題。最痛苦的還是這種 bulkLoader 導(dǎo)入方式如果過程中出現(xiàn)問題喷好,失敗了翔横,只能將數(shù)據(jù)清理掉重新導(dǎo)入。

先說 Hbase 參數(shù)相關(guān)的問題梗搅,JanusGraph 導(dǎo)入的過程中會往Hbase中寫入大量數(shù)據(jù)棕孙,這個時候 Hbase 會有很多的異常情況出現(xiàn)。

下列參數(shù)就是導(dǎo)入過程中和 Hbase 相關(guān)的參數(shù)些膨,這些參數(shù)都是從一次次失敗中提煉總結(jié)出來的蟀俊。當然這些參數(shù)都是根據(jù)我們自己的環(huán)境設(shè)置的,大家應(yīng)該做相應(yīng)調(diào)整

# 這個參數(shù)批量導(dǎo)入需要設(shè)置
storage.batch-loading=true

# 這個參數(shù) 經(jīng)過調(diào)試订雾,這個值比較合理
ids.block-size=20000000
ids.renew-timeout=3600000
storage.buffer-size=20240
# 使插入數(shù)據(jù)更 robust
storage.read-attempts=100
storage.write-attempts=100
storage.attempt-wait=1000

# 分區(qū)數(shù)肢预,最好設(shè)置為 hbase 機器數(shù) 的2到 3倍
storage.hbase.region-count = 150

# hbase 超時時間,這個非常重要洼哎,不然導(dǎo)入會因為超時報錯
# 需要hbase 服務(wù)器端同步設(shè)置烫映,取客服端和服務(wù)器端的最小值
# 這些參數(shù)的只是是 看 janusgraph 源碼才發(fā)現(xiàn)可以設(shè)置的
storage.hbase.ext.hbase.rpc.timeout = 300000
storage.hbase.ext.hbase.client.operation.timeout = 300000
storage.hbase.ext.hbase.client.scanner.timeout.period = 300000

再來說導(dǎo)入過程中 Spark 相關(guān)的問題。JanusGraph 官方集成 Spark的時候只提供了單機模式和 standalone cluster 模式的配置方式噩峦,沒有提供如何集成 Spark on Yarn 的文檔锭沟。這就導(dǎo)致一個問題,我們是有 Spark on Yarn 環(huán)境的并且集群性能和資源都很好∈恫梗現(xiàn)在利用不上這部分資源需要重新申請機器再搭建一個 standalone cluster 的 Spark 集群族淮。好在我們當時有部分機器空閑搭建了 standalone cluster 集群。并且我們也通過其他同事的努力解決了 JanusGraph 如何集成 Spark on Yarn

說回 Spark 導(dǎo)入過程中相關(guān)的問題凭涂,最主要的問題就是如何平衡 executor 內(nèi)存和并行度的問題祝辣。executor 內(nèi)存配置的小能夠增加并行度但是會出現(xiàn) OutOfMemoryError,如果把內(nèi)存調(diào)整的很大并行度又下來了切油,導(dǎo)入時間會很長蝙斜,不確定性增加。另一個問題就是如果并行度過高 Hbase 集群能否支撐的住澎胡。最終需要在這些問題中找到平衡孕荠。

下面是我們生產(chǎn)環(huán)境配置的 Spark 相關(guān)參數(shù)

spark.network.timeout=7600

spark.master=yarn
spark.deploy-mode=client
#  spark.executor.memory/spark.executor.cores 保障在8g左右,具體看數(shù)據(jù)量
spark.executor.memory=20g
spark.executor.cores=2
spark.yarn.queue=root.graph
spark.executor.instances=70

spark.executor.extraJavaOptions=-XX:+UseG1GC
spark.shuffle.io.retryWait=120s

spark.serializer=org.apache.spark.serializer.KryoSerializer
spark.kryo.registrator=org.apache.tinkerpop.gremlin.spark.structure.io.gryo.GryoRegistrator

gremlin.spark.graphStorageLevel=MEMORY_AND_DISK
gremlin.spark.persistContext=true
gremlin.spark.graphWriter=org.apache.tinkerpop.gremlin.spark.structure.io.PersistedOutputRDD
gremlin.spark.persistStorageLevel=DISK_ONLY

以上相關(guān)的參數(shù)在我上面提到的 github倉庫中都有做相關(guān)說明攻谁,大家可以根據(jù)自己的情況自行做相應(yīng)調(diào)整剪返。

4.JanusGraph 查詢優(yōu)化

本來以為經(jīng)歷完漫長的數(shù)據(jù)導(dǎo)入過程从橘,后面會順利很多,但是現(xiàn)實和期望還是有差距。問題是 JanusGraph 在大數(shù)據(jù)量情況下掀亩,查詢性能達不到生產(chǎn)要求,查詢需要幾十秒妒挎。相同的功能在 AgensGraph 查詢都是秒級腰埂。

好在 JanusGraph 查詢語句都可以用 profile 功能進行分析調(diào)試,通過分析的結(jié)果能明確知道那些地方有性能問題那槽。

經(jīng)過分析發(fā)現(xiàn)慢的最主要的原因就是 JanusGraph 獲取頂點屬性特別慢悼沿,默認居然不是并行獲取而是逐條獲取。我們的應(yīng)用場景屬性都是放到頂點上骚灸,例如:如果我要查詢一個用戶的通話關(guān)系糟趾,但是需要過濾只要相關(guān)注冊用戶,查詢語句像下面這樣

g.V().has("name","138xxxx4444").both("CALL").has("is_register","true")

上面的查詢語句假設(shè)這個用戶和 1000 個人有通話關(guān)系,但是我只關(guān)心和他相關(guān)的注冊用戶 100 人义郑。JanusGraph 默認的做法是逐條獲取這個1000 個用戶的所有屬性蝶柿,再在內(nèi)存中做過濾最后獲得這 100 個用戶,這就導(dǎo)致關(guān)聯(lián)的頂點數(shù)量比較大的時候非驮,直接不可用交汤。

好在 JanusGraph 在最新的 0.4 版本中提供了一個 _multiPreFetch 的優(yōu)化功能,能在屬性過濾的時候批量并行獲取所有關(guān)聯(lián)頂點的屬性劫笙,再在內(nèi)存做屬性過濾芙扎,關(guān)于這個功能的詳細介紹可以看這里。個人感覺在沒有這個優(yōu)化功能的情況下 JanusGraph 基本不具備在生產(chǎn)環(huán)境使用的條件填大。并且這個功能并不是很完善戒洼,當你的過濾條件是 hasNot, 或者返回邊的屬性允华,或者語句后有 limit 操作都會使這個優(yōu)化失效圈浇。而你能做的只能是想盡辦法繞開,例如:has("is_exception", neq("true"))

另一個問題就是 JanusGraph 查詢的數(shù)據(jù)如何返回的問題例获,Gremlin 返回數(shù)據(jù)支持多種寫法汉额。最常用的就是使用 valueMap 的方式,但是這里面有兩個比較大的坑榨汤,第一個是返回的屬性值默認是list類型蠕搜,第二個是如果返回結(jié)果使用多個 valueMap 導(dǎo)致特別消耗內(nèi)存。

這兩個問題好在都能找到解決方法收壕,詳細情況不在這里做過多說明請參考這里妓灌。這些問題JanusGraph 都沒有做很好的說明,并且默認也沒做規(guī)避蜜宪,都是一次次痛苦的經(jīng)歷得出來的經(jīng)驗虫埂,所以說 JanusGraph目前還不是特別成熟。

5.未來

雖然經(jīng)過上面的優(yōu)化圃验,我們發(fā)現(xiàn)在數(shù)據(jù)量比較大的情況下掉伏,查詢還是比較慢。經(jīng)過分析發(fā)現(xiàn)主要從 Hbase 獲取大量數(shù)據(jù)比較慢澳窑。分析 Hbase Region Server 的負載情況斧散,發(fā)現(xiàn)磁盤IO 負載比較高。所以我們下一步的策略是搭建 一套基于 SSD 磁盤的 Hbase 集群來加速查詢性能摊聋。

同時也期待 JanusGraph 開源社區(qū)的快速發(fā)展鸡捐,為大家提供性能更高效的圖數(shù)據(jù)庫。同時也希望我們自己能對 JanusGraph 做一些優(yōu)化并且回饋社區(qū)麻裁。希望大家一起為 JanusGraph 圖數(shù)據(jù)庫社區(qū)的發(fā)展助力

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箍镜,一起剝皮案震驚了整個濱河市源祈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌色迂,老刑警劉巖香缺,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異脚草,居然都是意外死亡赫悄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門馏慨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人姑隅,你說我怎么就攤上這事写隶。” “怎么了讲仰?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵慕趴,是天一觀的道長。 經(jīng)常有香客問我鄙陡,道長冕房,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任趁矾,我火速辦了婚禮耙册,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毫捣。我一直安慰自己详拙,他們只是感情好,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布蔓同。 她就那樣靜靜地躺著饶辙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪斑粱。 梳的紋絲不亂的頭發(fā)上弃揽,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音则北,去河邊找鬼矿微。 笑死,一個胖子當著我的面吹牛咒锻,可吹牛的內(nèi)容都是我干的冷冗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惑艇,長吁一口氣:“原來是場噩夢啊……” “哼蒿辙!你這毒婦竟也來了拇泛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤思灌,失蹤者是張志新(化名)和其女友劉穎俺叭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泰偿,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡熄守,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了耗跛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裕照。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖调塌,靈堂內(nèi)的尸體忽然破棺而出晋南,到底是詐尸還是另有隱情,我是刑警寧澤羔砾,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布负间,位于F島的核電站,受9級特大地震影響姜凄,放射性物質(zhì)發(fā)生泄漏政溃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一态秧、第九天 我趴在偏房一處隱蔽的房頂上張望董虱。 院中可真熱鬧,春花似錦屿聋、人聲如沸空扎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽转锈。三九已至,卻和暖如春楚殿,著一層夾襖步出監(jiān)牢的瞬間撮慨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工脆粥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留砌溺,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓变隔,卻偏偏與公主長得像规伐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子匣缘,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容