業(yè)務(wù)系統(tǒng)產(chǎn)生的明細(xì)數(shù)據(jù)通常要經(jīng)過加工處理,按照一定邏輯計(jì)算成需要的結(jié)果,用以支持企業(yè)的經(jīng)營活動(dòng)。這類數(shù)據(jù)加工任務(wù)一般會(huì)有很多個(gè),需要批量完成計(jì)算,在銀行和保險(xiǎn)行業(yè)常常被稱為跑批,其它像石油、電力等行業(yè)也經(jīng)常會(huì)有跑批的需求。
大部分業(yè)務(wù)統(tǒng)計(jì)都會(huì)要求以某日作為截止點(diǎn),而且為了不影響生產(chǎn)系統(tǒng)的運(yùn)行,跑批任務(wù)一般會(huì)在夜間進(jìn)行,這時(shí)候才能將生產(chǎn)系統(tǒng)當(dāng)天產(chǎn)生的新明細(xì)數(shù)據(jù)導(dǎo)出來,送到專門的數(shù)據(jù)庫或數(shù)據(jù)倉庫完成跑批計(jì)算。第二天早上,跑批結(jié)果就可以提供給業(yè)務(wù)人員使用了。
和在線查詢不同,跑批計(jì)算是定時(shí)自動(dòng)執(zhí)行的離線任務(wù),不會(huì)出現(xiàn)多人同時(shí)訪問一個(gè)任務(wù)的情況,所以沒有并發(fā)問題,也不必實(shí)時(shí)返回結(jié)果。但是,跑批必須在規(guī)定的窗口時(shí)間內(nèi)完成。比如某銀行的跑批窗口時(shí)間是晚上8:00到第二天早上7:00,如果到了早上7:00跑批任務(wù)還沒有完成,就會(huì)造成業(yè)務(wù)人員無法正常工作的嚴(yán)重后果。
跑批任務(wù)涉及的數(shù)據(jù)量非常大,很可能用到所有的歷史數(shù)據(jù),而且計(jì)算邏輯復(fù)雜、步驟眾多,所以跑批時(shí)間經(jīng)常是以小時(shí)計(jì)的,一個(gè)任務(wù)兩三小時(shí)是家常便飯,跑到十個(gè)小時(shí)也不足為奇。隨著業(yè)務(wù)的發(fā)展,數(shù)據(jù)量還在不斷增加。跑批數(shù)據(jù)庫的負(fù)擔(dān)快速增長,就會(huì)發(fā)生整晚都跑不完的情況,嚴(yán)重影響用戶的業(yè)務(wù),這是無法接受的。
問題分析
要解決跑批時(shí)間過長的問題,必須仔細(xì)分析現(xiàn)有的系統(tǒng)架構(gòu)中的問題。
跑批系統(tǒng)比較典型的架構(gòu)大致如下圖:
從圖上看,數(shù)據(jù)要從生產(chǎn)數(shù)據(jù)庫取出,存入跑批數(shù)據(jù)庫。跑批數(shù)據(jù)庫通常是關(guān)系型的,編寫存儲(chǔ)過程代碼完成跑批計(jì)算。跑批的結(jié)果一般不會(huì)直接使用,而是再從跑批數(shù)據(jù)庫中導(dǎo)出,采用接口文件的方式提供給其他系統(tǒng),或者再導(dǎo)入其他系統(tǒng)數(shù)據(jù)庫。這是比較典型的架構(gòu),圖中的生產(chǎn)數(shù)據(jù)庫也可能是某個(gè)中央數(shù)據(jù)倉庫或者Hadoop等。一般情況下,生產(chǎn)庫和跑批庫不會(huì)是同一種數(shù)據(jù)庫,它們之間往往通過文件的方式傳遞數(shù)據(jù),這樣也比較有利于降低耦合度。跑批計(jì)算完成后,結(jié)果要給多個(gè)應(yīng)用系統(tǒng)使用,一般也都是以文件方式傳遞。
跑批很慢的第一個(gè)原因,是用來完成跑批任務(wù)的關(guān)系數(shù)據(jù)庫入庫、出庫太慢。由于關(guān)系數(shù)據(jù)庫的存儲(chǔ)和計(jì)算能力具有封閉性,數(shù)據(jù)的進(jìn)出要做過多的約束檢查和安全處理,當(dāng)數(shù)據(jù)量較大時(shí),寫入讀出的效率非常低,耗時(shí)會(huì)非常長。所以,跑批數(shù)據(jù)庫導(dǎo)入文件數(shù)據(jù)的過程,以及跑批計(jì)算結(jié)果再導(dǎo)出文件的過程都會(huì)很慢。
跑批很慢的第二個(gè)原因,是存儲(chǔ)過程性能差。由于SQL的語法體系過于陳舊,存在諸多限制,很多高效的算法無法實(shí)施,所以存儲(chǔ)過程中的SQL語句計(jì)算性能很不理想。而且,業(yè)務(wù)邏輯比較復(fù)雜的時(shí)候很難用一個(gè)SQL實(shí)現(xiàn),經(jīng)常要分成多個(gè)步驟,用十幾甚至幾十個(gè)SQL語句才能完成。每個(gè)SQL的中間結(jié)果,都要存入臨時(shí)表給后續(xù)步驟的SQL使用。臨時(shí)表數(shù)據(jù)量較大時(shí)就必須落地,會(huì)造成大量的數(shù)據(jù)寫出。而數(shù)據(jù)庫的寫出要比讀入性能差很多,會(huì)嚴(yán)重拖慢整個(gè)存儲(chǔ)過程。
對于更復(fù)雜的計(jì)算,甚至很難用SQL語句直接實(shí)現(xiàn),需要用數(shù)據(jù)庫游標(biāo)遍歷取出數(shù)據(jù),循環(huán)計(jì)算。但數(shù)據(jù)庫游標(biāo)遍歷計(jì)算性能又要比SQL語句差很多,一般也都不直接支持多線程并行計(jì)算,很難利用多CPU核的計(jì)算能力,會(huì)讓計(jì)算性能更加糟糕。
那么,是否可以考慮用分布式數(shù)據(jù)庫來代替?zhèn)鹘y(tǒng)關(guān)系數(shù)據(jù)庫,通過增加節(jié)點(diǎn)數(shù)量的辦法,來提高跑批任務(wù)的速度呢?
答案仍然是不可行。主要原因是跑批計(jì)算的邏輯相當(dāng)復(fù)雜,即使是用傳統(tǒng)數(shù)據(jù)庫的存儲(chǔ)過程,也常常要寫幾千甚至上萬行代碼,而分布式數(shù)據(jù)庫的存儲(chǔ)過程計(jì)算能力還比較弱,很難實(shí)現(xiàn)這么復(fù)雜的跑批計(jì)算。
而且,當(dāng)復(fù)雜計(jì)算任務(wù)不得不分成多個(gè)步驟時(shí),分布式數(shù)據(jù)庫也面臨中間結(jié)果落地的問題。由于數(shù)據(jù)可能在不同的節(jié)點(diǎn)上,所以前序步驟將中間結(jié)果落地,后續(xù)步驟再讀取的時(shí)候,都會(huì)造成大量跨網(wǎng)絡(luò)的讀寫操作,性能很不可控。
這時(shí),也不能采用分布式數(shù)據(jù)庫依靠數(shù)據(jù)冗余來提升查詢速度的辦法。這是因?yàn)椋樵冎翱梢灶A(yù)先準(zhǔn)備好多份冗余數(shù)據(jù),但是,跑批的中間結(jié)果是臨時(shí)生成的,如果冗余的話就要臨時(shí)生成多份,整體的性能只會(huì)變得更慢。
所以,現(xiàn)實(shí)的跑批業(yè)務(wù)通常仍然是使用大型單體數(shù)據(jù)庫進(jìn)行,計(jì)算強(qiáng)度太大時(shí)會(huì)采用類似ExaData這樣的一體機(jī)(ExaData是多數(shù)據(jù)庫,但被Oracle專門優(yōu)化過,可以看成是個(gè)超大型單體數(shù)據(jù)庫)。雖然很慢,但是暫時(shí)找不到更好的選擇,只有這類大型數(shù)據(jù)庫有足夠的計(jì)算能力,所以只能用它來完成跑批任務(wù)了。
SPL用于跑批
開源的專業(yè)計(jì)算引擎SPL提供了不依賴數(shù)據(jù)庫的計(jì)算能力,直接利用文件系統(tǒng)計(jì)算,可以解決關(guān)系數(shù)據(jù)庫出庫入庫太慢的問題。而且SPL實(shí)現(xiàn)了更優(yōu)算法,性能遠(yuǎn)遠(yuǎn)超過存儲(chǔ)過程,能顯著提高單機(jī)計(jì)算效率,非常適合跑批計(jì)算。
利用SPL實(shí)現(xiàn)的跑批系統(tǒng)新架構(gòu)是下面這樣的:
在新架構(gòu)中,SPL解決了造成跑批慢的兩大瓶頸問題。
首先來看數(shù)據(jù)的入庫、出庫問題。SPL可以直接基于生產(chǎn)庫導(dǎo)出的文件計(jì)算,不必再將數(shù)據(jù)導(dǎo)入到關(guān)系數(shù)據(jù)庫中。完成跑批計(jì)算后,SPL還能將最終結(jié)果直接存儲(chǔ)成文本文件等通用格式,傳遞給其他應(yīng)用系統(tǒng),避免了原有跑批數(shù)據(jù)庫的出庫操作。這樣一來,SPL就省去了關(guān)系數(shù)據(jù)庫緩慢的入庫、出庫過程。
下面再來看計(jì)算的過程。SPL提供了更優(yōu)的算法(有許多是業(yè)界首創(chuàng)),計(jì)算性能遠(yuǎn)遠(yuǎn)超過存儲(chǔ)過程和SQL語句。這些高性能算法包括:
這些高性能算法可以應(yīng)用于跑批任務(wù)中的常見JOIN計(jì)算、遍歷、分組匯總等,能有效提升計(jì)算速度。例如,跑批任務(wù)常常要遍歷整個(gè)歷史表。有些情況下,對一個(gè)歷史表還要遍歷好多次,來完成多種業(yè)務(wù)邏輯的計(jì)算。歷史表數(shù)據(jù)量一般都很大,每次遍歷都要消耗很多的時(shí)間。此時(shí)我們可以應(yīng)用SPL的遍歷復(fù)用機(jī)制,僅對大表遍歷一次,就可以同時(shí)完成多種計(jì)算,可以節(jié)省大量時(shí)間。
SPL的多路游標(biāo)能做到數(shù)據(jù)的并行讀取和計(jì)算,即使是很復(fù)雜的跑批邏輯,也可以利用多CPU核實(shí)現(xiàn)多線程并行運(yùn)算。而數(shù)據(jù)庫游標(biāo)是很難并行的,這樣一來,SPL的計(jì)算速度常常可以達(dá)到存儲(chǔ)過程的數(shù)倍。
SPL的延遲游標(biāo)機(jī)制,可以在一個(gè)游標(biāo)上定義多個(gè)計(jì)算步驟,之后讓數(shù)據(jù)流按順序依次完成這些步驟,實(shí)現(xiàn)鏈?zhǔn)接?jì)算,能夠有效減少中間結(jié)果落地的次數(shù)。在數(shù)據(jù)必須落地的情況下,SPL也可以將中間結(jié)果存成內(nèi)置的高性能數(shù)據(jù)格式,供下一個(gè)步驟使用。SPL高性能存儲(chǔ)基于文件,采用有序壓縮存儲(chǔ)、自由列式存儲(chǔ)、倍增分段、自有壓縮編碼等技術(shù),減少了硬盤占用,讀寫速度要遠(yuǎn)遠(yuǎn)好于數(shù)據(jù)庫。
應(yīng)用效果
SPL在技術(shù)架構(gòu)上打破了關(guān)系型跑批數(shù)據(jù)庫存在的兩大瓶頸,在實(shí)際應(yīng)用中也取得了非常好的效果。
L 銀行跑批任務(wù)采用傳統(tǒng)架構(gòu),以關(guān)系數(shù)據(jù)庫作為跑批數(shù)據(jù)庫,用存儲(chǔ)過程編程實(shí)現(xiàn)跑批邏輯。其中,貸款協(xié)議存儲(chǔ)過程需要執(zhí)行 2 個(gè)小時(shí),而且是很多其他跑批任務(wù)的前序任務(wù),耗時(shí)這么久,對整個(gè)跑批任務(wù)造成了嚴(yán)重影響。
采用SPL后,使用高性能列存、文件游標(biāo)、多線程并行、小結(jié)果內(nèi)存分組、游標(biāo)復(fù)用等高性能算法和存儲(chǔ)機(jī)制,將原來2個(gè)小時(shí)的計(jì)算時(shí)間縮短為10分鐘,性能提高12倍。
而且,SPL代碼更簡潔。原存儲(chǔ)過程3300多行,改為SPL后,僅有500格語句,代碼量減少了6倍多,大大提高了開發(fā)效率。
P保險(xiǎn)公司的車險(xiǎn)業(yè)務(wù)中,需要用往年歷史保單來關(guān)聯(lián)新的保單,在跑批中稱為歷史保單關(guān)聯(lián)任務(wù)。原來也采用關(guān)系數(shù)據(jù)庫完成跑批,存儲(chǔ)過程計(jì)算10天的新增保單關(guān)聯(lián)歷史保單,運(yùn)行時(shí)間47分鐘;30天則需要112分鐘,接近2小時(shí);如果日期跨度更大,運(yùn)行時(shí)間就會(huì)長的無法忍受,基本就變成不可能完成的任務(wù)了。
采用SPL后,應(yīng)用了高性能文件存儲(chǔ)、文件游標(biāo)、有序歸并分段取出、內(nèi)存關(guān)聯(lián)和遍歷復(fù)用等技術(shù),計(jì)算10天新增保單僅需13分鐘;30天新增保單只需要17分鐘,速度提高了近7倍。而且,新算法執(zhí)行的時(shí)間隨著保單天數(shù)的增長并不是很大,并沒有像存儲(chǔ)過程那樣成正比的增長。
從代碼總量來看,原來存儲(chǔ)過程有2000行代碼,去掉注釋后還有1800多行,而SPL的全部代碼只有不到500格,不到原來的1/3。
T銀行通過互聯(lián)網(wǎng)渠道發(fā)放貸款的明細(xì)數(shù)據(jù),需要每天執(zhí)行跑批任務(wù),統(tǒng)計(jì)匯總指定日期之前的所有歷史數(shù)據(jù)。跑批任務(wù)采用關(guān)系數(shù)據(jù)庫的SQL語句實(shí)現(xiàn),運(yùn)行總時(shí)間7.8小時(shí),占用了過多的跑批時(shí)間,甚至影響了其他的跑批任務(wù),必須優(yōu)化。
采用SPL后,應(yīng)用了高性能文件、文件游標(biāo)、有序分組、有序關(guān)聯(lián)、延遲游標(biāo)、二分法等技術(shù),原來需要7.8小時(shí)的跑批任務(wù),單線程僅需180秒,2線程僅需137秒,速度提高了204倍。