與以磁盤存儲為主的普通數(shù)據(jù)庫相比,內(nèi)存數(shù)據(jù)庫的數(shù)據(jù)訪問速度可以高出幾個(gè)數(shù)量級,能大幅提高運(yùn)算性能,更適合高并發(fā)、低延時(shí)的業(yè)務(wù)場景。
不過,當(dāng)前大部分內(nèi)存數(shù)據(jù)庫仍然采用 SQL 模型,而 SQL 缺乏一些必要的數(shù)據(jù)類型和運(yùn)算,不能充分利用內(nèi)存的特征實(shí)現(xiàn)某些高性能算法。僅僅是把外存的數(shù)據(jù)和運(yùn)算簡單地搬進(jìn)內(nèi)存,固然也能獲得比外存好得多的性能,但還沒有充分利用內(nèi)存特征,也就不能獲得極致的性能。
下面我們來看看,有哪些適合內(nèi)存特征的算法和存儲機(jī)制,可以進(jìn)一步提升內(nèi)存數(shù)據(jù)庫計(jì)算速度。
指針式復(fù)用
我們知道,內(nèi)存可以通過地址(指針)來訪問。但 SQL 沒有用內(nèi)存指針表示的數(shù)據(jù)對象,在返回結(jié)果集時(shí),通常要把數(shù)據(jù)復(fù)制一份,形成一個(gè)新的數(shù)據(jù)表。這樣不僅多消耗 CPU 時(shí)間(用于復(fù)制數(shù)據(jù))而且還會占用更多昂貴的內(nèi)存空間(用于存儲復(fù)制的數(shù)據(jù)),降低內(nèi)存使用率。
除了 SQL 型的內(nèi)存數(shù)據(jù)庫外,Spark 中的 RDD 也有這個(gè)問題,而且情況更嚴(yán)重。為了保持 RDD 的 immutable 特性,Spark 在每個(gè)計(jì)算步驟后都會復(fù)制出新的 RDD,造成內(nèi)存和 CPU 的大量浪費(fèi)。所以,即使耗用了巨大資源,Spark 也仍然做不到高性能。相比之下,SQL 型的內(nèi)存數(shù)據(jù)庫通常還會優(yōu)化,在 SQL 語句中的計(jì)算會盡量使用內(nèi)存地址,通常要比 Spark 的性能更好。
但是,受到理論限制,實(shí)現(xiàn) SQL 的邏輯時(shí),返回的結(jié)果集就必須復(fù)制了。如果涉及多步驟的過程運(yùn)算,要多次在上一步的結(jié)果集(臨時(shí)表)基礎(chǔ)上進(jìn)一步計(jì)算,SQL 的劣勢就會很明顯了。
事實(shí)上,如果沒有改變數(shù)據(jù)結(jié)構(gòu),我們可以直接用原數(shù)據(jù)的地址形成結(jié)果集,不需要復(fù)制數(shù)據(jù)本身,僅僅多保存一個(gè)地址(指針),同時(shí)減少 CPU 和內(nèi)存的消耗。
SPL 擴(kuò)展了 SQL 的數(shù)據(jù)類型,支持這種指針式復(fù)用機(jī)制。比如,對訂單表按照訂單日期(odate)范圍過濾后,分別求出訂單金額(amount1)大于 1000 和運(yùn)貨費(fèi)(amount2)大于 1000 的訂單,再計(jì)算出兩者的交集、并集和差集,最后將差集按照客戶號(cid)排序。SPL 代碼大致是這樣:
A | B | |
---|---|---|
1 | =orders.select(odate>=date(2000,1,1) && odate<=date(2022,1,1)) | |
2 | =A1.select(amount1>1000) | =A1.select(amount2>1000) |
3 | =A2^B2 | =A2&B2 |
4 | =A2\B2 | =B2\A2 |
5 | =A4.sort(cid) | =B4.sort(cid) |
以上代碼中有好幾個(gè)步驟,有的中間結(jié)果也被用了多次,但由于使用的都是訂單表記錄的指針,所以內(nèi)存占用增加的很少,也避免了記錄復(fù)制的耗時(shí)。 |
外鍵預(yù)關(guān)聯(lián)
外鍵關(guān)聯(lián)是指用一個(gè)表(事實(shí)表)的非主鍵字段,去關(guān)聯(lián)另一個(gè)表(維表)的主鍵。比如訂單表中的客戶號和產(chǎn)品號分別關(guān)聯(lián)客戶表、產(chǎn)品表的主鍵?,F(xiàn)實(shí)運(yùn)算中這種關(guān)聯(lián)可能多達(dá)七八個(gè)甚至十幾個(gè)表,還可能出現(xiàn)多層的關(guān)聯(lián)。SQL 數(shù)據(jù)庫通常使用 HASH JOIN 算法來做內(nèi)存連接,需要計(jì)算和比對 HASH 值,過程中還會占用內(nèi)存來存儲中間結(jié)果,關(guān)聯(lián)表很多時(shí)計(jì)算性能就會急劇下降。
其實(shí),我們也可以利用內(nèi)存指針引用機(jī)制事先做好關(guān)聯(lián)。在系統(tǒng)初始化階段,把事實(shí)表中的關(guān)聯(lián)字段值轉(zhuǎn)換為對應(yīng)維表記錄的指針。因?yàn)榫S表的關(guān)聯(lián)字段是主鍵,所以關(guān)聯(lián)記錄唯一,將外鍵值轉(zhuǎn)換成記錄指針不會引起錯(cuò)誤。在后續(xù)計(jì)算中,需要引用維表字段時(shí),可以用指針直接引用,無需計(jì)算和比對 HASH 值,也不需要再存儲中間結(jié)果,從而獲得更優(yōu)的性能。SQL 沒有記錄指針這種數(shù)據(jù)類型,也就無法實(shí)現(xiàn)預(yù)關(guān)聯(lián)了。
SPL 則從原理上支持并實(shí)現(xiàn)了這種預(yù)關(guān)聯(lián)機(jī)制。例如,完成訂單表和客戶表、產(chǎn)品表預(yù)關(guān)聯(lián)的代碼大致是這樣:
A | |
---|---|
1 | =file(“customer.btx”).import@b().keys@i(cid) |
2 | =file(“product.btx”).import@b().keys@i(pid) |
3 | =file(“orders.btx”).import@b().switch(cid,A1;pid,A2) |
4 | >env(orders,A3) |
A1、A2 加載客戶表和產(chǎn)品表。
A3:加載訂單表,將其中的客戶號 cid、產(chǎn)品號 pid 轉(zhuǎn)換為對應(yīng)維表記錄的指針。
A4:將完成預(yù)關(guān)聯(lián)的訂單表存入全局變量,供后續(xù)計(jì)算使用。
系統(tǒng)運(yùn)行時(shí),按照產(chǎn)品供應(yīng)商過濾訂單,再按客戶所在城市分組匯總的代碼大致是下面這樣:
A | |
---|---|
1 | =orders.select(pid.supplier==“raqsoft.com”).groups(cid.city;sum(pid.price*quantity)) |
訂單表中的 pid 已經(jīng)轉(zhuǎn)換為產(chǎn)品表記錄的指針,所以可以直接用“.”操作符引用產(chǎn)品表記錄。 不僅書寫更簡單,而且運(yùn)算性能也快得多。
只是兩、三個(gè)表關(guān)聯(lián)時(shí),預(yù)關(guān)聯(lián)和 HASH JOIN 的差別還不是非常明顯。這是因?yàn)殛P(guān)聯(lián)并不是最終目的,之后還會有其它很多運(yùn)算,關(guān)聯(lián)本身運(yùn)算消耗時(shí)間的占比相對不大。但如果關(guān)聯(lián)情況比較復(fù)雜,涉及的表很多,以及有多層的時(shí)候(比如訂單關(guān)聯(lián)產(chǎn)品,產(chǎn)品關(guān)聯(lián)供應(yīng)商,供應(yīng)商關(guān)聯(lián)城市,城市關(guān)聯(lián)國家等等),預(yù)關(guān)聯(lián)的性能優(yōu)勢會更明顯。
序號定位
與外存相比,內(nèi)存的另一個(gè)重要特征是支持高速的隨機(jī)訪問,可以快速從內(nèi)存表中按指定序號(也就是位置)取出數(shù)據(jù)。在做查找計(jì)算時(shí),如果被查找的值正好是目標(biāo)值在內(nèi)存表中的序號,或者很容易通過被查找值計(jì)算出目標(biāo)值的序號,我們就可以用序號直接取目標(biāo)記錄。這種方法不需要進(jìn)行任何比對就能直接取出查找結(jié)果,性能不僅遠(yuǎn)遠(yuǎn)好于遍歷查找,也好于使用索引的查找算法。
但是,SQL 以無序集合為基礎(chǔ),不能按序號取成員,只能用序號去查找。如果沒有索引就只能遍歷查找,會非常慢。即使有索引也要計(jì)算 HASH 值或用二分法查找,速度也比不上直接定位。而且,建立索引也會占用昂貴的內(nèi)存。如果數(shù)據(jù)表中沒有序號還要先排序再硬造個(gè)序號時(shí),性能就會更差。
SPL 以有序集合為基礎(chǔ),提供序號定位功能。比如訂單表中的訂單號是從 1 開始的自然數(shù)。在查找訂單號 i 時(shí),直接取訂單表中的第 i 條記錄就行了。再比如數(shù)據(jù)表 T 從 2000 年到 2022 年每天存儲一條數(shù)據(jù),現(xiàn)在需要查詢指定日期的記錄。日期雖然不是目標(biāo)值的序號,但是我們可以先算出指定日期距離起始日期的天數(shù)。這就是目標(biāo)值的序號,然后再用序號取 T 表記錄就可以了。對表 T 用序號定位查找 2022 年 4 月 20 日記錄的代碼,大致是下面這樣:
A | |
---|---|
1 | =date(2022,12,31)-date(1999,12,31) |
2 | =T_orginal.align@b(to(A1),dt-date(1999,12,31)) |
3 | =env(T,A5) |
4 | =T(date(2021,4,20)-date(1999,12,31)) |
A1:計(jì)算出 2000 年到 2022 年總天數(shù)是 8401 天。
A2:用原始的 T 表記錄計(jì)算出距離起始日期的天數(shù),再和 to(A1)這個(gè)自然數(shù)集合 [1,2,3,…,8401] 對齊,空缺的日期會用 null 補(bǔ)齊。align 的 @b 選項(xiàng)表示對齊時(shí)將使用二分法來查找位置,這樣完成對齊動作也會更快一點(diǎn)。
A3:計(jì)算好的結(jié)果,放到全局變量 T 中。
A4:要查找 2021 年 4 月 20 日記錄,求出這個(gè)日期和起始日期距離 7781 天,直接取出 T 表中第 7781 條記錄就可以了。
A1 到 A3 是對齊計(jì)算,用于處理空缺的日期,可以放在系統(tǒng)初始化階段。在查找計(jì)算時(shí),用 A4 中的序號定位代碼就能得到查找結(jié)果,實(shí)際查找的日期可以作為參數(shù)傳入。
集群維表
當(dāng)數(shù)據(jù)量太大,超出單機(jī)內(nèi)存時(shí),就要使用集群來加載這些數(shù)據(jù)。許多內(nèi)存數(shù)據(jù)庫也支持分布式計(jì)算,通常是將數(shù)據(jù)分成多段,分別加載到集群不同分機(jī)的內(nèi)存中。
JOIN 是分布式計(jì)算的一個(gè)麻煩任務(wù),會涉及多個(gè)分機(jī)之間的數(shù)據(jù)傳輸。嚴(yán)重的時(shí)候,傳輸造成的延遲會抵消集群分?jǐn)傆?jì)算量得到的好處,會出現(xiàn)集群變大反而性能并不能提升的現(xiàn)象。
SQL 體系下的分布式數(shù)據(jù)庫,通常是將單機(jī) HASH JOIN 方法擴(kuò)展到集群上。每個(gè)分機(jī)根據(jù) HASH 值將本機(jī)數(shù)據(jù)分發(fā)到其他分機(jī),確保相關(guān)聯(lián)的數(shù)據(jù)在同一分機(jī)上。然后再在各個(gè)分機(jī)上做單機(jī)連接。但是,HASH 方法在運(yùn)氣不好的時(shí)候,可能會造成數(shù)據(jù)分配的嚴(yán)重不均衡,需要借助外存來緩存這些分發(fā)到的數(shù)據(jù),否則可能因?yàn)閮?nèi)存溢出而導(dǎo)致系統(tǒng)崩潰。但是,內(nèi)存數(shù)據(jù)庫的主要特征就是將數(shù)據(jù)加載到內(nèi)存中計(jì)算,出現(xiàn)外存緩存會嚴(yán)重拖慢計(jì)算性能。
實(shí)際上,外鍵關(guān)聯(lián)的事實(shí)表和維表有很大區(qū)別。事實(shí)表一般都比較大,要用各個(gè)分機(jī)內(nèi)存分段加載才能裝的下。正好事實(shí)表也比較適合分段,每個(gè)分段的數(shù)據(jù)都相互獨(dú)立,分機(jī)之間不需要相互訪問。而維表記錄則會被隨機(jī)訪問,事實(shí)表的任何一個(gè)分段都可能關(guān)聯(lián)全部維表記錄。我們可以利用事實(shí)表和維表的區(qū)別,對集群的外鍵關(guān)聯(lián)提速。
如果維表比較小,則將維表全量數(shù)據(jù)復(fù)制到所有分機(jī)內(nèi)存中。這樣,每個(gè)分機(jī)中的事實(shí)表分段和全量維表就可以繼續(xù)完成預(yù)關(guān)聯(lián),完全避免了關(guān)聯(lián)過程中的網(wǎng)絡(luò)傳輸。
如果維表也很大,單機(jī)內(nèi)存放不下,只能在各分機(jī)內(nèi)存中分段加載。這時(shí),沒有一個(gè)分機(jī)上有全量的維表,外鍵關(guān)聯(lián)計(jì)算就無法避免網(wǎng)絡(luò)傳輸了。不過傳輸內(nèi)容并不算很大,只涉及事實(shí)表的外鍵和維表關(guān)聯(lián)記錄的字段,事實(shí)表其它字段不需要傳輸,計(jì)算可以直接完成,過程中也不會產(chǎn)生緩存數(shù)據(jù)。
SPL 從原理上區(qū)分維表和事實(shí)表,針對維表較小和維表較大兩種情況,分別提供了維表復(fù)制機(jī)制和分段維表機(jī)制,實(shí)現(xiàn)了上述算法,能顯著提高集群情況下外鍵關(guān)聯(lián)的計(jì)算性能。
備胎式容錯(cuò)
集群系統(tǒng)必須要考慮容錯(cuò),內(nèi)存數(shù)據(jù)的容錯(cuò)和外存是不同的。外存一般使用副本的方法,即同一份數(shù)據(jù)有多個(gè)副本,某個(gè)分機(jī)失效后仍然能在其它分機(jī)找到數(shù)據(jù)。這種機(jī)制的存儲利用率很低,只有 1/k(k 是副本數(shù)量)。
但是,對于內(nèi)存中的數(shù)據(jù),卻不能使用這種副本容錯(cuò)方法。這是因?yàn)橛脖P足夠便宜且?guī)缀蹩梢詿o限擴(kuò)容,但是內(nèi)存要昂貴的多而且擴(kuò)容有上限。只有 1/k 的內(nèi)存利用率是無法容忍的。
內(nèi)存容錯(cuò)需要不同于外存的專門手段。SPL 提供了備胎式容錯(cuò)機(jī)制,將數(shù)據(jù)分成 n 段后分別加載到 n 個(gè)分機(jī)的內(nèi)存中。然后準(zhǔn)備 k 個(gè)空閑的分機(jī)作為備用機(jī)。當(dāng)正在運(yùn)行的某個(gè)分機(jī)失效時(shí),則立即啟用某個(gè)備用機(jī),臨時(shí)加載失效分機(jī)的數(shù)據(jù),和其它分機(jī)重新組成擁有完整數(shù)據(jù)的集群繼續(xù)提供服務(wù)。失效的分機(jī)排除故障后恢復(fù)使用,可以再充當(dāng)備用機(jī)。整個(gè)過程和汽車更換備胎的模式很像。
備胎式容錯(cuò)機(jī)制的內(nèi)存利用率可以高達(dá) n/(n+k),遠(yuǎn)遠(yuǎn)高于副本式容錯(cuò)的 1/k。能加載進(jìn)內(nèi)存的數(shù)據(jù)量通常不會非常大,分機(jī)失效后臨時(shí)加載的時(shí)間并不多,集群服務(wù)就可以較快地恢復(fù)。
回顧與總結(jié)
內(nèi)存數(shù)據(jù)庫的計(jì)算體系,必須充分利用內(nèi)存的特征才能獲得極致性能。從數(shù)據(jù)計(jì)算的角度來看,內(nèi)存主要優(yōu)點(diǎn)有:支持指針引用、支持高速隨機(jī)訪問、并發(fā)讀取能力強(qiáng)。內(nèi)存的缺點(diǎn)是:成本高昂、擴(kuò)容有上限。
而 SQL 計(jì)算體系中缺乏一些必要的數(shù)據(jù)類型和運(yùn)算,比如:缺少記錄指針類型,不支持有序運(yùn)算,JOIN 定義過于籠統(tǒng),不區(qū)分 JOIN 類型等,從原理上就不能充分利用內(nèi)存的上述特征實(shí)現(xiàn)某些高速算法。基于 SQL 的內(nèi)存數(shù)據(jù)庫,通常只是簡單的照搬外存數(shù)據(jù)結(jié)構(gòu)和運(yùn)算,會出現(xiàn)各種問題。比如:記錄式復(fù)制過多消耗 CPU 和內(nèi)存;查找和 JOIN 性能沒有達(dá)到極致。再比如集群方面:內(nèi)存利用率過低;大量網(wǎng)絡(luò)傳輸導(dǎo)致分機(jī)數(shù)量增加但性能反而下降;多機(jī) JOIN 出現(xiàn)外存緩存等等。
開源數(shù)據(jù)計(jì)算引擎 SPL 擴(kuò)展了數(shù)據(jù)類型和運(yùn)算定義,可以充分利用內(nèi)存的特征,從而實(shí)現(xiàn)多種高性能算法,讓性能達(dá)到極致。其中,指針式復(fù)用利用內(nèi)存特有的指針引用機(jī)制,節(jié)省了內(nèi)存空間,而且速度更快。預(yù)關(guān)聯(lián)同樣利用指針引用機(jī)制,在初始化階段完成很耗時(shí)的外鍵關(guān)聯(lián),后續(xù)計(jì)算中直接使用關(guān)聯(lián)好的結(jié)果,計(jì)算速度顯著提高。序號定位利用有序性,充分發(fā)揮內(nèi)存高速隨機(jī)訪問的優(yōu)勢,不用做任何計(jì)算和比對,直接用序號讀取記錄,性能好于 HASH 索引等查找算法。集群維表有效避免或減少了網(wǎng)絡(luò)傳輸、避免了外存緩存,備胎式容錯(cuò)在保證高可用性的前提下,有效提高了集群內(nèi)存利用率。文章來源:http://www.zghlxwxcb.cn/news/detail-780383.html
除此之外,SPL 還提供了排號鍵、序號索引、數(shù)據(jù)類型壓縮等等其它方法。程序員可以根據(jù)具體的場景,有針對性的采用這些方法,就能充分發(fā)揮內(nèi)存的優(yōu)勢,從而有效提升內(nèi)存數(shù)據(jù)計(jì)算的性能。文章來源地址http://www.zghlxwxcb.cn/news/detail-780383.html
SPL資料
- SPL官網(wǎng)
- SPL下載
- SPL源代碼
到了這里,關(guān)于內(nèi)存數(shù)據(jù)庫如何發(fā)揮內(nèi)存優(yōu)勢?的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!