自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

1號(hào)店訂單系統(tǒng)水平分庫的實(shí)踐之路以及關(guān)鍵步驟

開發(fā) 架構(gòu)
隨著大型互聯(lián)網(wǎng)應(yīng)用的發(fā)展,海量數(shù)據(jù)的存儲(chǔ)和訪問成為系統(tǒng)設(shè)計(jì)的瓶頸,分布式處理成為不二選擇。數(shù)據(jù)庫拆分,特別是水平分庫是個(gè)高難度的活,涉及一系列技術(shù)決策。

隨著大型互聯(lián)網(wǎng)應(yīng)用的發(fā)展,海量數(shù)據(jù)的存儲(chǔ)和訪問成為系統(tǒng)設(shè)計(jì)的瓶頸,分布式處理成為不二選擇。數(shù)據(jù)庫拆分,特別是水平分庫是個(gè)高難度的活,涉及一系列技術(shù)決策。

本人有幸負(fù)責(zé)1號(hào)店訂單水平分庫的方案設(shè)計(jì)及實(shí)施落地,本人結(jié)合項(xiàng)目實(shí)踐,對(duì)水平分庫做一個(gè)系統(tǒng)地剖析,希望為大家水平分庫(包括去IOE)改造提供總體思路,主要內(nèi)容包括:

[[184540]]

水平分庫說明

分庫維度-- 根據(jù)哪個(gè)字段分庫

分庫策略-- 記錄如何分配到不同庫

分庫數(shù)量-- 初始庫數(shù)量及庫數(shù)量如何增長

路由透明-- 如何實(shí)現(xiàn)庫路由,支持應(yīng)用透明

分頁處理-- 跨多個(gè)庫的分頁case如何處理

Lookup映射—非分庫字段映射到分庫字段,實(shí)現(xiàn)單庫訪問

整體架構(gòu)-- 分庫的整體技術(shù)架構(gòu)

上線步驟-- 分庫改造實(shí)施上線

項(xiàng)目總結(jié)

水平分庫說明

數(shù)據(jù)庫拆分有兩種:

1)   垂直分庫

數(shù)據(jù)庫里的表太多,拿出部分到新的庫里,一般是根據(jù)業(yè)務(wù)劃分表,關(guān)系密切的表放同一數(shù)據(jù)庫,應(yīng)用修改數(shù)據(jù)庫連接即可,比較簡單。

2)   水平分庫

某張表太大,單個(gè)數(shù)據(jù)庫存儲(chǔ)不下或訪問性能有壓力,把一張表拆成多張,每張表存放部分記錄,保存在不同的數(shù)據(jù)庫里,水平分庫需要對(duì)系統(tǒng)做大的改造。

[[184542]]

1號(hào)店核心的訂單表存儲(chǔ)在Oracle數(shù)據(jù)庫,記錄有上億條,字段有上百個(gè),訪問的模式也是復(fù)雜多樣,隨著業(yè)務(wù)快速增長,無論存儲(chǔ)空間或訪問性能都面臨巨大挑戰(zhàn),特別在大促時(shí),訂單庫已成為系統(tǒng)瓶頸。

通常有兩種解決辦法:

Scale up,升級(jí)Oracle數(shù)據(jù)庫所在的物理機(jī),提升內(nèi)存/存儲(chǔ)/IO性能,但這種升級(jí)費(fèi)用昂貴,并且只能滿足短期需要。

Scale out,把訂單庫拆分為多個(gè)庫,分散到多臺(tái)機(jī)器進(jìn)行存儲(chǔ)和訪問,這種做法支持水平擴(kuò)展,可以滿足長遠(yuǎn)需要。

1號(hào)店采取后一種做法,它的訂單庫主要包括訂單主表/訂單明細(xì)表(記錄商品明細(xì))/訂單擴(kuò)展表,水平分庫即把這3張表的記錄分到多個(gè)數(shù)據(jù)庫中,訂單水平分庫效果如下圖所示:

 

原來一個(gè)Oracle庫被多個(gè)MySQL庫取代,支持1主多備和讀寫分離,主備之間通過MySQL自帶的數(shù)據(jù)同步機(jī)制(SLA<1秒),所有應(yīng)用通過訂單服務(wù)訪問訂單數(shù)據(jù)。

分庫維度

水平分庫首先要考慮根據(jù)哪個(gè)字段作為分庫維度,選擇標(biāo)準(zhǔn)是盡量避免應(yīng)用代碼和SQL性能受影響,這就要求當(dāng)前SQL在分庫后,訪問盡量落在單個(gè)庫里,否則單庫訪問變成多庫掃描,讀寫性能和應(yīng)用邏輯都會(huì)受較大影響。

對(duì)于訂單拆分,大家首先想到的是按照用戶Id拆分,結(jié)論沒錯(cuò),但***還是數(shù)據(jù)說話,不能拍腦袋。好的做法是首先收集所有SQL,挑選where語句最常出現(xiàn)的過濾字段,比如用戶Id/訂單Id/商家Id,每個(gè)字段在SQL中有三種情況:

單Id過濾,如用戶Id=?

多Id過濾,如用戶Id IN (?,?,?)

該Id不出現(xiàn)

然后進(jìn)一步統(tǒng)計(jì),假設(shè)共有500個(gè)SQL訪問訂單庫,3個(gè)過濾字段出現(xiàn)情況如下:

過濾字段單Id過濾多Id過濾不出現(xiàn)

用戶Id12040330

訂單Id6080360

商家Id150485

結(jié)論明顯,應(yīng)該選擇用戶Id進(jìn)行分庫。

等一等,這只是靜態(tài)分析,每個(gè)SQL訪問的次數(shù)是不一樣的,因此還要分析每個(gè)SQL的訪問量。我們分析了Top15執(zhí)行最多的SQL (它們占總執(zhí)行次數(shù)85%),如果按照用戶Id分庫,這些SQL 85%落到單個(gè)數(shù)據(jù)庫, 13%落到多個(gè)數(shù)據(jù)庫,只有2%需要遍歷所有數(shù)據(jù)庫,明顯優(yōu)于使用其他Id進(jìn)行分庫。

通過量化分析,我們知道按照用戶Id分庫是***的,同時(shí)也大致知道分庫對(duì)現(xiàn)有系統(tǒng)的影響,比如這個(gè)例子中,85%的SQL會(huì)落到單個(gè)數(shù)據(jù)庫,這部分的訪問性能會(huì)優(yōu)化,堅(jiān)定了各方對(duì)分庫的信心。

分庫策略

分庫維度確定后,如何把記錄分到各個(gè)庫里呢?一般有兩種方式:

根據(jù)數(shù)值范圍,比如用戶Id為1-9999的記錄分到***個(gè)庫,10000-20000的分到第二個(gè)庫,以此類推。

根據(jù)數(shù)值取模,比如用戶Id mod n,余數(shù)為0的記錄放到***個(gè)庫,余數(shù)為1的放到第二個(gè)庫,以此類推。

兩種分法的優(yōu)劣比較如下:

評(píng)價(jià)指標(biāo)按照范圍分庫按照Mod分庫

庫數(shù)量前期數(shù)目比較小,可以隨用戶/業(yè)務(wù)按需增長前期即根據(jù)mode因子確定庫數(shù)量,數(shù)目一般比較大

訪問性能前期庫數(shù)量小,全庫查詢消耗資源少,單庫查詢性能略差前期庫數(shù)量大,全庫查詢消耗資源多,單庫查詢性能略好

調(diào)整庫數(shù)量比較容易,一般只需為新用戶增加庫,老庫拆分也只影響單個(gè)庫困難,改變mod因子導(dǎo)致數(shù)據(jù)在所有庫之間遷移

數(shù)據(jù)熱點(diǎn)新舊用戶購物頻率有差異,有數(shù)據(jù)熱點(diǎn)問題新舊用戶均勻到分布到各個(gè)庫,無熱點(diǎn)

實(shí)踐中,為了處理簡單,選擇mod分庫的比較多。同時(shí)二次分庫時(shí),為了數(shù)據(jù)遷移方便,一般是按倍數(shù)增加,比如初始4個(gè)庫,二次分裂為8個(gè),再16個(gè)。這樣對(duì)于某個(gè)庫的數(shù)據(jù),一半數(shù)據(jù)移到新庫,剩余不動(dòng),對(duì)比每次只增加一個(gè)庫,所有數(shù)據(jù)都要大規(guī)模變動(dòng)。

補(bǔ)充下,mod分庫一般每個(gè)庫記錄數(shù)比較均勻,但也有些數(shù)據(jù)庫,存在超級(jí)Id,這些Id的記錄遠(yuǎn)遠(yuǎn)超過其他Id,比如在廣告場景下,某個(gè)大廣告主的廣告數(shù)可能占總體很大比例。如果按照廣告主Id取模分庫,某些庫的記錄數(shù)會(huì)特別多,對(duì)于這些超級(jí)Id,需要提供單獨(dú)庫來存儲(chǔ)記錄。

分庫數(shù)量

分庫數(shù)量首先和單庫能處理的記錄數(shù)有關(guān),一般來說,Mysql 單庫超過5000萬條記錄,Oracle單庫超過1億條記錄,DB壓力就很大(當(dāng)然處理能力和字段數(shù)量/訪問模式/記錄長度有進(jìn)一步關(guān)系)。

在滿足上述前提下,如果分庫數(shù)量少,達(dá)不到分散存儲(chǔ)和減輕DB性能壓力的目的;如果分庫的數(shù)量多,好處是每個(gè)庫記錄少,單庫訪問性能好,但對(duì)于跨多個(gè)庫的訪問,應(yīng)用程序需要訪問多個(gè)庫,如果是并發(fā)模式,要消耗寶貴的線程資源;如果是串行模式,執(zhí)行時(shí)間會(huì)急劇增加。

***分庫數(shù)量還直接影響硬件的投入,一般每個(gè)分庫跑在單獨(dú)物理機(jī)上,多一個(gè)庫意味多一臺(tái)設(shè)備。所以具體分多少個(gè)庫,要綜合評(píng)估,一般初次分庫建議分4-8個(gè)庫。

路由透明

分庫從某種意義上來說,意味著DB schema改變了,必然影響應(yīng)用,但這種改變和業(yè)務(wù)無關(guān),所以要盡量保證分庫對(duì)應(yīng)用代碼透明,分庫邏輯盡量在數(shù)據(jù)訪問層處理。當(dāng)然完全做到這一點(diǎn)很困難,具體哪些應(yīng)該由DAL負(fù)責(zé),哪些由應(yīng)用負(fù)責(zé),這里有一些建議:

對(duì)于單庫訪問,比如查詢條件指定用戶Id,則該SQL只需訪問特定庫。此時(shí)應(yīng)該由DAL層自動(dòng)路由到特定庫,當(dāng)庫二次分裂時(shí),也只要修改mod 因子,應(yīng)用代碼不受影響。

對(duì)于簡單的多庫查詢,DAL負(fù)責(zé)匯總各個(gè)數(shù)據(jù)庫返回的記錄,此時(shí)仍對(duì)上層應(yīng)用透明。

對(duì)于帶聚合運(yùn)算的多庫查詢,如帶groupBy/orderby/min/max/avg等關(guān)鍵字,建議DAL匯總單個(gè)庫返回的結(jié)果,上層應(yīng)用做進(jìn)一步處理。一方面DAL全面支持各種case,實(shí)現(xiàn)很復(fù)雜;另一方面,從1號(hào)店實(shí)踐來看,這樣的例子不多,在上層應(yīng)用作針對(duì)性處理,更加靈活。

DAL可進(jìn)一步細(xì)分為JDBC和DAL兩層,基于JDBC層面實(shí)現(xiàn)分庫路由,系統(tǒng)開發(fā)難度大,靈活性低,目前也沒有很好的成功案例;一般是基于持久層框架進(jìn)一步封裝成DDAL(分布式數(shù)據(jù)訪問層),實(shí)現(xiàn)分庫路由,1號(hào)店DAL即基于iBatis進(jìn)行上層封裝而來。

分頁處理

分庫后,有些分頁查詢需要遍歷所有庫,這些case是分庫***的受害者L。

舉個(gè)分頁的例子,比如要求按時(shí)間順序展示某個(gè)商家的訂單,每頁100條記錄,由于是按商家查詢,需要遍歷所有數(shù)據(jù)庫,假設(shè)庫數(shù)量是8,我們來看下分頁處理邏輯:

如果取第1頁數(shù)據(jù),則需要從每個(gè)庫里按時(shí)間順序取前100條記錄,8個(gè)庫匯總后有800條,然后對(duì)這800條記錄在應(yīng)用里進(jìn)行二次排序,***取前100條。

如果取第10頁數(shù)據(jù),則需要從每個(gè)庫里取前1000(100*10)條記錄,匯總后有8000條記錄,然后對(duì)這8000條記錄二次排序后取(900,1000)條記錄。

分庫情況下,對(duì)于第k頁記錄,每個(gè)庫要多取100*(k-1)條記錄,所有庫加起來,多取的記錄更多,所以越是靠后的分頁,系統(tǒng)要耗費(fèi)更多內(nèi)存和執(zhí)行時(shí)間。

對(duì)比沒分庫的情況,無論取那一頁,都只要從單個(gè)DB里取100條記錄,而且無需在應(yīng)用內(nèi)部做二次排序,非常簡單。

那如何解決分庫情況下的分頁問題呢?有以下幾種辦法:

如果是在前臺(tái)應(yīng)用提供分頁,則限定用戶只能看前面n頁,這個(gè)限制在業(yè)務(wù)上也是合理的,一般看后面的分頁意義不大(如果一定要看,可以要求用戶縮小范圍重新查詢)。

如果是后臺(tái)批處理任務(wù)要求分批獲取數(shù)據(jù),則可以加大page size,比如每次獲取5000條記錄,有效減少分頁數(shù)(當(dāng)然離線訪問一般走備庫,避免沖擊主庫)。

分庫設(shè)計(jì)時(shí),一般還有配套大數(shù)據(jù)平臺(tái)匯總所有分庫的記錄,有些分頁查詢可以考慮走大數(shù)據(jù)平臺(tái)。

Lookup映射

分庫字段只有一個(gè),比如這里是用戶Id,但訂單表還有其他字段可唯一區(qū)分記錄,比如訂單Id,給定一個(gè)訂單Id,相應(yīng)記錄一定在某個(gè)庫里。如果盲目地查詢所有分庫,則帶來不必要的開銷,Lookup映射可根據(jù)訂單Id,找到相應(yīng)的用戶Id,從而實(shí)現(xiàn)單庫定位。

可以事先檢索所有訂單Id和用戶Id,保存在Lookup表里,Lookup表的記錄數(shù)和訂單庫記錄總數(shù)相等,但它只有2個(gè)字段,所以存儲(chǔ)和查詢性能都不是問題。實(shí)際使用時(shí),一般通過分布式緩存來優(yōu)化Lookup性能。對(duì)于新增的訂單,除了寫訂單表,同時(shí)要寫Lookup表。

整體架構(gòu)

1號(hào)店訂單水平分庫的總體技術(shù)架構(gòu)如下圖所示:

上層應(yīng)用通過訂單服務(wù)/分庫代理和DAL訪問數(shù)據(jù)庫。

代理對(duì)訂單服務(wù)實(shí)現(xiàn)功能透明,包括聚合運(yùn)算,非用戶Id到用戶Id的映射。

Lookup表用于訂單Id/用戶Id映射,保證按訂單Id訪問時(shí),可以直接落到單個(gè)庫,Cache是Lookup的內(nèi)存數(shù)據(jù)映像,提升性能,cache故障時(shí),直接訪問Lookup表。

DAL提供庫的路由,根據(jù)用戶Id定位到某個(gè)庫,對(duì)于多庫訪問,DAL支持可選的并發(fā)訪問模式,并支持簡單記錄匯總。

Lookup表初始化數(shù)據(jù)來自于現(xiàn)有分庫數(shù)據(jù),新增記錄時(shí),直接由代理異步寫入。

上線步驟

訂單表是核心業(yè)務(wù)表,它的水平拆分影響很多業(yè)務(wù),本身的技術(shù)改造也很大,很容易出紕漏,上線時(shí),必須謹(jǐn)慎考慮,1號(hào)店整個(gè)方案實(shí)施過程如下:

首先實(shí)現(xiàn)Oracle和MySQL兩套庫并行,所有數(shù)據(jù)訪問指向Oracle庫,通過數(shù)據(jù)同步程序把數(shù)據(jù)從Oracle拆分到多個(gè)MySQL分庫,比如3分鐘增量同步一次。

按照上述架構(gòu)圖搭建整個(gè)體系,選擇幾個(gè)對(duì)數(shù)據(jù)實(shí)時(shí)性不高的訪問例子(如訪問歷史訂單),轉(zhuǎn)向MySQL分庫訪問,然后逐漸增加更多非實(shí)時(shí)case,以檢驗(yàn)整套體系可行性。

如果性能和功能都沒問題,再一次性把所有實(shí)時(shí)讀寫訪問轉(zhuǎn)向MySQL,廢棄Oracle。

這個(gè)上線步驟多了數(shù)據(jù)同步程序的開發(fā)(大約1人周工作量,風(fēng)險(xiǎn)很低),但分散了風(fēng)險(xiǎn),把***步的技術(shù)風(fēng)險(xiǎn)(Lookup/DAL等基礎(chǔ)設(shè)施改造)和第二步的業(yè)務(wù)功能風(fēng)險(xiǎn)(Oracle改MySQL語法)分開。1號(hào)店兩階段上線都是一次性成功,特別是第二階段上線,100多個(gè)依賴方應(yīng)用簡單重啟即完成升級(jí),中間沒有出現(xiàn)一例較大問題。

項(xiàng)目總結(jié)

1號(hào)店完成訂單水平分庫的同時(shí),把訂單庫從Oralce遷到MySQL,設(shè)備從小型機(jī)換成X86服務(wù)器,通過水平分庫和去IOE,不但支持訂單量未來增長,并且總體成本也大幅下降。

由于去IOE和訂單分庫一起實(shí)施,帶來雙重的性能影響,我們花了很大精力做性能測試,為了模擬真實(shí)場景,大家通過Tcpcopy把線上實(shí)際的查詢流量引到測試環(huán)境,先后經(jīng)過13輪的性能測試,最終6個(gè)MySQL庫相對(duì)一個(gè)Oracle,平均SQL執(zhí)行時(shí)間基本持平,性能不降低的情況下,優(yōu)化了架構(gòu),節(jié)省了成本。

對(duì)核心表做水平分庫之前,必須先做好服務(wù)化,即外部系統(tǒng)通過統(tǒng)一的訂單服務(wù)訪問相關(guān)表,不然很容易遺漏一些SQL。

1號(hào)店最終是根據(jù)用戶Id后三位取模,初始分6個(gè)庫,理論上支持多達(dá)768個(gè)庫,并且對(duì)訂單Id生成規(guī)則做了改造,使其包括用戶Id后三位,這樣新訂單Id本身包含庫定位所需信息,無需走Lookup機(jī)制,隨著老訂單歸檔到歷史庫,上述架構(gòu)中l(wèi)ookup部分可廢棄。

水平分庫是一項(xiàng)系統(tǒng)性工作,首先需要在理論模式指導(dǎo)下,結(jié)合實(shí)際情況,每個(gè)方面做出***選擇。其次對(duì)于特殊場景,如跨庫分頁,沒有銀彈,可以靈活處理,不走常規(guī)路。***控制好節(jié)奏,系統(tǒng)改造、數(shù)據(jù)遷移、上線實(shí)施等各個(gè)環(huán)節(jié)做好銜接,全局一盤棋。

大膽設(shè)計(jì),小心求證,謹(jǐn)慎實(shí)施,分庫并不難。

作者介紹

王慶友,前1號(hào)店***架構(gòu)師,先后就職于ebay、騰訊、1號(hào)店等公司,精通電商業(yè)務(wù),擅長復(fù)雜系統(tǒng)業(yè)務(wù)建模和架構(gòu)分析,同時(shí)在構(gòu)建大規(guī)模的分布式系統(tǒng)方 面有豐富實(shí)踐,尤其在大型系統(tǒng)的SOA改造方面有很深入的理論和實(shí)踐,目前在尋找合作機(jī)會(huì),微信號(hào)Brucetwins,個(gè)人公眾號(hào)”架構(gòu)之道”,歡迎一起聊架構(gòu)。隨著大型互聯(lián)網(wǎng)應(yīng)用的發(fā)展,海量數(shù)據(jù)的存儲(chǔ)和訪問成為系統(tǒng)設(shè)計(jì)的瓶頸,分布式處理成為不二選擇。數(shù)據(jù)庫拆分,特別是水平分庫是個(gè)高難度的活,涉及一系列技術(shù)決策。

本人有幸負(fù)責(zé)1號(hào)店訂單水平分庫的方案設(shè)計(jì)及實(shí)施落地,本人結(jié)合項(xiàng)目實(shí)踐,對(duì)水平分庫做一個(gè)系統(tǒng)地剖析,希望為大家水平分庫(包括去IOE)改造提供總體思路,主要內(nèi)容包括:

水平分庫說明

分庫維度-- 根據(jù)哪個(gè)字段分庫

分庫策略-- 記錄如何分配到不同庫

分庫數(shù)量-- 初始庫數(shù)量及庫數(shù)量如何增長

路由透明-- 如何實(shí)現(xiàn)庫路由,支持應(yīng)用透明

分頁處理-- 跨多個(gè)庫的分頁case如何處理

Lookup映射—非分庫字段映射到分庫字段,實(shí)現(xiàn)單庫訪問

整體架構(gòu)-- 分庫的整體技術(shù)架構(gòu)

上線步驟-- 分庫改造實(shí)施上線

項(xiàng)目總結(jié)

水平分庫說明

數(shù)據(jù)庫拆分有兩種:

1)   垂直分庫

數(shù)據(jù)庫里的表太多,拿出部分到新的庫里,一般是根據(jù)業(yè)務(wù)劃分表,關(guān)系密切的表放同一數(shù)據(jù)庫,應(yīng)用修改數(shù)據(jù)庫連接即可,比較簡單。

2)   水平分庫

某張表太大,單個(gè)數(shù)據(jù)庫存儲(chǔ)不下或訪問性能有壓力,把一張表拆成多張,每張表存放部分記錄,保存在不同的數(shù)據(jù)庫里,水平分庫需要對(duì)系統(tǒng)做大的改造。

1號(hào)店核心的訂單表存儲(chǔ)在Oracle數(shù)據(jù)庫,記錄有上億條,字段有上百個(gè),訪問的模式也是復(fù)雜多樣,隨著業(yè)務(wù)快速增長,無論存儲(chǔ)空間或訪問性能都面臨巨大挑戰(zhàn),特別在大促時(shí),訂單庫已成為系統(tǒng)瓶頸。

通常有兩種解決辦法:

Scale up,升級(jí)Oracle數(shù)據(jù)庫所在的物理機(jī),提升內(nèi)存/存儲(chǔ)/IO性能,但這種升級(jí)費(fèi)用昂貴,并且只能滿足短期需要。

Scale out,把訂單庫拆分為多個(gè)庫,分散到多臺(tái)機(jī)器進(jìn)行存儲(chǔ)和訪問,這種做法支持水平擴(kuò)展,可以滿足長遠(yuǎn)需要。

1號(hào)店采取后一種做法,它的訂單庫主要包括訂單主表/訂單明細(xì)表(記錄商品明細(xì))/訂單擴(kuò)展表,水平分庫即把這3張表的記錄分到多個(gè)數(shù)據(jù)庫中,訂單水平分庫效果如下圖所示:

原來一個(gè)Oracle庫被多個(gè)MySQL庫取代,支持1主多備和讀寫分離,主備之間通過MySQL自帶的數(shù)據(jù)同步機(jī)制(SLA<1秒),所有應(yīng)用通過訂單服務(wù)訪問訂單數(shù)據(jù)。

分庫維度

水平分庫首先要考慮根據(jù)哪個(gè)字段作為分庫維度,選擇標(biāo)準(zhǔn)是盡量避免應(yīng)用代碼和SQL性能受影響,這就要求當(dāng)前SQL在分庫后,訪問盡量落在單個(gè)庫里,否則單庫訪問變成多庫掃描,讀寫性能和應(yīng)用邏輯都會(huì)受較大影響。

對(duì)于訂單拆分,大家首先想到的是按照用戶Id拆分,結(jié)論沒錯(cuò),但***還是數(shù)據(jù)說話,不能拍腦袋。好的做法是首先收集所有SQL,挑選where語句最常出現(xiàn)的過濾字段,比如用戶Id/訂單Id/商家Id,每個(gè)字段在SQL中有三種情況:

單Id過濾,如用戶Id=?

多Id過濾,如用戶Id IN (?,?,?)

該Id不出現(xiàn)

然后進(jìn)一步統(tǒng)計(jì),假設(shè)共有500個(gè)SQL訪問訂單庫,3個(gè)過濾字段出現(xiàn)情況如下:

過濾字段單Id過濾多Id過濾不出現(xiàn)

用戶Id12040330

訂單Id6080360

商家Id150485

結(jié)論明顯,應(yīng)該選擇用戶Id進(jìn)行分庫。

等一等,這只是靜態(tài)分析,每個(gè)SQL訪問的次數(shù)是不一樣的,因此還要分析每個(gè)SQL的訪問量。我們分析了Top15執(zhí)行最多的SQL (它們占總執(zhí)行次數(shù)85%),如果按照用戶Id分庫,這些SQL 85%落到單個(gè)數(shù)據(jù)庫, 13%落到多個(gè)數(shù)據(jù)庫,只有2%需要遍歷所有數(shù)據(jù)庫,明顯優(yōu)于使用其他Id進(jìn)行分庫。

通過量化分析,我們知道按照用戶Id分庫是***的,同時(shí)也大致知道分庫對(duì)現(xiàn)有系統(tǒng)的影響,比如這個(gè)例子中,85%的SQL會(huì)落到單個(gè)數(shù)據(jù)庫,這部分的訪問性能會(huì)優(yōu)化,堅(jiān)定了各方對(duì)分庫的信心。

分庫策略

分庫維度確定后,如何把記錄分到各個(gè)庫里呢?一般有兩種方式:

根據(jù)數(shù)值范圍,比如用戶Id為1-9999的記錄分到***個(gè)庫,10000-20000的分到第二個(gè)庫,以此類推。

根據(jù)數(shù)值取模,比如用戶Id mod n,余數(shù)為0的記錄放到***個(gè)庫,余數(shù)為1的放到第二個(gè)庫,以此類推。

兩種分法的優(yōu)劣比較如下:

評(píng)價(jià)指標(biāo)按照范圍分庫按照Mod分庫

庫數(shù)量前期數(shù)目比較小,可以隨用戶/業(yè)務(wù)按需增長前期即根據(jù)mode因子確定庫數(shù)量,數(shù)目一般比較大

訪問性能前期庫數(shù)量小,全庫查詢消耗資源少,單庫查詢性能略差前期庫數(shù)量大,全庫查詢消耗資源多,單庫查詢性能略好

調(diào)整庫數(shù)量比較容易,一般只需為新用戶增加庫,老庫拆分也只影響單個(gè)庫困難,改變mod因子導(dǎo)致數(shù)據(jù)在所有庫之間遷移

數(shù)據(jù)熱點(diǎn)新舊用戶購物頻率有差異,有數(shù)據(jù)熱點(diǎn)問題新舊用戶均勻到分布到各個(gè)庫,無熱點(diǎn)

實(shí)踐中,為了處理簡單,選擇mod分庫的比較多。同時(shí)二次分庫時(shí),為了數(shù)據(jù)遷移方便,一般是按倍數(shù)增加,比如初始4個(gè)庫,二次分裂為8個(gè),再16個(gè)。這樣對(duì)于某個(gè)庫的數(shù)據(jù),一半數(shù)據(jù)移到新庫,剩余不動(dòng),對(duì)比每次只增加一個(gè)庫,所有數(shù)據(jù)都要大規(guī)模變動(dòng)。

補(bǔ)充下,mod分庫一般每個(gè)庫記錄數(shù)比較均勻,但也有些數(shù)據(jù)庫,存在超級(jí)Id,這些Id的記錄遠(yuǎn)遠(yuǎn)超過其他Id,比如在廣告場景下,某個(gè)大廣告主的廣告數(shù)可能占總體很大比例。如果按照廣告主Id取模分庫,某些庫的記錄數(shù)會(huì)特別多,對(duì)于這些超級(jí)Id,需要提供單獨(dú)庫來存儲(chǔ)記錄。

分庫數(shù)量

分庫數(shù)量首先和單庫能處理的記錄數(shù)有關(guān),一般來說,Mysql 單庫超過5000萬條記錄,Oracle單庫超過1億條記錄,DB壓力就很大(當(dāng)然處理能力和字段數(shù)量/訪問模式/記錄長度有進(jìn)一步關(guān)系)。

在滿足上述前提下,如果分庫數(shù)量少,達(dá)不到分散存儲(chǔ)和減輕DB性能壓力的目的;如果分庫的數(shù)量多,好處是每個(gè)庫記錄少,單庫訪問性能好,但對(duì)于跨多個(gè)庫的訪問,應(yīng)用程序需要訪問多個(gè)庫,如果是并發(fā)模式,要消耗寶貴的線程資源;如果是串行模式,執(zhí)行時(shí)間會(huì)急劇增加。

***分庫數(shù)量還直接影響硬件的投入,一般每個(gè)分庫跑在單獨(dú)物理機(jī)上,多一個(gè)庫意味多一臺(tái)設(shè)備。所以具體分多少個(gè)庫,要綜合評(píng)估,一般初次分庫建議分4-8個(gè)庫。

路由透明

分庫從某種意義上來說,意味著DB schema改變了,必然影響應(yīng)用,但這種改變和業(yè)務(wù)無關(guān),所以要盡量保證分庫對(duì)應(yīng)用代碼透明,分庫邏輯盡量在數(shù)據(jù)訪問層處理。當(dāng)然完全做到這一點(diǎn)很困難,具體哪些應(yīng)該由DAL負(fù)責(zé),哪些由應(yīng)用負(fù)責(zé),這里有一些建議:

對(duì)于單庫訪問,比如查詢條件指定用戶Id,則該SQL只需訪問特定庫。此時(shí)應(yīng)該由DAL層自動(dòng)路由到特定庫,當(dāng)庫二次分裂時(shí),也只要修改mod 因子,應(yīng)用代碼不受影響。

對(duì)于簡單的多庫查詢,DAL負(fù)責(zé)匯總各個(gè)數(shù)據(jù)庫返回的記錄,此時(shí)仍對(duì)上層應(yīng)用透明。

對(duì)于帶聚合運(yùn)算的多庫查詢,如帶groupBy/orderby/min/max/avg等關(guān)鍵字,建議DAL匯總單個(gè)庫返回的結(jié)果,上層應(yīng)用做進(jìn)一步處理。一方面DAL全面支持各種case,實(shí)現(xiàn)很復(fù)雜;另一方面,從1號(hào)店實(shí)踐來看,這樣的例子不多,在上層應(yīng)用作針對(duì)性處理,更加靈活。

DAL可進(jìn)一步細(xì)分為JDBC和DAL兩層,基于JDBC層面實(shí)現(xiàn)分庫路由,系統(tǒng)開發(fā)難度大,靈活性低,目前也沒有很好的成功案例;一般是基于持久層框架進(jìn)一步封裝成DDAL(分布式數(shù)據(jù)訪問層),實(shí)現(xiàn)分庫路由,1號(hào)店DAL即基于iBatis進(jìn)行上層封裝而來。

分頁處理

分庫后,有些分頁查詢需要遍歷所有庫,這些case是分庫***的受害者L。

舉個(gè)分頁的例子,比如要求按時(shí)間順序展示某個(gè)商家的訂單,每頁100條記錄,由于是按商家查詢,需要遍歷所有數(shù)據(jù)庫,假設(shè)庫數(shù)量是8,我們來看下分頁處理邏輯:

如果取第1頁數(shù)據(jù),則需要從每個(gè)庫里按時(shí)間順序取前100條記錄,8個(gè)庫匯總后有800條,然后對(duì)這800條記錄在應(yīng)用里進(jìn)行二次排序,***取前100條。

如果取第10頁數(shù)據(jù),則需要從每個(gè)庫里取前1000(100*10)條記錄,匯總后有8000條記錄,然后對(duì)這8000條記錄二次排序后取(900,1000)條記錄。

分庫情況下,對(duì)于第k頁記錄,每個(gè)庫要多取100*(k-1)條記錄,所有庫加起來,多取的記錄更多,所以越是靠后的分頁,系統(tǒng)要耗費(fèi)更多內(nèi)存和執(zhí)行時(shí)間。

對(duì)比沒分庫的情況,無論取那一頁,都只要從單個(gè)DB里取100條記錄,而且無需在應(yīng)用內(nèi)部做二次排序,非常簡單。

那如何解決分庫情況下的分頁問題呢?有以下幾種辦法:

如果是在前臺(tái)應(yīng)用提供分頁,則限定用戶只能看前面n頁,這個(gè)限制在業(yè)務(wù)上也是合理的,一般看后面的分頁意義不大(如果一定要看,可以要求用戶縮小范圍重新查詢)。

如果是后臺(tái)批處理任務(wù)要求分批獲取數(shù)據(jù),則可以加大page size,比如每次獲取5000條記錄,有效減少分頁數(shù)(當(dāng)然離線訪問一般走備庫,避免沖擊主庫)。

分庫設(shè)計(jì)時(shí),一般還有配套大數(shù)據(jù)平臺(tái)匯總所有分庫的記錄,有些分頁查詢可以考慮走大數(shù)據(jù)平臺(tái)。

Lookup映射

分庫字段只有一個(gè),比如這里是用戶Id,但訂單表還有其他字段可唯一區(qū)分記錄,比如訂單Id,給定一個(gè)訂單Id,相應(yīng)記錄一定在某個(gè)庫里。如果盲目地查詢所有分庫,則帶來不必要的開銷,Lookup映射可根據(jù)訂單Id,找到相應(yīng)的用戶Id,從而實(shí)現(xiàn)單庫定位。

可以事先檢索所有訂單Id和用戶Id,保存在Lookup表里,Lookup表的記錄數(shù)和訂單庫記錄總數(shù)相等,但它只有2個(gè)字段,所以存儲(chǔ)和查詢性能都不是問題。實(shí)際使用時(shí),一般通過分布式緩存來優(yōu)化Lookup性能。對(duì)于新增的訂單,除了寫訂單表,同時(shí)要寫Lookup表。

整體架構(gòu)

1號(hào)店訂單水平分庫的總體技術(shù)架構(gòu)如下圖所示:

上層應(yīng)用通過訂單服務(wù)/分庫代理和DAL訪問數(shù)據(jù)庫。

代理對(duì)訂單服務(wù)實(shí)現(xiàn)功能透明,包括聚合運(yùn)算,非用戶Id到用戶Id的映射。

Lookup表用于訂單Id/用戶Id映射,保證按訂單Id訪問時(shí),可以直接落到單個(gè)庫,Cache是Lookup的內(nèi)存數(shù)據(jù)映像,提升性能,cache故障時(shí),直接訪問Lookup表。

DAL提供庫的路由,根據(jù)用戶Id定位到某個(gè)庫,對(duì)于多庫訪問,DAL支持可選的并發(fā)訪問模式,并支持簡單記錄匯總。

Lookup表初始化數(shù)據(jù)來自于現(xiàn)有分庫數(shù)據(jù),新增記錄時(shí),直接由代理異步寫入。

上線步驟

訂單表是核心業(yè)務(wù)表,它的水平拆分影響很多業(yè)務(wù),本身的技術(shù)改造也很大,很容易出紕漏,上線時(shí),必須謹(jǐn)慎考慮,1號(hào)店整個(gè)方案實(shí)施過程如下:

首先實(shí)現(xiàn)Oracle和MySQL兩套庫并行,所有數(shù)據(jù)訪問指向Oracle庫,通過數(shù)據(jù)同步程序把數(shù)據(jù)從Oracle拆分到多個(gè)MySQL分庫,比如3分鐘增量同步一次。

按照上述架構(gòu)圖搭建整個(gè)體系,選擇幾個(gè)對(duì)數(shù)據(jù)實(shí)時(shí)性不高的訪問例子(如訪問歷史訂單),轉(zhuǎn)向MySQL分庫訪問,然后逐漸增加更多非實(shí)時(shí)case,以檢驗(yàn)整套體系可行性。

如果性能和功能都沒問題,再一次性把所有實(shí)時(shí)讀寫訪問轉(zhuǎn)向MySQL,廢棄Oracle。

這個(gè)上線步驟多了數(shù)據(jù)同步程序的開發(fā)(大約1人周工作量,風(fēng)險(xiǎn)很低),但分散了風(fēng)險(xiǎn),把***步的技術(shù)風(fēng)險(xiǎn)(Lookup/DAL等基礎(chǔ)設(shè)施改造)和第二步的業(yè)務(wù)功能風(fēng)險(xiǎn)(Oracle改MySQL語法)分開。1號(hào)店兩階段上線都是一次性成功,特別是第二階段上線,100多個(gè)依賴方應(yīng)用簡單重啟即完成升級(jí),中間沒有出現(xiàn)一例較大問題。

項(xiàng)目總結(jié)

1號(hào)店完成訂單水平分庫的同時(shí),把訂單庫從Oralce遷到MySQL,設(shè)備從小型機(jī)換成X86服務(wù)器,通過水平分庫和去IOE,不但支持訂單量未來增長,并且總體成本也大幅下降。

由于去IOE和訂單分庫一起實(shí)施,帶來雙重的性能影響,我們花了很大精力做性能測試,為了模擬真實(shí)場景,大家通過Tcpcopy把線上實(shí)際的查詢流量引到測試環(huán)境,先后經(jīng)過13輪的性能測試,最終6個(gè)MySQL庫相對(duì)一個(gè)Oracle,平均SQL執(zhí)行時(shí)間基本持平,性能不降低的情況下,優(yōu)化了架構(gòu),節(jié)省了成本。

對(duì)核心表做水平分庫之前,必須先做好服務(wù)化,即外部系統(tǒng)通過統(tǒng)一的訂單服務(wù)訪問相關(guān)表,不然很容易遺漏一些SQL。

1號(hào)店最終是根據(jù)用戶Id后三位取模,初始分6個(gè)庫,理論上支持多達(dá)768個(gè)庫,并且對(duì)訂單Id生成規(guī)則做了改造,使其包括用戶Id后三位,這樣新訂單Id本身包含庫定位所需信息,無需走Lookup機(jī)制,隨著老訂單歸檔到歷史庫,上述架構(gòu)中l(wèi)ookup部分可廢棄。

水平分庫是一項(xiàng)系統(tǒng)性工作,首先需要在理論模式指導(dǎo)下,結(jié)合實(shí)際情況,每個(gè)方面做出***選擇。其次對(duì)于特殊場景,如跨庫分頁,沒有銀彈,可以靈活處理,不走常規(guī)路。***控制好節(jié)奏,系統(tǒng)改造、數(shù)據(jù)遷移、上線實(shí)施等各個(gè)環(huán)節(jié)做好銜接,全局一盤棋。

大膽設(shè)計(jì),小心求證,謹(jǐn)慎實(shí)施,分庫并不難。

作者介紹

王慶友,前1號(hào)店***架構(gòu)師,先后就職于ebay、騰訊、1號(hào)店等公司,精通電商業(yè)務(wù),擅長復(fù)雜系統(tǒng)業(yè)務(wù)建模和架構(gòu)分析,同時(shí)在構(gòu)建大規(guī)模的分布式系統(tǒng)方 面有豐富實(shí)踐,尤其在大型系統(tǒng)的SOA改造方面有很深入的理論和實(shí)踐,目前在尋找合作機(jī)會(huì),微信號(hào)Brucetwins,個(gè)人公眾號(hào)”架構(gòu)之道”,歡迎一起聊架構(gòu)。

責(zé)任編輯:張燕妮 來源: 聊聊架構(gòu)
相關(guān)推薦

2020-11-18 09:39:02

MySQL數(shù)據(jù)庫SQL

2020-07-30 17:59:34

分庫分表SQL數(shù)據(jù)庫

2022-11-30 07:58:10

支付業(yè)務(wù)系統(tǒng)分庫分表

2018-01-12 15:17:40

數(shù)據(jù)庫水平分庫數(shù)據(jù)遷移

2022-10-09 18:14:31

訂單系統(tǒng)分庫分表

2013-03-06 10:54:03

云服務(wù)實(shí)踐關(guān)鍵步驟

2014-04-10 09:21:22

Windows Ser

2021-08-02 08:05:05

系統(tǒng)訂單 Python

2015-08-06 11:45:28

電商混合云運(yùn)維實(shí)踐

2015-08-05 09:38:18

1號(hào)店混合云運(yùn)維

2021-02-05 10:27:23

轉(zhuǎn)型計(jì)劃項(xiàng)目負(fù)責(zé)人CIO

2018-09-07 10:14:58

2022-07-21 14:37:12

云計(jì)算安全云架構(gòu)

2009-12-25 14:52:49

2021-11-24 14:46:06

云計(jì)算云遷移數(shù)據(jù)中心

2023-07-31 11:19:16

2023-02-15 14:09:57

云托管云退出策略

2025-02-08 11:23:55

2020-09-28 06:32:53

VDI測試清單虛擬化

2019-06-12 14:34:42

云平臺(tái)云遷移云計(jì)算
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)