圖解JanusGraph系列 - 關於JanusGraph圖數據批量快速導入的方案和想法(bulk load data)

你們好,我是洋仔,JanusGraph圖解系列文章,實時更新~java

圖數據庫文章總目錄:

源碼分析相關可查看github碼文不易,求個star~): https://github.com/YYDreamer/janusgraphnode

版本:JanusGraph-0.5.2git

轉載文章請保留如下聲明:github

做者:洋仔聊編程算法

微信公衆號:匠心Java數據庫

原文地址:https://liyangyang.blog.csdn.net/編程

前言

JanusGraph的批量導入速度一直是用戶使用的痛點, 下面會依託官網的介紹和我的理解,聊一下關於圖數據批量快速導入的一些方案、方案使用場景和一些想;json

寫這篇文章的目的主要是爲了讓你們瞭解一下janus的導入的一些經常使用方案,算是一個總結吧,若有疑問或者文章錯誤,歡迎留言聯繫我後端

首先,說一下JanusGraph的批量導入的可配置的優化配置選項 和 基於第三方存儲和索引的優化配置選項:api

  • 批量導入的配置選項
  • 第三方存儲後端的優化選項(Hbase爲例)
  • 第三方索引後端的優化選項(ES爲例)

以後分析一下數據導入的四個方案:

  • 基於JanusGraph Api的批量導入
  • 基於Gremlin Server的批量導入
  • 使用JanusGraph-utils的批量導入
  • 基於bulk loader 導入方式
  • 基於抽取序列化邏輯生成Hfile離線批量導入

最後聊一下關於批量導入的一些想法;

一:批量導入的優化配置選項

一、批量導入的配置選項

JanusGraph中有許多配置選項和工具能夠將大量的圖數據更有效地導入。這種導入稱爲批量加載,與默認的事務性加載相反,默認的事務性加載單個事務只會添加少許數據。

下述介紹了配置選項和工具,這些工具和工具使JanusGraph中的批量加載更加高效。

在繼續操做以前,請仔細遵照每一個選項的限制和假設,以避免丟失數據或損壞數據。

配置選項

janusgraph支持批量導入,可經過相關配置項設置

下面具體看一下對應配置項的詳細做用:

批量加載

1) 配置項:storage.batch-loading

啓用該配置項,至關於打開了JanusGraph的批量導入開關;

影響:

啓用批處理加載會在許多地方禁用JanusGraph內部一致性檢查,重要的是會禁用lock鎖定來保證分佈式一致性;JanusGraph假定要加載到JanusGraph中的數據與圖形一致,所以出於性能考慮禁用了本身的一致性檢查。

換句話說,咱們要在導入數據以前,保證要導入的數據和圖中已有的數據不會產生衝突也就是保持一致性!

爲何要禁用一致性檢查來提高性能?

在許多批量加載方案中,在加載數據以前確保去數據一致性,而後在將數據加載到數據庫比在加載數據到圖庫時確保數據一致性,消耗的成本要便宜的多。

例如,將現有用戶數據文件批量加載到JanusGraph中的用例:假設用戶名屬性鍵具備定義的惟一複合索引,即用戶名在整個圖中必須是惟一的。

那麼按名稱對數據文件進行排序並過濾出重複項或編寫執行此類過濾的Hadoop做業消耗的時間成本,就會比開啓一致性檢查在導入圖數據時janusgraph檢查花費的成本要少的多。

基於上述,咱們能夠啓用 storage.batch-loading配置,從而大大減小了批量加載時間,由於JanusGraph沒必要檢查每一個添加的用戶該名稱是否已存在於數據庫中。

重要提示

啓用storage.batch-loading要求用戶確保加載的數據在內部是一致的,而且與圖中已存在的任何數據一致。

特別是,啓用批處理加載時,併發類型建立會致使嚴重的數據完整性問題。所以,咱們強烈建議經過schema.default = none在圖形配置中進行設置來禁用自動類型建立。

優化ID分配

一、ID塊大小

1)配置項:ids.block-size

該配置項爲配置在分佈式id的生成過程當中每次獲取 id block的大小;

分佈式id相關具體可看文章《圖解Janusgraph系列-分佈式id生成策略分析》

原理:

每一個新添加的頂點或邊都分配有惟一的ID。JanusGraph的ID池管理器以block的形式獲取特定JanusGraph實例的ID。id塊獲取過程很昂貴,由於它須要保證塊的全局惟一分配。

增長 ids.block-size會減小獲取次數,但可能會使許多ID未被分配,從而形成浪費。對於事務性工做負載,默認塊大小是合理的,可是在批量加載期間,頂點和邊的添加要頻繁得多,並且要快速連續。

所以,一般建議將塊大小增長10倍或更多,具體取決於每臺機器要添加的頂點數量。

經驗法則

設置ids.block-size爲您但願每小時爲每一個JanusGraph實例添加的頂點數。

重要提示:

必須爲全部JanusGraph實例配置相同的值,ids.block-size以確保正確的ID分配。所以,在更改此值以前,請務必關閉全部JanusGraph實例

二、ID Acquisition流程

當許多JanusGraph實例頻繁並行分配id塊時,不可避免地會出現實例之間的分配衝突,從而減慢了分配過程。

此外,因爲大容量加載而致使的增長的寫負載可能會使該過程進一步減慢到JanusGraph認爲失敗並引起異常的程度。能夠調整2個配置選項來避免這種狀況;

1)配置項:ids.authority.wait-time

配置ID池管理器等待應用程序獲取ID塊被存儲後端確認的時間(以毫秒爲單位)。這段時間越短,應用程序在擁擠的存儲羣集上發生故障的可能性就越大。

經驗法則

將其設置爲負載下存儲後端集羣上測量的第95百分位讀寫時間的總和。

重要說明

全部JanusGraph實例的該值都應該相同。

2)配置項:ids.renew-timeout

配置JanusGraph的ID池管理器在嘗試獲取新的ID塊總共等待的毫秒數。

經驗法則

將此值設置爲儘量大,沒必要爲不可恢復的故障等待過久。增長它的惟一缺點是JanusGraph將在不可用的存儲後端羣集上嘗試更長的時間

優化讀寫

一、緩衝區大小

JanusGraph在數據導入時存在一個緩衝區,用來緩衝當前事務的部分請求,從而能夠小批量的寫入和執行,從而減小針對存儲後端的請求數。在短期內執行大量寫操做時,存儲後端可能會由於大量的寫請求打入而變得超負荷;

配置項:storage.buffer-size

這些批次的大小由storage.buffer-size來控制。 增長storage.buffer-size能夠經過增長緩衝區大小,來使得批次保存更多的請求,從而減小寫請求的次數來避免上述失敗。

注意:

增長緩衝區大小會增長寫請求的等待時間及其失敗的可能性。所以,不建議爲事務性負載增長此設置,而且應該在批量加載期間仔細嘗試此設置的一個合適的值。

二、讀寫健壯性

在批量加載期間,羣集上的負載一般會增長,從而使讀和寫操做失敗的可能性更大(尤爲是如上所述,若是緩衝區大小增長了)。

1)配置項:storage.read-attempts

該配置項配置JanusGraph在放棄以前嘗試對存儲後端執行讀取或寫入操做的次數。

2)配置項:storage.attempt-wait

該配置項指定JanusGraph在從新嘗試失敗的後端操做以前將等待的毫秒數。較高的值能夠確保重試操做不會進一步增長後端的負載。

注意:

若是在批量加載期間後端上可能會有很高的負載,一般建議增長這些配置選項。

其餘

1)配置項:storage.read-attempts

二、第三方存儲後端的優化選項

針對於第三方存儲的優化分爲兩部分:

  • 第三方存儲集羣自身的優化
  • JanusGraph結合第三方存儲的優化選項

集羣自身的優化

集羣自身的優化,本文主要介紹janusgraph相關優化這裏就很少說這部分了,主要是提高hbase集羣的讀寫能力;

這裏主要仍是關注的Hbase的寫數據能力優化後的提高!這部分的優化相當重要! 下面舉幾個例子:

1)配置項: hbase.client.write.buffer

設置buffer的容量

HBase Client會在數據累積到設置的閾值後才提交Region Server。這樣作的好處在於能夠減小RPC鏈接次數。

計算一下服務端所以而消耗的內存:hbase.client.write.buffer * hbase.regionserver.handler.count從在減小PRC次數和增長服務器端內存之間找到平衡點。

2)配置項: hbase.regionserver.handler.count

定義每一個Region Server上的RPC Handler的數量

Region Server經過RPC Handler接收外部請求並加以處理。因此提高RPC Handler的數量能夠必定程度上提升HBase接收請求的能力。

固然,handler數量也不是越大越好,這要取決於節點的硬件狀況。

等等各類配置項

3)針對一些CF、RowKey設計之類的優化點,由於這些都是janus預設好的,因此在janusGraph中使用不到;

JanusGraph針對優化

針對於JanusGraph+第三方存儲的優化,官網(配置項文檔超連接) 給出了一些配置選項,可從其找出對應的配置項;

針對於hbase,我在配置項中找出了對應的一些可能有做用的配置以下:

1)配置項: storage.hbase.compression-algorithm

hbase存儲數據壓縮算法的配置,咱們在《圖解圖庫JanusGraph系列-一文知曉「圖數據「底層存儲結構》文章中提到有好幾個地方都是壓縮存儲的,此處就是配置的壓縮算法;

類型: 枚舉值,支持 lzogzsnappylz4bzip2zstd五種壓縮算法 和 不壓縮配置:none

默認值: gz壓縮;

注意:此處配置的算法須要hbase也支持才能夠! 若是存儲空間足夠,能夠考慮配置爲不壓縮,也會提高導入速率!

2)配置項:storage.hbase.skip-schema-check

假設JanusGraph的HBase表和列族已經存在。 若是是這樣,JanusGraph將不會檢查其 table/ CF 的存在,也不會在任何狀況下嘗試建立它們。

類型: 布爾值

默認值: false,檢查

注意: 能夠在數據導入時,將該配置項設置爲true,去除table/ CF的檢查,這個其實做用不大;由於都是在初始化圖實例的時候就去檢查了。。

三、第三方索引後端的優化選項

針對於第三方存索引的優化分爲兩部分:

  • 第三方索引集羣自身的優化
  • JanusGraph結合第三方索引的優化選項

集羣自身的優化

集羣自身的優化,本文主要介紹janusgraph相關優化這裏就很少說這部分了,主要是提高索引集羣的讀寫能力;

這裏主要仍是關注的索引的寫數據能力優化後的提高!這部分的優化相當重要!

例如es的線程池參數優化等

JanusGraph針對優化

針對於JanusGraph+第三方索引的優化,官網(配置項文檔超連接) 給出了一些配置選項,可從其找出對應的配置項;

針對於es,我在配置項中找出了對應的一些可能有做用的配置以下:

1)配置項: index.[X].elasticsearch.retry_on_conflict

指定在發生衝突時應重試操做多少次。

類型: 整數

默認值: 0次

注意: 增大該值能夠提高在批量導入中,發生衝突後解決衝突的概率

三、JVM的優化

JanusGraph基於Java語言編寫,則毋庸置疑會用到JVM

對JVM的調優也主要集中到垃圾收集器和堆內存的調優

堆大小調整:

咱們在導入圖數據時會產生大量的臨時數據,這裏須要咱們調整一個合適的堆空間;

推薦至少爲8G

垃圾收集器調優:

若是在使用CMS發現GC過於頻繁的話,咱們能夠考慮將垃圾收集器設置爲:G1

這個收集器適用於大堆空間的垃圾收集,有效的減小垃圾收集消耗的時間;

注意:

此處的JVM調優設計JanusGraph java api項目gremlin server部分的JVM調優;

二:基於數據層面的優化

2.1 拆分圖 併發執行

在某些狀況下,圖數據能夠分解爲多個斷開鏈接的子圖。這些子圖能夠跨多臺機器獨立地並行加載;無論是採用下述的那種方式加載均可以;

這裏有一個前提: 底層第三方存儲集羣的處理能力沒有達到最大; 若是底層存儲集羣當前的平均cpu已是80 90%的了,就算拆分多個圖也沒用,底層存儲的處理能力已經被限制住當前的速度了;

這個方式官網上提了一句,這個地方其實很難能夠將圖拆分爲斷開的子圖,而且針對於拆分爲多個子圖來講,主要仍是依託於底層存儲集羣的處理能力;

通常狀況下,不用拆分圖進行一個好的優化後,底層存儲集羣的處理能力均可以徹底調用起來;

2.2 分步驟 併發執行

若是沒法分解圖形,則分多個步驟加載一般是有益的,也就是將vertex 和 edge 分開導入;

這種方式,須要數據同窗作好充分的數據探查,否則可能會產生數據不一致的狀況! 下面是步驟(其中最後兩個步驟能夠在多臺計算機上並行執行):

  1. 前提: 確保vertex和edge數據集 刪除了重複數據 而且是一致的
  2. 環境配置: 設置batch-loading=true 而且優化上述介紹的其餘選項
  3. vertex全量導入: 將全部的vertex及節點對應的property添加到圖中。維護一份從頂點ID(由加載的數據用戶自定義)到JanusGraph的內部頂點分佈式一致性ID(即vertex.getId())的映射,該ID 爲64位長
  4. edge全量導入: 使用映射添加全部的邊 來查找JanusGraph的頂點id 並使用該id檢索頂點。

講述過程:

假設存在3個用戶,「-」號後爲對應的自定義的頂點id值(注意,非導入圖庫中的頂點id,只是標識當前節點的業務id):

user1-1
user2-2
user3-3

上述第三步,咱們將這些節點導入到圖庫中! 產生一個業務id 與 圖庫中節點的分佈式惟一id的對應關係以下:

咱們在導入一個點後,janus會返回一個vertex實例對象,經過這個對象就能夠拿到對應的圖庫vertexId

業務id-圖庫中節點id
1-4261
2-4274
3-4351

注意:這一步驟,咱們能夠多線程並行導入而無需擔憂一致性問題,由於節點所有惟一

節點導入完成!

假設存在對應的有3條邊以下,

edge1:user1 --> user2 
edge2:user1 --> user3
edge3:user2 --> user3

咱們經過user1對應業務id:1,而業務id:1對應節點id:4261,咱們就能夠轉化爲下述對應關係:

4261 --> 4274
4261 --> 4351
4274 --> 4351

在JanusGraph中經過節點id查詢節點,是獲取節點的最快方式!!

咱們就能夠經過id獲取圖庫中對應的vertex對象實例,而後使用addVertex將edge導入!

注意:這一步驟,咱們能夠多線程並行導入而無需擔憂一致性問題,由於edge所有惟一

第三個步驟和第四個步驟也能夠並行執行,咱們在導入點的過程當中,能夠也同時將源節點和目標節點已經導入到圖庫中的edge同步入圖;

三:批量導入方案

下述介紹一下5種導入方案,其中包含3種批量導入方案;

3.1 方案一:基於JanusGraph Api的數據導入

該方案能夠整合上述第二部分二:基於數據層面的優化

涉及方法:

public JanusGraphVertex addVertex(Object... keyValues);
public JanusGraphEdge addEdge(String label, Vertex vertex, Object... keyValues);

在janusGraph的業務項目中,能夠開發一個數據導入模塊,使用提供的相似於java api等,進行數據的導入;

流程:

這種是最簡單的方案,具體的細節,這裏就不給出了,節點導入大致流程爲下述:

  1. 獲取圖實例
  2. 獲取圖實例事務對象
  3. 插入節點
  4. 提交事務

邊導入大致流程以下:

  1. 獲取圖實例
  2. 獲取圖實例事務對象
  3. 查詢源節點 + 目標節點(這個地方多是性能瓶頸)
  4. 在兩個節點中插入邊
  5. 提交事務

主要做用:

此方案能夠用於數據量較小的狀況下使用,例如天天的增量導入等;

優化點:

一、批量事務提交

此處的事務提交,咱們能夠經過一個經常使用的優化手段: 處理多個vertex 或者 edge後再提交事務!

能夠減小janus與底層存儲的交互,減小網絡消耗和鏈接數,提高導入的性能!

處理的個數多少主要仍是和底層存儲集羣相關,幾百仍是幾千這就須要本身調試獲取當前環境下的最優配置了

注意:

若是開啓了上述提到的storage.batch-loading,則須要大家如今的環境下注意一致性的問題;

例如圖庫中本來存在一個a節點,你又插入一個a節點,便會有一致性問題;

咱們能夠經過插入數據前,先經過惟一索引查詢節點,節點存在則更新節點,不存在則插入節點;

3.2 方案二:基於Gremlin Server的批量導入

該方案能夠整合上述第二部分二:基於數據層面的優化

這裏須要咱們搭建一個Gremlin server服務器,經過在服務器執行gremlin-server.sh便可,暴露出一個tcp接口;

則能夠將對應的gremlin 語句提交到對應的gremlin服務器執行;

具體的流程和第一個方案一致

優化點:

同上一個方案優化點1;

三、gremlin server池參數調整

除了上述給定的一些配置的優化項,還有兩個gremlin server的優化項須要調整

  • threadPoolWorke:最大2*core個數,用於處理非阻塞讀寫的Gremlin服務器可用的線程數;

  • gremlinPool:用於在ScriptEngine中執行實際腳本的「Gremlin」線程的數量。此池表示Gremlin服務器中可用於處理阻塞操做的工做者;

和線程池調優同樣,要找出最合適的一個值,過小很差,太大也很差;

注意:

該方案本質上和第一個方案相似,只不過是一個是經過給定的java api提交插入請求,一個直接經過gremlin語句提交插入請求到gremlin server;

3.3 方案三:IBM的janusgraph-utils

這個方案沒用過,簡單看了一下,這個主要也是經過多線程對數據進行導入;

本身手動組裝對應的schema文件,將schema導入到數據庫;

而後將組裝爲特定格式的csv文件中的數據,導入到圖庫中;

github地址: https://github.com/IBM/janusgraph-utils

優勢:

一、使用難度不高,讓咱們不用再去手寫多線程的導入了;減小工做量

二、直連hbase和es,相對於前兩種減小了對應的gremlin server和janus server的網絡交互

三、支持經過配置文件自動建立Janusgraph schema和index

四、可配置化的線程池大小和每次批量提交的數量

問題:

一、schema和csv文件也是要用戶組裝出對應格式

二、相對於前兩種方式性能提高有限,主要是少了一層網絡交互。多線程和批量提交,前兩種均可以手動去實現;還須要引入一個新的組件

三、支持janus版本較低,能夠手動升級,不難

四、相對於下面兩種方案,性能仍是較低

3.4 方案四:bulk loader

官方提供的批量導入方式;須要hadoop集羣和spark集羣的支持;

hadoop和spark集羣配置,能夠看官網:https://docs.janusgraph.org/advanced-topics/hadoop/

該方案對導入的數據有着嚴格的要求,支持多種數據格式:jsoncsvxmlkryo

數據要求: 節點、節點對應的屬性、節點對應的邊須要在一行中(一個json中、一個xml項中)

數據案例: 下面給一下官網的案例,在data目錄下:

-- json格式
{"id":2,"label":"song","inE":{"followedBy":[{"id":0,"outV":1,"properties":{"weight":1}},{"id":323,"outV":34,"properties":{"weight":1}}]},"outE":{"followedBy":[{"id":6190,"inV":123,"properties":{"weight":1}},{"id":6191,"inV":50,"properties":{"weight":1}}],"sungBy":[{"id":7666,"inV":525}],"writtenBy":[{"id":7665,"inV":525}]},"properties":{"name":[{"id":3,"value":"IM A MAN"}],"songType":[{"id":5,"value":"cover"}],"performances":[{"id":4,"value":1}]}}

-- xml格式
<node id="4"><data key="labelV">song</data><data key="name">BERTHA</data><data key="songType">original</data><data key="performances">394</data></node><node id="5"><data key="labelV">song</data><data key="name">GOING DOWN THE ROAD FEELING BAD</data><data key="songType">cover</data><data key="performances">293</data></node><node id="6"><data key="labelV">song</data><data key="name">MONA</data><data key="songType">cover</data><data key="performances">1</data></node><node id="7"><data key="labelV">song</data><data key="name">WHERE HAVE THE HEROES GONE</data><data key="songType"></data><data key="performances">0</data></node>

-- csv格式
2,song,IM A MAN,cover,1 followedBy,50,1|followedBy,123,1|sungBy,525|writtenBy,525       followedBy,1,1|followedBy,34,1

咱們能夠觀察到,這實際上是不容易構造的,節點屬性邊所有須要整合到一塊;

數據整理方案: spark的cogroup, cogroup的做用就是將多個 RDD將相同的key jion成一行,從而使用csv格式進行導入,操做實示例以下:

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()))

這裏你們能夠參考360對這方面的處理,轉化代碼github地址:https://github.com/360jinrong/janusgraph-data-importer

注意:

此處的原始數據的準備須要細緻,一致性保證徹底依賴於原始數據的一致性保證;

3.5 方案五:基於抽取序列化邏輯的生成Hbase File離線批量導入

博主在圖庫初始化時採用了這種方式,前先後後花費了接近一個月的時間,通過細緻的驗證,現已應用到生產環境使用,下面介紹一下對應的注意點和主要流程:

方案: 依據源碼抽取出對應的序列化邏輯,分佈式生成Hfile,將Hfile導入到Hbase;

問題: 人力成本太高,須要看源碼抽邏輯,而且須要一個細緻的驗證;

方案難點:

JanusGraph對於Hbase的數據底層格式,能夠看我寫的博客:

這兩篇博客,一個分析了底層存儲的格式,一個進行了相應的源碼分析;

流程+驗證+建議: 請看我寫的另一個博客:《圖解JanusGraph系列-生成Hbase file離線批量導入方案》

這種方式,其實消耗的人力會比較大;另外,對於抽取的邏輯是否開源,這個後續咱們會考慮這個問題,開源後地址會同步更新到本文章;

四:幾種場景

4.1 圖庫中已經存在數據

若是圖庫中已經存在數據,對於3.4 方案四:bulk loader3.5 方案五:基於抽取序列化邏輯的生成Hbase File離線批量導入 這兩種方案可能就沒法使用了;

咱們能夠採起兩種方式:

  1. 使用第一種方案和第二種方案進行導入(注意數據一致性)
  2. 總體遷移圖庫,將圖庫中現有數據和將要導入的數據總體遷移到另一個新圖庫,就可使用四、5方案進行導入

4.2 圖數據初始化或者遷移

數據量小,建議使用3.1 方案一:基於JanusGraph Api的數據導入3.2 方案二:基於Gremlin Server的批量導入3.3 方案三:IBM的janusgraph-utils

數據量大,建議使用3.4 方案四:bulk loader3.5 方案五:基於抽取序列化邏輯的生成Hbase File離線批量導入

4.3 單純只看業務數據量

選擇什麼方式導入,單純基於業務數據量給一些我的建議:

  • 小數據量(億級如下): 直接janusgraph api 或者 gremlin server導入便可,幾小時就ok了; 若是想要更快可使用另外的方式,只是會增長人力成本;
  • 中等數據量(十億級如下):數據充分探查,開啓storage.batch-loading徹底能夠支持,使用api,2天左右能夠完成全量的數據導入
  • 大數據量(百億級數據):推薦採用bulk load方式,配置hadoop集羣,使用spark cluster導入
  • 另外一個方案:若是上述仍是沒法知足大家的需求,能夠採用依據源碼抽取序列化邏輯生成Hfile,而後離線導入到Hbase的方案,不過這種是花費人力成本最大的一種方式,不過效果也幾乎是最好的,尤爲是數據量越大效果越明顯

總結

數據的批量導入一直是JanusGraph讓人難受的地方,通過本文的介紹你們應該有一個大致的認識,針對於百億級的數據導入,上述的幾種方案是能夠支持的;

其餘:批量導入後,天天的增量採用消息中間件接入JanusGraph api導入便可;

數據導入過程當中,針對於不一樣的底層存儲、不一樣的版本仍是會有一些問題,具體的導入的坑你們能夠加我v,邀你加羣

注意!!!以上僅做爲參考,有任何問題可評論或加博主v討論

參考:
JanusGaph官網
https://www.jianshu.com/p/f372f0ef6c42
https://www.jianshu.com/p/4b59c00a15de/

相關文章
相關標籤/搜索