快速導(dǎo)入十億數(shù)據(jù)到hugegraph圖數(shù)據(jù)庫

在前面學(xué)習(xí)了《快速入門hugegraph圖數(shù)據(jù)庫》和《hugegraph圖數(shù)據(jù)庫概念詳解》之后私杜,大家一定想導(dǎo)入一定規(guī)模的真實(shí)數(shù)據(jù)到hugegraph練練手,本文就以Stanford的公開數(shù)據(jù)為例整以,教大家如何快速導(dǎo)入10億+的數(shù)據(jù)到hugegraph圖數(shù)據(jù)庫。

1. 環(huán)境準(zhǔn)備

導(dǎo)入數(shù)據(jù)到hugegraph之前需要準(zhǔn)備好一些必要環(huán)境玷犹,包括:安裝服務(wù)hugegraph-server和下載導(dǎo)入工具hugegraph-loader隔披,請讀者先根據(jù)文檔安裝hugegraph-server,下載hugegraph-loader援岩,hugegraph-server和hugegraph-loader在同一臺(tái)機(jī)器即可歼狼。

2. 數(shù)據(jù)準(zhǔn)備

2.1 原始數(shù)據(jù)下載

本文以Stanford的公開數(shù)據(jù)集Friendster為例掏导,該數(shù)據(jù)約31G左右享怀,請大家自行去下載該數(shù)據(jù)。

下載完之后趟咆,我們來看看文件內(nèi)容是什么樣的添瓷。

前10行

$ head -10 com-friendster.ungraph.txt
# Undirected graph: ../../data/output/friendster.txt
# Friendster
# Nodes: 65608366 Edges: 1806067135
# FromNodeId    ToNodeId
101 102
101 104
101 107
101 125
101 165
101 168

后10行

$ tail -10 com-friendster.ungraph.txt
124802963   124804978
124802963   124814064
124804978   124805174
124804978   124805533
124804978   124814064
124805174   124805533
124806381   124806684
124806596   124809830
124814064   124829667
124820359   124826374

可以看到,文件結(jié)構(gòu)很簡單值纱,每一行代表一條邊鳞贷,其包含兩列,第一列是源頂點(diǎn)Id虐唠,第二列是目標(biāo)頂點(diǎn)Id搀愧,兩列之間以\t分隔。另外,文件最上面幾行是一些概要信息咱筛,它說明了文件共有65608366個(gè)頂點(diǎn)搓幌,1806067135條邊(行)。而且從文件的前面10行和頂點(diǎn)數(shù)中都可以看出迅箩,這1806067135行中有很多頂點(diǎn)是重復(fù)出現(xiàn)的溉愁。當(dāng)然,這是由于文件本身無法描述圖結(jié)構(gòu)導(dǎo)致的饲趋。

了解過hugegraph-loader的讀者應(yīng)該知道拐揭,hugegraph-loader暫時(shí)不支持在讀一次文件的時(shí)候既導(dǎo)入頂點(diǎn)又導(dǎo)入邊,所以我們需要對邊文件做一下處理奕塑,將所有的頂點(diǎn)Id去重后堂污,輸出到一個(gè)單獨(dú)的頂點(diǎn)文件里面,這樣hugegraph-loader就可以分別導(dǎo)入頂點(diǎn)和邊了龄砰。

2.2 數(shù)據(jù)處理

這里數(shù)據(jù)處理的關(guān)鍵在于去重敷鸦,在不考慮數(shù)據(jù)量的情況下,我們可以按照以下步驟去重并寫入到新文件:

  • 定義一個(gè)內(nèi)存的set容器寝贡,便于判斷某個(gè)Id是否存在
  • 按行讀取源文件扒披,每一行解析出兩個(gè)整型Id
  • 對每個(gè)Id,先判斷set容器中是否包含它圃泡,如果不包含碟案,則加入到容器,并寫入到新文件中

依靠內(nèi)存的set容器我們就能實(shí)現(xiàn)去重颇蜡,這是數(shù)據(jù)處理的核心思想价说。但是有一個(gè)問題需要考慮到,那就是set容器是否足夠放下所有不重復(fù)的頂點(diǎn)Id风秤,我們可以計(jì)算一下:

// 65608366個(gè)頂點(diǎn)Id
// 每個(gè)頂點(diǎn)Id是整型鳖目,即32字節(jié)
(65608366 * 32) / (1024 * 1024 * 1024) = 1.9G

很幸運(yùn),目前絕大多數(shù)的機(jī)器的內(nèi)存都是能放得下1.9G的數(shù)據(jù)的缤弦,除非你已經(jīng)十幾年沒有換過電腦了领迈,所以大家可以自己寫一個(gè)腳本按照我上面的邏輯快速地實(shí)現(xiàn)去重。

不過碍沐,我下面還是給大家介紹一種更加通用一點(diǎn)的處理方案狸捅,以免下一次換了一個(gè)數(shù)據(jù)集,而那個(gè)數(shù)據(jù)集的頂點(diǎn)Id占的內(nèi)存是3.9G累提、5.9G或7.9G尘喝,這時(shí),估計(jì)就有一部分人的機(jī)器裝不下了斋陪。

下面我要介紹的這種方案在處理海量數(shù)據(jù)領(lǐng)域頗為常見朽褪,其核心思想是分而治之:

  • 將原始的全部頂點(diǎn)Id分成較均勻的若干份置吓,保證在每份之間沒有重復(fù)的,在每份內(nèi)部允許有重復(fù)的缔赠;
  • 對每一份文件交洗,應(yīng)用上面的去重方法。

那如何才能將全部頂點(diǎn)Id分成較均勻的若干份呢橡淑?由于頂點(diǎn)Id都是連續(xù)的數(shù)字构拳,我們可以做求余哈希,將所有余數(shù)相同的頂點(diǎn)Id寫到一個(gè)文件中梁棠。比如我們決定分成10份置森,那可以創(chuàng)建編號為0-9的10個(gè)文件,將所有頂點(diǎn)Id除以10求余符糊,余數(shù)為0的寫到編號為0的文件凫海,余數(shù)為1的寫到編號為1的文件,以此類推男娄。

我已經(jīng)按照上面的邏輯寫好了腳本行贪,代碼如下:

#!/usr/bin/python
# coding=utf-8


def ensure_file_exist(shard_file_dict, shard_prefix, index):
    if not (shard_file_dict.has_key(index)):
        name = shard_file_path + shard_prefix + str(index)
        shard_file = open(name, "w")
        shard_file_dict[index] = shard_file

if __name__ == '__main__':

    raw_file_path = "path/raw_file.txt"
    output_file_path = "path/de_dup.txt"
    shard_file_path = "path/shard/"
    shard_prefix = "shard_"
    shard_count = 100
    shard_file_dict = {}

    # Split into many shard files
    with open(raw_file_path, "r+") as raw_file:
        # Read next line
        for raw_line in raw_file:
            # Skip comment line
            if raw_line.startswith('#'):
                continue
            parts = raw_line.split('\t')
            assert len(parts) == 2

            source_node_id = int(parts[0])
            target_node_id = int(parts[1])
            # Calculate the residue by shard_count
            source_node_residue = source_node_id % shard_count
            target_node_residue = target_node_id % shard_count

            # Create new file if it doesn't exist
            ensure_file_exist(shard_file_dict, shard_prefix, source_node_residue)
            ensure_file_exist(shard_file_dict, shard_prefix, target_node_residue)

            # Append to file with corresponding index
            shard_file_dict[source_node_residue].write(str(source_node_id) + '\n')
            shard_file_dict[target_node_residue].write(str(target_node_id) + '\n')

    print "Split original file info %s shard files" % shard_count

    # Close all files
    for shard_file in shard_file_dict.values():
        shard_file.close()

    print "Prepare duplicate and merge shard files into %s" % output_file_path
    merge_file = open(output_file_path, "w")
    line_count = 0

    # Deduplicate and merge into another file
    for index in shard_file_dict.keys():
        name = shard_file_path + shard_prefix + str(index)
        with open(name, "r+") as shard_file:
            elems = {}
            # Read next line
            for raw_line in shard_file:
                # Filter duplicate elems
                if not elems.has_key(raw_line):
                    elems[raw_line] = ""
                    merge_file.write(raw_line)
                    line_count += 1
        print "Processed shard file %s" % name

    merge_file.close()
    print "Processed all shard files and merge into %s" % merge_file
    print "%s lines after processing the file" % line_count

    print "Finished"

在使用這個(gè)腳本之前,需要修改raw_file_path模闲、output_file_path建瘫、shard_file_path為你自己路徑。

處理完之后尸折,我們再看看去重后的頂點(diǎn)文件

$ head -10 com-friendster.ungraph.vertex.txt
1007000
310000
1439000
928000
414000
1637000
1275000
129000
2537000
5356000

看一下文件有多少行

$ wc -l com-friendster.ungraph.vertex.txt
65608366 com-friendster.ungraph.vertex.txt

可以看到啰脚,確實(shí)是與文件描述相符的。

除了我說的這種方法外实夹,肯定還有其他的處理辦法橄浓,比如大數(shù)據(jù)處理神器:MapReduce,大家可以自行選擇亮航,只要能提取頂點(diǎn)Id并去重就行荸实。

3. 導(dǎo)入準(zhǔn)備

3.1 構(gòu)建圖模型

由于頂點(diǎn)和邊除了Id外,都沒有其他的屬性缴淋,所以圖的schema其實(shí)很簡單准给。

schema.propertyKey("id").asInt().ifNotExist().create();
// 使用Id作為主鍵
schema.vertexLabel("person").primaryKeys("id").properties("id").ifNotExist().create();
schema.edgeLabel("friend").sourceLabel("person").targetLabel("person").ifNotExist().create();

3.2 編寫輸入源映射文件

這里只有一個(gè)頂點(diǎn)文件和邊文件,且文件的分隔符都是\t宴猾,所以將input.format指定為TEXT圆存,input.delimiter使用默認(rèn)即可。

頂點(diǎn)有一個(gè)屬性id仇哆,而頂點(diǎn)文件頭沒有指明列名,所以我們需要顯式地指定input.header["id"]夫植,input.header的作用是告訴hugegraph-loader文件的每一列的列名是什么讹剔,但要注意:列名并不一定就是頂點(diǎn)或邊的屬性名油讯,描述文件中有一個(gè)mapping域用來將列名映射為屬性名。

邊沒有任何屬性延欠,邊文件中只有源頂點(diǎn)和目標(biāo)頂點(diǎn)的Id陌兑,我們需要先將input.header指定為["source_id", "target_id"],這樣就給兩個(gè)Id列取了不同的名字由捎。然后再分別指定sourcetarget["source_id"]["target_id"]兔综,sourcetarget的作用是告訴hugegraph-loader邊的源頂點(diǎn)和目標(biāo)頂點(diǎn)的Id與文件中的哪些列有關(guān)。

注意這里“有關(guān)”的含義狞玛。當(dāng)頂點(diǎn)Id策略是PRIMARY_KEY時(shí)软驰,sourcetarget指定的列是主鍵列(加上mapping),用來拼接生成頂點(diǎn)Id心肪;當(dāng)頂點(diǎn)Id策略是CUSTOMIZE_STRINGCUSTOMIZE_NUMBER時(shí)锭亏,sourcetarget指定的列就是Id列(加上mapping)。

由于這里頂點(diǎn)Id策略是PRIMARY_KEY的硬鞍,所以sourcetarget指定的列["source_id"]["target_id"]將作為主鍵列慧瘤,再在mapping域中指定source_idtarget_idid,hugegraph-loader就知道解析道一個(gè)source_id列的值value后固该,將其解釋為id:value锅减,然后使用頂點(diǎn)Id拼接算法生成源頂點(diǎn)Id(目標(biāo)頂點(diǎn)類似)。

{
  "vertices": [
    {
      "label": "person",
      "input": {
        "type": "file",
        "path": "path/com-friendster.ungraph.vertex.txt",
        "format": "TEXT",
        "header": ["id"],
        "charset": "UTF-8"
      }
    }
  ],
  "edges": [
    {
      "label": "friend",
      "source": ["source_id"],
      "target": ["target_id"],
      "input": {
        "type": "file",
        "path": "path/com-friendster.ungraph.txt",
        "format": "TEXT",
        "header": ["source_id", "target_id"],
        "comment_symbols": ["#"]
      },
      "mapping": {
        "source_id": "id",
        "target_id": "id"
      }
    }
  ]
}  

由于邊文件中前面幾行是注釋行伐坏,可以使用"comment_symbols": ["#"]令hugegraph-loader忽略以#開頭的行上煤。

更多關(guān)于映射文件的介紹請參考:官網(wǎng)hugegraph-loader編寫輸入源映射文件

4. 執(zhí)行導(dǎo)入

進(jìn)入到hugegraph-loader目錄下,執(zhí)行以下命令(記得修改路徑):

$ bin/hugegraph-loader -g hugegraph -f ../data/com-friendster/struct.json -s ../data/com-friendster/schema.groovy --check-vertex false

這時(shí)hugegraph-loader就會(huì)開始導(dǎo)入數(shù)據(jù)著淆,并會(huì)打印進(jìn)度到控制臺(tái)上劫狠,等所有頂點(diǎn)和邊導(dǎo)入完成后,會(huì)看到以下統(tǒng)計(jì)信息:

Vertices has been imported: 65608366
Edges has been imported: 1806067135
---------------------------------------------
vertices results:
    parse failure vertices   :  0
    insert failure vertices  :  0
    insert success vertices  :  65608366
---------------------------------------------
edges results:
    parse failure edges      :  0
    insert failure edges     :  0
    insert success edges     :  1806067135
---------------------------------------------
time results:
    vertices loading time    :  200
    edges loading time       :  8089
    total loading time       :  8289

頂點(diǎn)和邊的導(dǎo)入速度分別為:65608366 / 200 = 328041.83(頂點(diǎn)/秒)永部,1806067135 / 8089 = 223274.46(邊/秒)独泞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市苔埋,隨后出現(xiàn)的幾起案子懦砂,更是在濱河造成了極大的恐慌,老刑警劉巖组橄,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荞膘,死亡現(xiàn)場離奇詭異,居然都是意外死亡玉工,警方通過查閱死者的電腦和手機(jī)羽资,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遵班,“玉大人屠升,你說我怎么就攤上這事潮改。” “怎么了腹暖?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵汇在,是天一觀的道長。 經(jīng)常有香客問我脏答,道長糕殉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任殖告,我火速辦了婚禮阿蝶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘丛肮。我一直安慰自己赡磅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布宝与。 她就那樣靜靜地躺著焚廊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪习劫。 梳的紋絲不亂的頭發(fā)上咆瘟,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音诽里,去河邊找鬼袒餐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛谤狡,可吹牛的內(nèi)容都是我干的灸眼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼墓懂,長吁一口氣:“原來是場噩夢啊……” “哼焰宣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捕仔,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤匕积,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后榜跌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闪唆,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年钓葫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悄蕾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瓤逼,死狀恐怖笼吟,靈堂內(nèi)的尸體忽然破棺而出库物,到底是詐尸還是另有隱情霸旗,我是刑警寧澤贷帮,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站诱告,受9級特大地震影響撵枢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜精居,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一锄禽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧靴姿,春花似錦沃但、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至维雇,卻和暖如春淤刃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吱型。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工逸贾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人津滞。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓铝侵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親触徐。 傳聞我的和親對象是個(gè)殘疾皇子咪鲜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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