攜程分布式圖數(shù)據(jù)庫Nebula Graph運(yùn)維治理實(shí)踐
作者簡介
Patrick Yu,攜程云原生研發(fā)專家,關(guān)注非關(guān)系型分布式數(shù)據(jù)存儲及相關(guān)技術(shù)。
一、背景
隨著互聯(lián)網(wǎng)世界產(chǎn)生的數(shù)據(jù)越來越多,數(shù)據(jù)之間的聯(lián)系越來越復(fù)雜層次越來越深,人們希望從這些紛亂復(fù)雜的數(shù)據(jù)中探索各種關(guān)聯(lián)的需求也在與日遞增。為了更有效地應(yīng)對這類場景,圖技術(shù)受到了越來越多的關(guān)注及運(yùn)用。
DB-ENGINES 趨勢報告顯示圖數(shù)據(jù)庫趨勢增長遙遙領(lǐng)先
在攜程,很早就有一些業(yè)務(wù)嘗試了圖技術(shù),并將其運(yùn)用到生產(chǎn)中,以Neo4j和JanusGraph為主。2021年開始,我們對圖數(shù)據(jù)庫進(jìn)行集中的運(yùn)維治理,期望規(guī)范業(yè)務(wù)的使用,并適配攜程已有的各種系統(tǒng),更好地服務(wù)業(yè)務(wù)方。經(jīng)過調(diào)研,我們選擇分布式圖數(shù)據(jù)庫Nebula Graph作為管理的對象,主要基于以下幾個因素考慮:
1)Nebula Graph開源版本即擁有橫向擴(kuò)展能力,為大規(guī)模部署提供了基本條件;
2)使用自研的原生存儲層,相比JanusGraph這類構(gòu)建在第三方存儲系統(tǒng)上的圖數(shù)據(jù)庫,性能和資源使用效率上具有優(yōu)勢;
3)支持兩種語言,尤其是兼容主流的圖技術(shù)語言Cypher,有助于用戶從其他使用Cypher語言的圖數(shù)據(jù)庫(例如Neo4j)中遷移;
4)擁有后發(fā)優(yōu)勢(2019起開源),社區(qū)活躍,且主流的互聯(lián)網(wǎng)公司都有參與(騰訊,快手,美團(tuán),網(wǎng)易等);
5)使用技術(shù)主流,代碼清晰,技術(shù)債較少,適合二次開發(fā);
二、Nebula Graph架構(gòu)及集群部署
Nebula Graph是一個分布式的計算存儲分離架構(gòu),如下圖:
其主要由Graphd,Metad和Storaged三部分服務(wù)組成,分別負(fù)責(zé)計算,元數(shù)據(jù)存取,圖數(shù)據(jù)(點(diǎn),邊,標(biāo)簽等數(shù)據(jù))的存取。在攜程的網(wǎng)絡(luò)環(huán)境中,我們提供了三種部署方式來支撐業(yè)務(wù):
2.1 三機(jī)房部署
用于滿足一致性和容災(zāi)的要求,優(yōu)點(diǎn)是任意一個機(jī)房發(fā)生機(jī)房級別故障,集群仍然可以使用,適用于核心應(yīng)用。但缺點(diǎn)也是比較明顯的,數(shù)據(jù)通過raft協(xié)議進(jìn)行同步的時候,會遇到跨機(jī)房問題,性能會受到影響。
2.2 單機(jī)房部署
集群所有節(jié)點(diǎn)都在一個機(jī)房中,節(jié)點(diǎn)之間通訊可以避免跨機(jī)房問題(應(yīng)用端與服務(wù)端之間仍然會存在跨機(jī)房調(diào)用),由于機(jī)房整體出現(xiàn)問題時該部署模式的系統(tǒng)將無法使用,所以適用于非核心應(yīng)用進(jìn)行訪問。
2.3 藍(lán)綠雙活部署
在實(shí)際使用中,以上兩種常規(guī)部署方式并不能滿足一些業(yè)務(wù)方的需求,比如性能要求較高的核心應(yīng)用,三機(jī)房的部署方式所帶來的網(wǎng)絡(luò)損耗可能會超出預(yù)期。根據(jù)攜程酒店某個業(yè)務(wù)場景真實(shí)測試數(shù)據(jù)來看,本地三機(jī)房的部署方式延遲要比單機(jī)房高50%+,但單機(jī)房部署無法抵抗單個IDC故障,此外還有用戶希望能存在類似數(shù)據(jù)回滾的能力,以應(yīng)對應(yīng)用發(fā)布,集群版本升級可能導(dǎo)致的錯誤。
考慮到使用圖數(shù)據(jù)庫的業(yè)務(wù)大多數(shù)據(jù)來自離線系統(tǒng),通過離線作業(yè)將數(shù)據(jù)導(dǎo)入到圖數(shù)據(jù)庫中,數(shù)據(jù)一致的要求并不高,在這種條件下使用藍(lán)綠部署能夠在災(zāi)備和性能上得到很好的滿足。
與此同時我們還增加了一些配套的輔助功能,比如:
分流:可以按比例分配機(jī)房的訪問,也可以主動切斷對某個機(jī)房的流量訪問
災(zāi)備:在發(fā)生機(jī)房級故障時,可自動切換讀訪問的流量,寫訪問的流量切換則通過人工進(jìn)行操作
藍(lán)綠雙活方式是在性能、可用性、一致性上的一個折中的選擇,使用此方案時應(yīng)用端架構(gòu)也需要有更多的調(diào)整以配合數(shù)據(jù)的存取。
生產(chǎn)上的一個例子:
三機(jī)房情況
藍(lán)綠部署
三、中間件及運(yùn)維管理
我們基于k8s crd和operator來進(jìn)行Nebula Graph的部署,同時通過服務(wù)集成到現(xiàn)有的部署配置頁面和運(yùn)維管理頁面,來獲得對pod的執(zhí)行和遷移的控制能力?;趕idecar模式監(jiān)控收集Nebula Graph的核心指標(biāo)并通過telegraf發(fā)送到攜程自研的Hickwall集中展示,并設(shè)置告警等一系列相關(guān)工作。
此外我們集成了跨機(jī)房的域名分配功能,為節(jié)點(diǎn)自動分配域名用于內(nèi)部訪問(域名只用于集群內(nèi)部,集群與外部連通是通過ip直連的),這樣做是為了避免節(jié)點(diǎn)漂移造成ip變更,影響集群的可用性。
在客戶端上,相比原生客戶端,我們主要做了以下幾個改進(jìn)和優(yōu)化:
3.1 Session管理功能
原生客戶端Session管理比較弱,尤其是2.x早期幾個版本,多線程訪問Session并不是線程安全的,Session過期或者失效都需要調(diào)用方來處理,不適合大規(guī)模使用。同時雖然官方客戶端創(chuàng)建的Session是可以復(fù)用的,并不需要release,官方也鼓勵用戶復(fù)用,但是卻沒有提供統(tǒng)一的Session管理功能來幫助用戶復(fù)用,因此我們增加了Session Pool的概念來實(shí)現(xiàn)復(fù)用。
其本質(zhì)上是管理一個或多個Session Object Queue,通過borrow-and-return的方式(下圖),確保了一個Session在同一時間只會由一個執(zhí)行器在使用,避免了共用Session產(chǎn)生的問題。同時通過對隊(duì)列的管理,我們可以進(jìn)行Session數(shù)量和版本的管理,比如預(yù)生成一定量的Session,或者在管理中心發(fā)出消息之后變更Session的數(shù)量或者訪問的路由。
3.2 藍(lán)綠部署(包括讀寫分離)
上面章節(jié)中介紹了藍(lán)綠部署,相應(yīng)的客戶端也需要改造以支持訪問2個集群。由于生產(chǎn)中,讀和寫的邏輯往往不同,比如讀操作希望可以由2個集群共同提供數(shù)據(jù),而寫的時候只希望影響單邊,所以我們在進(jìn)行藍(lán)綠處理的時候也增加了讀寫分離(下圖)。
3.3 流量分配
如果要考慮到單邊切換以及讀寫不同的路由策略,就需要增加流量分配功能。我們沒有采用攜程內(nèi)廣泛使用的Virtual IP作為訪問路由,希望有更為強(qiáng)大的定制管理能力及更好的性能。
a)通過直連而不是Virtual IP中轉(zhuǎn)可以減少一次轉(zhuǎn)發(fā)的損耗
b)在維持長連接的同時也能實(shí)現(xiàn)每次請求使用不同的鏈路,平攤graphd的訪問壓力
c)完全自主控制路由,可以實(shí)現(xiàn)更為靈活的路由方案
d)當(dāng)存在節(jié)點(diǎn)無法訪問的時候,客戶端可以自動臨時排除有問題的IP,在短時間內(nèi)避免再次使用。而如果使用Virtual IP的話,由于一個Virtual IP會對應(yīng)多個物理IP,就沒有辦法直接這樣操作。
通過構(gòu)造面向不同idc的Session Pool,并根據(jù)配置進(jìn)行權(quán)重輪詢,就可以達(dá)到按比例分配訪問流量的目的(下圖)。
將流量分配集成進(jìn)藍(lán)綠模式,就基本實(shí)現(xiàn)了基本的客戶端改造(下圖)。
3.4 結(jié)構(gòu)化語句查詢
圖DSL目前主流的有兩種,Gremlin和Cypher,前者是過程式語言而后者是聲明式語言。Nebula Graph支持了openCypher(Cypher的開源項(xiàng)目)語法和自己設(shè)計的nGQL原生語法,這兩種都是聲明式語言,在風(fēng)格上比較類似SQL。盡管如此,對于一些較為簡單的語句,類似Gremlin風(fēng)格的過程式語法對用戶會更為友好,并且有利用監(jiān)控埋點(diǎn)?;谶@個原因,我們封裝了一個過程式的語句生成器。
例如:
Cypher風(fēng)格 | MATCH (v:user{name:"XXX"})-[e:follow|:serve]->(v2) RETURN v2 AS Friends; |
新增的過程式風(fēng)格 | Builder.match() .vertex("v") .hasTag("user") .property("name", "XXX", DataType.String()) .edge("e", Direction.OUTGOING) .type("follow") .type("serve") .vertex("v2") .ret("v2", "Friends") |
四、系統(tǒng)調(diào)優(yōu)實(shí)踐
由于建模,使用場景,業(yè)務(wù)需求的差異,使用Nebula Graph的過程中所遇到的問題很可能會完全不同,以下以攜程酒店信息圖譜線上具體的例子進(jìn)行說明,在整個落地過程我們遇到的問題及處理過程(文中以下內(nèi)容是基于Nebula Graph 2.6.1進(jìn)行的)。
關(guān)于酒店該業(yè)務(wù)的更多細(xì)節(jié),可以閱讀《 信息圖譜在攜程酒店的應(yīng)用 》這篇文章。
4.1 酒店集群不穩(wěn)定
起因是酒店應(yīng)用上線后發(fā)生了一次故障,大量的訪問超時,并伴隨著“The leader has changed”這樣的錯誤信息。稍加排查,我們發(fā)現(xiàn)metad集群有問題,metad0的local ip和metad_server_address的配置不一致,所以metad0實(shí)際上一直沒有工作。
但這本身并不會導(dǎo)致系統(tǒng)問題,因?yàn)?節(jié)點(diǎn)部署,只需要2個節(jié)點(diǎn)工作即可,后來metad1容器又意外被漂移了,導(dǎo)致ip變更,這個時候?qū)嶋H上metad集群已經(jīng)無法工作(下圖),導(dǎo)致整個集群都受到了影響。
在處理完以上故障并重啟之后,整個系統(tǒng)卻并沒有恢復(fù)正常,cpu的使用率很高。此時外部應(yīng)用并沒有將流量接入進(jìn)來,但整個metad集群內(nèi)部網(wǎng)絡(luò)流量卻很大,如下圖所示:
監(jiān)控顯示metad磁盤空間使用量很大,檢查下來WAL在不斷增加,說明這些流量主要是數(shù)據(jù)的寫入操作。我們打開WAL數(shù)據(jù)的某幾個文件,其大部分都是Session的元數(shù)據(jù),因?yàn)镾ession信息是會在Nebula集群內(nèi)持久化的,所以考慮問題可能出在這里。閱讀源碼我們注意到,graphd會從metad中同步所有的session信息,并在修改之后將數(shù)據(jù)再全部回寫到metad中,所以如果流量都是session信息的話,那么問題就可能:
a)Session沒有過期
b)創(chuàng)建了太多的Session
檢查發(fā)現(xiàn)該集群沒有配置超時時間, 所以我們修改以下配置來處理 這個問題:
類型 | 配置項(xiàng) | 原始值 | 修改值 | 說明 |
Graphd | session_idle_timeout_secs | 默認(rèn)(0) | 86400 | 此配置控制session的過期,由于初始我們沒有設(shè)置這個參數(shù),這意味著session永遠(yuǎn)不會過期,這會導(dǎo)致過去訪問過該graphd的session會永遠(yuǎn)存在于metad存儲層,造成session元數(shù)據(jù)累積。 |
session_reclaim_interval_secs | 默認(rèn)(10) | 30 | 原設(shè)置說明每10s graphd會將session信息發(fā)送給metad持久化。這也會導(dǎo)致寫入數(shù)據(jù)量過多??紤]到即使down機(jī)也只是損失部分的Session元數(shù)據(jù)更新,這些損失帶來的危害比較小,所以我們改成了30s以減少于metad之間同步元數(shù)據(jù)的次數(shù)。 | |
Metad | wal_ttl | 默認(rèn)(14400) | 3600 | wal用于記錄修改操作的,一般來說是不需要保留太久的,況且nebula graph為了安全,都至少會為每個分片保留最后2個wal文件,所以減少ttl加快wal淘汰,將空間節(jié)約出來 |
修改之后,metad的磁盤空間占用下降,同時通信流量和磁盤讀寫也明顯下降(下圖):
系統(tǒng)逐步恢復(fù)正常,但是還有一個問題沒有解決,就是為什么有如此之多的session數(shù)據(jù)?查看應(yīng)用端日志,我們注意到session創(chuàng)建次數(shù)超乎尋常,如下圖所示:
通過日志發(fā)現(xiàn)是我們自己開發(fā)的客戶端中的bug造成的。我們會在報錯時讓客戶端釋放對應(yīng)的session,并重新創(chuàng)建,但由于系統(tǒng)抖動,這個行為造成了比較多的超時,導(dǎo)致更多的session被釋放并重建,引起了惡性循環(huán)。針對這個問題,對客戶端進(jìn)行了如下優(yōu)化:
修改 | |
1 | 將創(chuàng)建session行為由并發(fā)改為串行,每次只允許一個線程進(jìn)行創(chuàng)建工作,不參與創(chuàng)建的線程監(jiān)聽session pool |
2 | 進(jìn)一步增強(qiáng)session的復(fù)用,當(dāng)session執(zhí)行失敗的時候,根據(jù)失敗原因來決定是否需要release。 原有的邏輯是一旦執(zhí)行失敗就release當(dāng)前session,但有些時候并非是session本身的問題,比如超時時間過短,nGQL有錯誤這些應(yīng)用層的情況也會導(dǎo)致執(zhí)行失敗,這個時候如果直接release,會導(dǎo)致session數(shù)量大幅度下降從而造成大量session創(chuàng)建。根據(jù)問題合理的劃分錯誤情況來進(jìn)行處理,可以最大程度保持session狀況的穩(wěn)定 |
3 | 增加預(yù)熱功能,根據(jù)配置提前創(chuàng)建好指定數(shù)量的session,以避免啟動時集中創(chuàng)建session導(dǎo)致超時 |
4.2 酒店集群存儲服務(wù)CPU使用率過高
酒店業(yè)務(wù)方在增加訪問量的時候,每次到80%的時候集群中就有少數(shù)storaged不穩(wěn)定,cpu使用率突然暴漲,導(dǎo)致整個集群響應(yīng)增加,從而應(yīng)用端產(chǎn)生大量超時報錯,如下圖所示:
和酒店方排查下來初步懷疑是存在稠密點(diǎn)問題(在圖論中,稠密點(diǎn)是指一個點(diǎn)有著極多的相鄰邊,相鄰邊可以是出邊或者是入邊),部分storaged被集中訪問引起系統(tǒng)不穩(wěn)定。由于業(yè)務(wù)方強(qiáng)調(diào)稠密點(diǎn)是其業(yè)務(wù)場景難以避免的情況,我們決定采取一些調(diào)優(yōu)手段來緩解這個問題。
1)嘗試通過Balance來分?jǐn)傇L問壓力
回憶之前的官方架構(gòu)圖,數(shù)據(jù)在storaged中是分片的,且raft協(xié)議中只有l(wèi)eader才會處理請求,所以重新進(jìn)行數(shù)據(jù)平衡操作,是有可能將多個稠密點(diǎn)分?jǐn)偟讲煌姆?wù)上意減輕單一服務(wù)的壓力。同時我們對整個集群進(jìn)行compaction操作(由于Storaged內(nèi)部使用了RocksDB作為存儲引擎,數(shù)據(jù)是通過追加來進(jìn)行修改的,Compaction可以清楚過時的數(shù)據(jù),提高訪問效率)。
操作之后集群的整體cpu是有一定的下降,同時服務(wù)的響應(yīng)速度也有小幅的提升,如下圖。
但在運(yùn)行一段時間之后仍然遇到了cpu突然增加的情況,稠密點(diǎn)顯然沒有被平衡掉,也說明在分片這個層面是沒法緩解稠密點(diǎn)帶來的訪問壓力的 。
2)嘗試通過配置緩解鎖競爭
進(jìn)一步調(diào)研出現(xiàn)問題的storaged的cpu的使用率,可以看到當(dāng)流量增加的時候,內(nèi)核占用的cpu非常高,如下圖所示:
抓取perf看到,鎖競爭比較激烈,即使在“正常”情況下,鎖的占比也很大,而在競爭激烈的時候,出問題的storaged服務(wù)上這個比例超過了50%。如下圖所示:
所以我們從減少沖突入手,對nebula graph集群主要做了如下改動:
類型 | 配置項(xiàng) | 原始值 | 修改值 | 說明 |
Storaged | rocksdb_block_cache | 默認(rèn)(4) | 8192 | block cache用緩存解壓縮之后的數(shù)據(jù),cache越大,數(shù)據(jù)淘汰情況越低,這樣就越可能更快的命中數(shù)據(jù),減少反復(fù)從page cache加載及depress的操作 |
enable_rocksdb_prefix_filtering | false | true | 在內(nèi)存足夠的情況下,我們打開prefix過濾,是希望通過其通過前綴更快的定位到數(shù)據(jù),減少查詢非必要的數(shù)據(jù),減少數(shù)據(jù)競爭 | |
RocksDB | disable_auto_compactions | 默認(rèn) | false | 打開自動compaction,緩解因?yàn)閿?shù)據(jù)碎片造成的查詢cpu升高 |
write_buffer_size | 默認(rèn) | 134217728 | 將memtable設(shè)置為128MB,減少其flush的次數(shù) | |
max_background_compactions | 默認(rèn) | 4 | 控制后臺compactions的線程數(shù) |
重新上線之后,整個集群服務(wù)變得比較平滑,cpu的負(fù)載也比較低,正常情況下鎖競爭也下降不少(下圖),酒店也成功的將流量推送到了100%。
但運(yùn)行了一段時間之后,我們?nèi)匀挥龅搅朔?wù)響應(yīng)突然變慢的情況,熱點(diǎn)訪問帶來的壓力 的確超過了優(yōu)化帶來的提升。
3)嘗試減小鎖的顆粒度
考慮到在分片級別的balance不起作用,而cpu的上升主要是因?yàn)殒i競爭造成的,那我們想到如果減小鎖的顆粒度,是不是就可以盡可能減小競爭?RocksDB的LRUCache允許調(diào)整shared數(shù)量,我們對此進(jìn)行了修改:
版本 | LRUCache默認(rèn)分片數(shù) | 方式 |
2.5.0 | 2 8 | 修改代碼,將分片改成2 10 |
2.6.1及以上 | 2 8 | 通過配置cache_bucket_exp = 10,將分片數(shù)改為2 10 |
觀察下來效果不明顯,無法解決熱點(diǎn)競爭導(dǎo)致的雪崩問題。其本質(zhì)同balance操作一樣,只是粒度的大小的區(qū)別,在熱點(diǎn)非常集中的情況下,在數(shù)據(jù)層面進(jìn)行處理是走不通的。
4)嘗試使用ClockCache
競爭的鎖來源是block cache造成的。nebula storaged使用rocksdb作為存儲,其使用的是LRUCache作為block cache等一系列cache的存儲模塊,LRUCache在任何類型的訪問的時候需要需要加鎖操作,以進(jìn)行一些LRU信息的更新,排序的調(diào)整及數(shù)據(jù)的淘汰,存在吞吐量的限制。
由于我們主要面臨的就是鎖競爭,在業(yè)務(wù)數(shù)據(jù)沒法變更的情況下,我們希望其他cache模塊來提升訪問的吞吐。按照rocksdb官方介紹,其還支持一種cache類型ClockCache,特點(diǎn)是在查詢時不需要加鎖,只有在插入時才需要加鎖,會有更大的訪問吞吐,考慮到我們主要是讀操作,看起來ClockCache會比較合適。
LRU cache和Clock cache的區(qū)別: https://rocksdb.org.cn/doc/Block-Cache.html
經(jīng)過修改源碼和重新編譯,我們將緩存模塊改成了ClockCache,如下圖所示:
但集群使用時沒幾分鐘就core, 查找資料我們發(fā)現(xiàn)目前ClockCache支持還存在問題( https://github.com/facebook/rocksdb/pull/8261) , 此方案目前無法使用。
5)限制線程使用
可以看到整個系統(tǒng)在當(dāng)前配置下,是存在非常多的線程的,如下圖所示。
如果是單線程,就必然不會存在鎖競爭。但作為一個圖服務(wù),每次訪問幾乎會解析成多個執(zhí)行器來并發(fā)訪問,強(qiáng)行改為單線程必然會造成訪問堆積。
所以我們考慮將原有的線程池中的進(jìn)程調(diào)小,以避免太多的線程進(jìn)行同步等待帶來的線程切換,以減小系統(tǒng)對cpu的占用。
類型 | 配置項(xiàng) | 原始值 | 修改值 | 說明 |
Storaged | num_io_threads | 默認(rèn)(16) | 4或者8 | |
num_worker_threads | 默認(rèn)(32) | 4或者8 | ||
reader_handlers | 默認(rèn)(32) | 8或者12 | 官方未公開配置 |
調(diào)整之后整個系統(tǒng)cpu非常平穩(wěn),絕大部分物理機(jī)cpu在20%以內(nèi),且沒有之前遇到的突然上下大幅波動的情況(瞬時激烈鎖競爭會大幅度提升cpu的使用率),說明這個調(diào)整對當(dāng)前業(yè)務(wù)來說是有一定效果的。
隨之又遇到了下列問題,前端服務(wù)突然發(fā)現(xiàn)nebula的訪問大幅度超時,而從系統(tǒng)監(jiān)控的角度卻毫無波動(下圖24,19:53系統(tǒng)其實(shí)已經(jīng)響應(yīng)出現(xiàn)問題了,但cpu沒有任何波動)。
原因是在于,限制了thread 確實(shí)有效果,減少了競爭,但隨著壓力的正大,線程吞吐到達(dá)極限,但如果增加線程,資源的競爭又會加劇,無法找到平衡點(diǎn)。
6)關(guān)閉數(shù)據(jù)壓縮,關(guān)閉block cache
在沒有特別好的方式避免鎖競爭的情況,我們重新回顧了鎖競爭的整個發(fā)生過程,鎖產(chǎn)生本身就是由cache自身的結(jié)構(gòu)帶來的,尤其是在讀操作的時候,我們并不希望存在什么鎖的行為。
使用block cache,是為了在合理的緩存空間中盡可能的提高緩存命中率,以提高緩存的效率。但如果緩存空間非常充足,且命中長期的數(shù)據(jù)長期處于特定的范圍內(nèi),實(shí)際上并沒有觀察到大量的緩存淘汰的情況,且當(dāng)前服務(wù)的緩存實(shí)際上也并沒有用滿,所以想到,是不是可以通過關(guān)閉block cache,而直接訪問page cache來避免讀操作時的加鎖行為。
除了block cache,存儲端還有一大類內(nèi)存使用是Indexes and filter blocks,與此有關(guān)的設(shè)置在RocksDB中是cache_index_and_filter_blocks。當(dāng)這個設(shè)置為true的時候,數(shù)據(jù)會緩存到block cache中,所以如果關(guān)閉了block cache,我們就需要同樣關(guān)閉cache_index_and_filter_blocks(在Nebula Graph中,通過配置項(xiàng)enable_partitioned_index_filter替代直接修改RocksDB的cache_index_and_filter_blocks)。
但僅僅修改這些并沒有解決問題,實(shí)際上觀察perf我們?nèi)匀豢吹芥i的競爭造成的阻塞(下圖):
這是因?yàn)楫?dāng)cache_index_and_filter_blocks為false的時候,并不代表index和filter數(shù)據(jù)不會被加載到內(nèi)存中,這些數(shù)據(jù)其實(shí)會被放進(jìn)table cache里,仍然需要通過LRU來維護(hù)哪些文件的信息需要淘汰,所以LRU帶來的問題并沒有完全解決。處理的方式是將max_open_files設(shè)置為-1,以提供給系統(tǒng)無限制的table cache的使用,在這種情況下,由于沒有文件信息需要置換出去,算法邏輯被關(guān)閉。
總結(jié)下來核心修改如下表:
類型 | 配置項(xiàng) | 原始值 | 修改值 | 說明 |
Storaged | rocksdb_block_cache | 8192 | -1 | 關(guān)閉block cache |
rocksdb_compression_per_level | lz4 | no:no:no:no:lz4:lz4:lz4 | 在L0~L3層關(guān)閉壓縮 | |
enable_partitioned_index_filter | true | false | 避免將index和filter緩存進(jìn)block cache | |
RocksDB | max_open_files | 4096 | -1 | 避免文件被table cache淘汰,避免文件描述符被關(guān)閉,加快文件的讀取 |
關(guān)閉了block cache后,整個系統(tǒng)進(jìn)入了一個非常穩(wěn)定的狀態(tài),線上集群在訪問量增加一倍以上的情況下,系統(tǒng)的cpu峰值反而穩(wěn)定在30%以下,且絕大部分時間都在10%以內(nèi)(下圖)。
需要說明的是,酒店場景中關(guān)閉block cache是一個非常有效的手段,能夠?qū)ζ涮囟ㄇ闆r下的熱點(diǎn)訪問起到比較好的效果,但這并非是一個常規(guī)方式,我們在其他業(yè)務(wù)方的nebula graph集群中并沒有關(guān)閉block cache。
4.3 數(shù)據(jù)寫入時服務(wù)down機(jī)
起因酒店業(yè)務(wù)在全量寫入的時候,即使量不算很大(4~5w/s),在不特定的時間就會導(dǎo)致整個graphd集群完全down機(jī),由于graphd集群都是無狀態(tài)的,且互相之間沒有關(guān)系,如此統(tǒng)一的在某個時刻集體down機(jī),我們猜測是由于訪問請求造成。通過查看堆棧發(fā)現(xiàn)了明顯的異常(下圖):
可以看到上圖中的三行語句被反復(fù)執(zhí)行,很顯然這里存在遞歸調(diào)用,并且無法在合理的區(qū)間內(nèi)退出,猜測為堆棧已滿。在增加了堆棧大小之后,整個執(zhí)行沒有任何好轉(zhuǎn),說明遞歸不僅層次很深,且可能存在指數(shù)級的增加的情況。同時觀察down機(jī)時的業(yè)務(wù)請求日志,失敗瞬間大量執(zhí)行失敗,但有一些執(zhí)行失敗顯示為null引用錯誤,如下圖所示:
這是因?yàn)榉祷亓藞箦e,但沒有error message,導(dǎo)致發(fā)生了空引用(空引用現(xiàn)象是客戶端未合理處理這種情況,也是我們客戶端的bug),但這種情況很奇怪,為什么會沒有error message,檢查其trace日志,發(fā)現(xiàn)這些請求執(zhí)行nebula時間都很長,且存在非常大段的語句,如下圖所示:
預(yù)感是這些語句導(dǎo)致了graphd的down機(jī),由于執(zhí)行被切斷導(dǎo)致客戶端生成了一個null值。將這些語句進(jìn)行重試,可以必現(xiàn)down機(jī)的場景。檢查這樣的請求發(fā)現(xiàn)其是由500條語句組成(業(yè)務(wù)方語句拼接上限500),并沒有超過配置設(shè)置的最大執(zhí)行語句數(shù)量(512)。
看起來這是一個nebula官方的bug,我們已經(jīng)將此問題提交給官方。同時業(yè)務(wù)方語句拼接限制從500降為200后順利避免該問題導(dǎo)致的down機(jī)。
五、Nebula Graph二次開發(fā)
當(dāng)前我們對Nebula Graph的修改主要集中的幾個運(yùn)維相關(guān)的環(huán)節(jié)上,比如新增了命令來指定遷移Storaged中的分片,以及將leader遷移到指定的實(shí)例上(下圖)。
六、未來規(guī)劃
- 與攜程大數(shù)據(jù)平臺整合,充分利用Spark或者Flink來實(shí)現(xiàn)數(shù)據(jù)的傳輸和ETL,提高異構(gòu)集群間數(shù)據(jù)的遷移能力。
- 提供Slowlog檢查功能,抓取造成slowlog的具體語句。
- 參數(shù)化查詢功能,避免依賴注入。
- 增強(qiáng)可視化能力,增加定制化功能。