作者:霖霧,攜程數(shù)據(jù)開發(fā)工程師鲫懒,關(guān)注圖數(shù)據(jù)庫等領(lǐng)域嫩实。
0. 背景
2017 年 9 月攜程金融成立,在金融和風(fēng)控業(yè)務(wù)中窥岩,有多種場(chǎng)景需要對(duì)圖關(guān)系網(wǎng)絡(luò)進(jìn)行分析和實(shí)時(shí)查詢甲献,傳統(tǒng)關(guān)系型數(shù)據(jù)庫難以保證此類場(chǎng)景下的關(guān)聯(lián)性能,且實(shí)現(xiàn)復(fù)雜性高颂翼,離線關(guān)聯(lián)耗時(shí)過長(zhǎng)晃洒,因此對(duì)圖數(shù)據(jù)庫的需求日益增加。攜程金融從 2020 年開始引入大規(guī)模圖存儲(chǔ)和圖計(jì)算技術(shù)朦乏,基于 NebulaGraph 構(gòu)建了千億級(jí)節(jié)點(diǎn)的圖存儲(chǔ)和分析平臺(tái)球及,并取得了一些實(shí)際應(yīng)用成果。
本文主要分享 NebulaGraph 在攜程金融的實(shí)踐呻疹,希望能帶給大家一些實(shí)踐啟發(fā)吃引。
本文主要從以下幾個(gè)部分進(jìn)行分析:
- 圖基礎(chǔ)介紹
- 圖平臺(tái)建設(shè)
- 內(nèi)部應(yīng)用案例分析
- 痛點(diǎn)與優(yōu)化
- 總結(jié)規(guī)劃
1. 圖基礎(chǔ)
首先我們來簡(jiǎn)單介紹下圖相關(guān)的概念:
1.1 什么是圖
在計(jì)算機(jī)科學(xué)中,圖就是一些頂點(diǎn)的集合,這些頂點(diǎn)通過一系列邊結(jié)對(duì)(連接)镊尺。比如我們用一個(gè)圖表示社交網(wǎng)絡(luò)朦佩,每一個(gè)人就是一個(gè)頂點(diǎn),互相認(rèn)識(shí)的人之間通過邊聯(lián)系庐氮。
在圖數(shù)據(jù)庫中吕粗,我們使用(起點(diǎn),邊類型,rank,終點(diǎn))表示一條邊。起點(diǎn)和終點(diǎn)比較好理解旭愧,表示一條邊兩個(gè)頂點(diǎn)的出入方向颅筋。邊類型則是用于區(qū)分異構(gòu)圖的不同邊,如我關(guān)注了你输枯,我向你轉(zhuǎn)賬议泵,關(guān)注和轉(zhuǎn)賬就是兩種不同種類的邊。而 rank 是用來區(qū)分同起始點(diǎn)同終點(diǎn)的不同邊桃熄,如 A 對(duì) B 的多次轉(zhuǎn)賬記錄先口,起點(diǎn)、終點(diǎn)瞳收、邊類型是完全相同的碉京,因此就需要如時(shí)間戳作為 rank 來區(qū)分不同的邊。
同時(shí)螟深,點(diǎn)邊均可具有屬性谐宙,如:A 的手機(jī)號(hào)、銀行卡界弧、身份證號(hào)凡蜻、籍貫等信息均可作為 A 的點(diǎn)屬性存在,A 對(duì) B 轉(zhuǎn)賬這條邊垢箕,也可以具有屬性划栓,如轉(zhuǎn)賬金額,轉(zhuǎn)賬地點(diǎn)等邊屬性条获。
1.2 什么時(shí)候用圖
(信息收集于開源社區(qū)帅掘、公開技術(shù)博客委煤、文章、視頻)
1.2.1 金融風(fēng)控
- 詐騙電話的特征提取锄开,如不在三步社交鄰居圈內(nèi)素标,被大量拒接等特征。實(shí)時(shí)識(shí)別攔截萍悴。(銀行 / 網(wǎng)警等)
- 轉(zhuǎn)賬實(shí)時(shí)攔截(銀行 / 支付寶等)
- 實(shí)時(shí)欺詐檢測(cè)头遭,羊毛黨的識(shí)別(電商)
- 黑產(chǎn)群體識(shí)別寓免,借貸記錄良好用戶關(guān)聯(lián),為用戶提供更高額貸款计维、增加營(yíng)收
1.2.2 股權(quán)穿透
影子集團(tuán)袜香、集團(tuán)客戶多層交叉持股、股權(quán)層層嵌套復(fù)雜關(guān)系的識(shí)別(天眼查 / 企查查)
1.2.3 數(shù)據(jù)血緣
在數(shù)據(jù)倉庫開發(fā)過程中鲫惶, 會(huì)因?yàn)閿?shù)據(jù)跨表關(guān)聯(lián)產(chǎn)生大量的中間表蜈首,使用圖可直接根據(jù)關(guān)系模型表示出數(shù)據(jù)加工過程和數(shù)據(jù)流向,以及在依賴任務(wù)問題時(shí)快速定位上下游欠母。
1.2.4 知識(shí)圖譜
構(gòu)建行業(yè)知識(shí)圖譜欢策。
1.2.5 泛安全
IP 關(guān)系等黑客攻擊場(chǎng)景,計(jì)算機(jī)進(jìn)程與線程等安全管理赏淌。
1.2.6 社交推薦
- 好友推薦踩寇,行為相似性,咨詢傳播路徑六水,可能認(rèn)識(shí)的人俺孙,大V 粉絲共同關(guān)注,共同閱讀文章等掷贾,商品相似性睛榄,實(shí)現(xiàn)好友商品或者咨詢的精準(zhǔn)推薦;
- 通過對(duì)用戶畫像想帅、好友關(guān)系等场靴,進(jìn)行用戶分群、實(shí)現(xiàn)用戶群體精準(zhǔn)管理博脑;
1.2.7 代碼依賴分析
分析代碼依賴關(guān)系憎乙。
1.2.8 供應(yīng)鏈上下游分析
如:汽車供應(yīng)鏈上下游可涉及上萬零件及供應(yīng)商,分析某些零件成本上漲 / 供應(yīng)商單一 / 庫存少等多維度的影響叉趣。(捷豹)
1.3 誰在研發(fā)圖,誰在使用圖
(信息收集于開源社區(qū)该押、公開技術(shù)博客疗杉、文章、視頻)
目前國(guó)內(nèi)幾家大公司都有各自研發(fā)的圖數(shù)據(jù)庫蚕礼,主要滿足內(nèi)部應(yīng)用的需求烟具,大多數(shù)都是閉源的,開源的僅有百度的 HugeGraph奠蹬。其他比較優(yōu)秀的開源產(chǎn)品有 Google Dgraph朝聋,vesoft 的 NebulaGraph 等,其中 NebulaGraph 在國(guó)內(nèi)互聯(lián)網(wǎng)公司應(yīng)用非常廣泛囤躁。結(jié)合我們的應(yīng)用場(chǎng)景冀痕,以及外部公開的測(cè)試和內(nèi)部壓測(cè)荔睹,我們最終選擇 NebulaGraph 構(gòu)建金融圖平臺(tái)。
2. 圖平臺(tái)建設(shè)
2.1 圖平臺(tái)建設(shè)
我們的圖平臺(tái)早期只有 1 個(gè) 3 節(jié)點(diǎn)的 Nebula 集群吨拗。隨著圖應(yīng)用場(chǎng)景的不斷擴(kuò)充,需要滿足實(shí)時(shí)檢索婿斥、離線分析劝篷、數(shù)據(jù)同步與校驗(yàn)等功能,最終演化成上述架構(gòu)圖民宿。
2.1.1 離線圖
主要用于圖構(gòu)建階段(建模娇妓、圖算法分析),通過 spark-connector 同集團(tuán)的大數(shù)據(jù)平臺(tái)打通勘高,此外我們還將 NebulaGraph 提供的數(shù) 10 種常用圖算法進(jìn)行工具化包裝峡蟋,方便圖分析人員在 Spark 集群提交圖算法作業(yè)。
2.1.2 線上圖
經(jīng)過離線圖分析確定最終建模后华望,會(huì)通過 spark-connector 將數(shù)據(jù)導(dǎo)入線上圖蕊蝗。通過對(duì)接 qmq 消息(集團(tuán)內(nèi)部的消息框架)實(shí)時(shí)更新,對(duì)外提供實(shí)時(shí)檢索服務(wù)赖舟。同時(shí)也會(huì)有 T+1 的 HIVE 增量數(shù)據(jù)通過 spark-connector 按天寫入蓬戚。
2.1.3 全量校驗(yàn)
雖然 NebulaGraph 通過 TOSS 保證了正反邊的插入一致性,但仍不支持事務(wù)宾抓,隨著數(shù)據(jù)持續(xù)更新子漩,實(shí)時(shí)圖和離線(HIVE 數(shù)據(jù))可能會(huì)存在不一致的情況,因此我們需要定期進(jìn)行全量數(shù)據(jù)的校驗(yàn)(把圖讀取到 Hive石洗,和 Hive 表存儲(chǔ)的圖數(shù)據(jù)進(jìn)行比對(duì)幢泼,找出差異、修復(fù))讲衫,保證數(shù)據(jù)的最終一致性缕棵。
2.1.4 集群規(guī)模
為了滿足千億節(jié)點(diǎn)的圖業(yè)務(wù)需求,實(shí)時(shí)集群采用三臺(tái)獨(dú)立部署的高性能機(jī)器涉兽,每臺(tái)機(jī)器 64 core / 320 GB / 12 TB SSD 招驴,版本為 Nebula v2.5,跨機(jī)房部署枷畏。離線集群 64 core / 320 GB / 3.6 TB SSD * 12 别厘,測(cè)試集群 48 core / 188 GB / 5T HDD * 4.
2.2 遇到的問題
在 NebulaGraph 應(yīng)用過程中,也發(fā)現(xiàn)一些問題拥诡,期待逐步完善:
- 資源隔離問題触趴,目前 Nebula 沒有資源分組隔離功能氮发,不同業(yè)務(wù)會(huì)相互影響;如業(yè)務(wù)圖 A 在導(dǎo)數(shù)據(jù)雕蔽,業(yè)務(wù)圖 B 線上延遲就非常高折柠。
- 版本升級(jí)問題:
- NebulaGraph 在版本升級(jí)過程中需要停止服務(wù),無法實(shí)現(xiàn)熱更新批狐;對(duì)于類似實(shí)時(shí)風(fēng)控等對(duì)可靠性要求非常高的場(chǎng)景非常不友好扇售。此種情況下如需保證在線升級(jí),就需要配備主備集群嚣艇,每個(gè)集群切量后挨個(gè)升級(jí)承冰,增加服務(wù)復(fù)雜性和運(yùn)維成本。
- 客戶端不兼容食零,客戶端需要跟著服務(wù)端一起升級(jí)版本困乒。對(duì)于已有多個(gè)應(yīng)用使用的 Nebula 集群,想要協(xié)調(diào)各應(yīng)用方同時(shí)升級(jí)客戶端是比較困難的贰谣。
3. 內(nèi)部應(yīng)用案例分析
3.1 數(shù)據(jù)血緣圖
數(shù)據(jù)治理是近年來比較熱的一個(gè)話題娜搂,他是解決數(shù)倉無序膨脹的有效手段,其中數(shù)據(jù)血緣是數(shù)據(jù)有效治理的重要依據(jù)吱抚,攜程金融借助 NebulaGraph 構(gòu)建了數(shù)據(jù)血緣圖百宇,以支撐數(shù)據(jù)治理的系統(tǒng)建設(shè)。
數(shù)據(jù)血緣就是數(shù)據(jù)產(chǎn)生的鏈路啄刹,記錄數(shù)據(jù)加工的流向,經(jīng)過了哪些過程和階段凄贩;主要解決 ETL 過程中可能產(chǎn)出幾十甚至幾百個(gè)中間表導(dǎo)致的復(fù)雜表關(guān)系誓军,借用數(shù)據(jù)血緣可以清晰地記錄數(shù)據(jù)源頭到最終數(shù)據(jù)的生成過程。
圖 a 是數(shù)據(jù)血緣的關(guān)系圖疲扎,采用庫名 + 表名作為圖的頂點(diǎn)來保證點(diǎn)的唯一性谭企,點(diǎn)屬性則是分開的庫名和表名,以便通過庫名或者表名進(jìn)行屬性查詢评肆。在兩張表之間會(huì)建立一條邊,邊的屬性主要存放任務(wù)的產(chǎn)生運(yùn)行情況非区,比如說:任務(wù)開始時(shí)間瓜挽,結(jié)束時(shí)間、用戶 ID 等等同任務(wù)相關(guān)的信息征绸。
圖 b 是實(shí)際查詢中的一張關(guān)系圖久橙,箭頭的方向表示了表的加工方向俄占,通過上游或者下游表我們可以快速地找到它的依賴,清晰明了地顯示從上游到下游的每一個(gè)鏈路淆衷。
如果要表達(dá)復(fù)雜的血緣依賴關(guān)系圖缸榄,通過傳統(tǒng)的關(guān)系型數(shù)據(jù)庫需要復(fù)雜的 SQL 實(shí)現(xiàn)(循環(huán)嵌套),性能也比較差祝拯,而通過圖數(shù)據(jù)庫實(shí)現(xiàn)甚带,則可直接按數(shù)據(jù)依賴關(guān)系存儲(chǔ),讀取也快于傳統(tǒng) DB佳头,非常簡(jiǎn)潔鹰贵。目前,數(shù)據(jù)血緣也是攜程金融在圖數(shù)據(jù)庫上的一個(gè)經(jīng)典應(yīng)用康嘉。
3.2 風(fēng)控關(guān)系人圖
關(guān)系人圖常用于欺詐識(shí)別等場(chǎng)景碉输,它是通過 ID、設(shè)備亭珍、手機(jī)標(biāo)識(shí)以及其他介質(zhì)信息關(guān)聯(lián)不同用戶的關(guān)系網(wǎng)絡(luò)敷钾。比如說,用戶 A 和用戶 B 共享一個(gè) Wi-Fi肄梨,他們便是局域網(wǎng)下的關(guān)系人阻荒;用戶 C 和用戶 D 相互下過單,他們便是下單關(guān)系人峭范。簡(jiǎn)言之财松,系統(tǒng)通過多種維度的數(shù)據(jù)關(guān)聯(lián)不同的用戶,這便是關(guān)系人圖纱控。
構(gòu)建模型時(shí)辆毡,通常要查詢某個(gè)時(shí)點(diǎn)(比如欺詐事件發(fā)生前)的關(guān)系圖,對(duì)當(dāng)時(shí)的圖進(jìn)行模型抽取和特征構(gòu)建甜害,我們稱這個(gè)過程為圖回溯舶掖。隨著回溯時(shí)間點(diǎn)的不同,返回的圖數(shù)據(jù)也是動(dòng)態(tài)變化的尔店;比如某人上午眨攘,下午各自打了一通電話,需要回溯此人中午時(shí)間點(diǎn)時(shí)的圖關(guān)系嚣州,只會(huì)出現(xiàn)上午的電話記錄鲫售,具體到圖,則每類邊都具有此類時(shí)間特性该肴,每一次查詢都需要對(duì)時(shí)間進(jìn)行限制情竹。
對(duì)于圖回溯場(chǎng)景,最初我們嘗試通過 HIVE SQL 實(shí)現(xiàn)匀哄,發(fā)現(xiàn)對(duì)于二階及以上的圖回溯秦效,SQL 表達(dá)會(huì)非常復(fù)雜雏蛮,而且性能不可接受(比如二階回溯 Hive 需要跑數(shù)小時(shí),三階回溯 Hive 幾乎不能實(shí)現(xiàn))阱州;因此嘗試借助圖數(shù)據(jù)庫來實(shí)現(xiàn)挑秉,把時(shí)間作為邊 rank 進(jìn)行建模,再根據(jù)邊關(guān)系進(jìn)行篩選來實(shí)現(xiàn)回溯苔货。這種回溯方式更直觀犀概、簡(jiǎn)潔,使用簡(jiǎn)單的 API 即可完成蒲赂,在性能上相比 Hive 也有 1 個(gè)數(shù)量級(jí)以上的提升(二階回溯阱冶,圖節(jié)點(diǎn):百億級(jí),待回溯節(jié)點(diǎn):10 萬級(jí))滥嘴。
下面用一個(gè)例子說明:如圖(a)若皱,點(diǎn) A 分別在 t0镊叁、t1、t2 時(shí)刻建立了一條邊走触,t0晦譬、t1、t2為邊 rank 值互广,需要返回 tx 時(shí)的的圖關(guān)系數(shù)據(jù)敛腌,只能返回 t0、t1 對(duì)應(yīng)的點(diǎn) B惫皱、C像樊,因?yàn)楫?dāng)回溯到 tx 時(shí)間點(diǎn)時(shí)候,t2 還沒有發(fā)生旅敷;最終返回的圖關(guān)系為 t0 和 t1 時(shí)候生棍,VertexA ->VertexB
、VertexA -> VertexC
(見圖(c))媳谁。這個(gè)例子是用一種邊進(jìn)行回溯涂滴,實(shí)際查詢中可能會(huì)涉及到 2~3 跳,且存在異構(gòu)邊(打電話是一種邊晴音,點(diǎn)外賣又是一種邊柔纵,下單酒店機(jī)票是一種邊,都是不同類型的邊)锤躁,而這種異構(gòu)圖的數(shù)據(jù)都具有回溯特征首量,因此實(shí)際的關(guān)系人圖回溯查詢也會(huì)變得復(fù)雜。
3.3 實(shí)時(shí)反欺詐圖
用戶下單時(shí),會(huì)進(jìn)入一個(gè)快速風(fēng)控的階段:通過基于關(guān)系型數(shù)據(jù)庫和圖數(shù)據(jù)庫的規(guī)則進(jìn)行模型特征計(jì)算觉啊,來判斷這個(gè)用戶是不是風(fēng)險(xiǎn)用戶拣宏,要不要對(duì)該用戶進(jìn)行下單攔截(實(shí)時(shí)反欺詐)。
我們可以根據(jù)圖關(guān)系配合模型規(guī)則杠人,用來挖掘欺詐團(tuán)伙勋乾。比如說,已知某個(gè) uid 是犯欺團(tuán)伙的一員嗡善,根據(jù)圖關(guān)聯(lián)來判斷跟他關(guān)系緊密的用戶是不是存在欺詐行為辑莫。為了避免影響正常用戶的下單流程,風(fēng)控階段需要快速響應(yīng)罩引,因此對(duì)圖查詢的性能要求非常高(P95 < 15 ms)各吨。我們基于 NebulaGraph 構(gòu)建了百億級(jí)的反欺詐圖,在查詢性能的優(yōu)化方面進(jìn)行了較多思考袁铐。
此圖 Schema 為脫敏過后的部分圖模型剔桨,當(dāng)中隱藏很多建模信息屉更。這里簡(jiǎn)單講解下部分的查詢流程和關(guān)聯(lián)信息。
如上圖為一次圖查詢流程洒缀,每一次圖查詢由多個(gè)起始點(diǎn)如用戶 uid瑰谜、用戶 mobile 等用戶信息同時(shí)開始,每條線為一次關(guān)聯(lián)查詢树绩,因此一次圖查詢由幾十次點(diǎn)邊查詢組成萨脑,由起始點(diǎn)經(jīng)過一跳查詢和 2 跳查詢,最終將結(jié)果集返回給風(fēng)控引擎葱峡。
系統(tǒng)會(huì)將用戶的信息砚哗,轉(zhuǎn)化為該用戶的標(biāo)簽。在圖查詢的時(shí)候砰奕,根據(jù)這些標(biāo)簽蛛芥,如 uid、mobile 進(jìn)行獨(dú)立查詢军援。舉個(gè)例子仅淑,根據(jù)某個(gè) uid 進(jìn)行一跳查詢,查詢出它關(guān)聯(lián)的 5 個(gè)手機(jī)號(hào)胸哥。再根據(jù)這 5 個(gè)手機(jī)號(hào)進(jìn)行獨(dú)立的 2 跳查詢涯竟,可能會(huì)出來 25 個(gè) uid,查詢會(huì)存在數(shù)據(jù)膨脹的情況。因此庐船,系統(tǒng)會(huì)做一個(gè)查詢限制银酬。去查看這 5 個(gè)手機(jī)號(hào)關(guān)聯(lián)的 uid 是不是超過了系統(tǒng)設(shè)定的熱點(diǎn)值。如果說通過 mobile 查詢出來關(guān)聯(lián)的手機(jī)號(hào)筐钟、uid 過多的話揩瞪,系統(tǒng)就會(huì)判斷其為熱點(diǎn)數(shù)據(jù),不進(jìn)行邊結(jié)果返回篓冲。(二階/三階回溯李破,圖點(diǎn)邊:百億級(jí))。
4.1 痛點(diǎn)及優(yōu)化
在上述應(yīng)用場(chǎng)景中壹将,對(duì)于風(fēng)控關(guān)系人圖和反欺詐圖嗤攻,由于圖規(guī)模比較大(百億點(diǎn)邊),查詢較多诽俯,且對(duì)時(shí)延要求較高妇菱,遇到了一些典型問題,接下來簡(jiǎn)單介紹一下惊畏。
4.1.1 查詢性能問題
為了滿足實(shí)時(shí)場(chǎng)景 2 跳查詢 P95 15 ms 需求恶耽,我們針對(duì)圖 Schema 和連接池以及查詢端做了一些優(yōu)化:
4.1.2 犧牲寫性能換取讀性能
首先偷俭,我們來看看這樣的一個(gè)需求:查詢 ID 關(guān)聯(lián)的手機(jī)號(hào),需要滿足對(duì)于這個(gè)手機(jī)號(hào)關(guān)聯(lián)邊不超過 3 個(gè)缰盏。這里解釋下為什么要限制關(guān)聯(lián)邊數(shù)量涌萤,因?yàn)槲覀冋€(gè)體關(guān)聯(lián)邊數(shù)量是有限的,會(huì)有一個(gè)對(duì)于大多數(shù)人的 P95 這樣的閾值邊數(shù)量口猜,超過這個(gè)閾值就是臟數(shù)據(jù)负溪。為了這個(gè)閾值校驗(yàn), 就需要對(duì)每次查詢的結(jié)果再多查詢一跳济炎。
如圖(a)所示川抡,我們需要進(jìn)行 2 次查詢,第一跳查詢是為了查詢用戶 ID 關(guān)聯(lián)的手機(jī)號(hào)须尚,第二跳查詢是為了保證我們的結(jié)果值是合法的(閾值內(nèi))崖堤,這樣每跳查詢最終需要進(jìn)行 2 跳查詢來滿足。如圖給出了圖查詢的 nGQL 2 步偽碼耐床,這種情況下無法滿足我們的高時(shí)效性密幔。如何優(yōu)化呢?看下圖(b) :
我們可以將熱點(diǎn)查詢固定在點(diǎn)屬性上昧廷,這樣一跳查詢時(shí)就可以知道該點(diǎn)有多少關(guān)聯(lián)邊,避免進(jìn)行圖 a 中(2)語句驗(yàn)證偎箫。還是以圖 (a)為例木柬,從一個(gè)用戶 ID 開始查詢,查詢他的手機(jī)號(hào)關(guān)聯(lián)镜廉,此時(shí)因?yàn)槭謾C(jī)號(hào)關(guān)聯(lián)的邊已經(jīng)變成了點(diǎn)屬性(修改了 schema)弄诲,圖(a) 2 條查詢語句實(shí)現(xiàn)的功能就可以變成一條查詢 go from $id over $edgeName where $手機(jī)號(hào).用戶id邊數(shù)據(jù) <5 | limit 5
。
這種設(shè)計(jì)的好處就是娇唯,在讀的時(shí)候可以加速驗(yàn)證過程,節(jié)約了一跳查詢寂玲。帶來的成本是:每寫一條邊塔插,同時(shí)需要更新 2 個(gè)點(diǎn)屬性來記錄點(diǎn)的關(guān)聯(lián)邊情況,而且需要保證冪等(保證重復(fù)提交不會(huì)疊加屬性 +1)拓哟。當(dāng)插入一條邊的時(shí)想许,先去圖里面查詢邊是否存在,不存在才會(huì)進(jìn)行寫邊以及點(diǎn)屬性 +1 的操作断序。也就是我們犧牲了寫性能流纹,來換取讀性能,并通過定期 check 保證數(shù)據(jù)一致违诗。
4.1.3 池化連接降低時(shí)延
第二個(gè)優(yōu)化手段是通過池化連接降低時(shí)延漱凝。Nebula 官方連接池每次進(jìn)行查詢均需要進(jìn)行建立初始化連接-執(zhí)行查詢?nèi)蝿?wù)-關(guān)閉連接。而在高頻(QPS 會(huì)達(dá)到幾千)的查詢場(chǎng)景中诸迟,頻繁的創(chuàng)建茸炒、關(guān)閉連接非常影響系統(tǒng)的性能和穩(wěn)定性。且建立連接過程耗時(shí)平均需要 6 ms阵苇, 比實(shí)際查詢時(shí)長(zhǎng) 1.5 ms 左右高出幾倍壁公,這是不可接受的。因此我們對(duì)官方客戶端進(jìn)行了二次封裝绅项,實(shí)現(xiàn)連接的復(fù)用和共享紊册。最后,將查詢 P95 從 20 ms 降低到了 4 ms快耿。通過合理控制并發(fā)囊陡,我們最終將 2 跳查詢性能控制在 P95 15 ms 。
這里貼下代碼供參考:
public class SessionPool {
/**
* 創(chuàng)建連接池
*
* @param maxCountSession 默認(rèn)創(chuàng)建連接數(shù)
* @param minCountSession 最大創(chuàng)建連接數(shù)
* @param hostAndPort 機(jī)器端口列表
* @param userName 用戶名
* @param passWord 密碼
* @throws UnknownHostException
* @throws NotValidConnectionException
* @throws IOErrorException
* @throws AuthFailedException
*/
public SessionPool(int maxCountSession, int minCountSession, String hostAndPort, String userName, String passWord) throws UnknownHostException, NotValidConnectionException, IOErrorException, AuthFailedException {
this.minCountSession = minCountSession;
this.maxCountSession = maxCountSession;
this.userName = userName;
this.passWord = passWord;
this.queue = new LinkedBlockingQueue<>(minCountSession);
this.pool = this.initGraphClient(hostAndPort, maxCountSession, minCountSession);
initSession();
}
public Session borrow() {
Session se = queue.poll();
if (se != null) {
return se;
}
try {
return this.pool.getSession(userName, passWord, true);
} catch (Exception e) {
log.error("execute borrow session fail, detail: ", e);
throw new RuntimeException(e);
}
}
public void release(Session se) {
if (se != null) {
boolean success = queue.offer(se);
if (!success) {
se.release();
}
}
}
public void close() {
this.pool.close();
}
private void initSession() throws NotValidConnectionException, IOErrorException, AuthFailedException {
for (int i = 0; i < minCountSession; i++) {
queue.offer(this.pool.getSession(userName, passWord, true));
}
}
private NebulaPool initGraphClient(String hostAndPort, int maxConnSize, int minCount) throws UnknownHostException {
List<HostAddress> hostAndPorts = getGraphHostPort(hostAndPort);
NebulaPool pool = new NebulaPool();
NebulaPoolConfig nebulaPoolConfig = new NebulaPoolConfig();
nebulaPoolConfig = nebulaPoolConfig.setMaxConnSize(maxConnSize);
nebulaPoolConfig = nebulaPoolConfig.setMinConnSize(minCount);
nebulaPoolConfig = nebulaPoolConfig.setIdleTime(1000 * 600);
pool.init(hostAndPorts, nebulaPoolConfig);
return pool;
}
private List<HostAddress> getGraphHostPort(String hostAndPort) {
String[] split = hostAndPort.split(",");
return Arrays.stream(split).map(item -> {
String[] splitList = item.split(":");
return new HostAddress(splitList[0], Integer.parseInt(splitList[1]));
}).collect(Collectors.toList());
}
private Queue<Session> queue;
private String userName;
private String passWord;
private int minCountSession;
private int maxCountSession;
private NebulaPool pool;
}
4.1.4 查詢端優(yōu)化
對(duì)于查詢端润努,像 3.3 中的例圖关斜,每一次圖查詢由多個(gè)起始點(diǎn)開始,可拆解為幾十次點(diǎn)邊查詢铺浇,需要讓每一層的查詢盡可能地并發(fā)進(jìn)行痢畜,降低最終時(shí)延。我們可以先對(duì) 1 跳查詢并發(fā)(約十幾次查詢),再對(duì)結(jié)果進(jìn)行分類合并丁稀,進(jìn)行第二輪的迭代并發(fā)查詢(十幾到幾十次查詢)吼拥,通過合理地控制并發(fā),可將一次組合圖查詢的 P95 控制在 15 ms 以內(nèi)线衫。
4.2 邊熱點(diǎn)問題
在圖查詢過程中凿可,存在部分用戶 ID 關(guān)聯(lián)過多信息,如黃牛用戶關(guān)聯(lián)過多信息授账,這部分異常用戶會(huì)在每一次查詢時(shí)被過濾掉枯跑,不會(huì)繼續(xù)參與下一次查詢,避免結(jié)果膨脹白热。而判斷是否為異常用戶敛助,則依賴于數(shù)據(jù)本身設(shè)定的閾值,異常數(shù)據(jù)不會(huì)流入下一階段對(duì)模型計(jì)算造成干擾屋确。
4.3 一致性問題
NebulaGraph 本身是沒有事務(wù)的纳击,對(duì)于上文寫邊以及點(diǎn)屬性 +1 的操作,如何保證這些操作的一致性攻臀,上文提到過焕数,我們會(huì)定期對(duì)全量 HIVE 表數(shù)據(jù)和圖數(shù)據(jù)庫進(jìn)行 check,以 HIVE 數(shù)據(jù)為準(zhǔn)對(duì)線上圖進(jìn)行修正刨啸,來實(shí)現(xiàn)最終一致性堡赔。目前來說,圖數(shù)據(jù)庫和 HIVE 表不一致的情況還是比較少的呜投。
5. 總結(jié)與展望
基于 NebulaGraph 的圖業(yè)務(wù)應(yīng)用加匈,完成了對(duì)數(shù)據(jù)血緣、對(duì)關(guān)系人網(wǎng)絡(luò)仑荐、反欺詐等場(chǎng)景的支持雕拼,并將持續(xù)應(yīng)用在金融更多場(chǎng)景下,助力金融業(yè)務(wù)粘招。我們將持續(xù)跟進(jìn)社區(qū)啥寇,結(jié)合自身應(yīng)用場(chǎng)景推進(jìn)圖平臺(tái)建設(shè);同時(shí)也期待社區(qū)版能提供熱升級(jí)洒扎、資源隔離辑甜、更豐富易用的算法包、更強(qiáng)大的 Studio 等功能袍冷。
謝謝你讀完本文 (///▽///)
如果你想嘗鮮圖數(shù)據(jù)庫 NebulaGraph磷醋,記得去 GitHub 下載、使用胡诗、(з)-☆ star 它 -> GitHub邓线;和其他的 NebulaGraph 用戶一起交流圖數(shù)據(jù)庫技術(shù)和應(yīng)用技能淌友,留下「你的名片」一起玩耍呀~