天天看點

sqlite3資料存儲最多存儲多少條資料?達到上限如何處理?_史上最接地氣的水準分庫方案,手把手教你如何落地...

随着大型網際網路應用的發展,海量資料的存儲和通路成為系統設計的瓶頸,分布式處理成為不二選擇。資料庫拆分,特别是水準分庫是個高難度的活,涉及一系列技術決策。

本人有幸負責1号店訂單水準分庫的方案設計及實施落地,本人結合項目實踐,對水準分庫做一個系統地剖析,希望為大家水準分庫(包括去IOE)改造提供總體思路,主要内容包括:

1) 水準分庫說明

2) 分庫次元-- 根據哪個字段分庫

3) 分庫政策-- 記錄如何配置設定到不同庫

4) 分庫數量-- 初始庫數量及庫數量如何增長

5) 路由透明-- 如何實作庫路由,支援應用透明

6) 分頁處理-- 跨多個庫的分頁case如何處理

7) Lookup映射—非分庫字段映射到分庫字段,實作單庫通路

8) 整體架構-- 分庫的整體技術架構

9) 上線步驟-- 分庫改造實施上線

10) 項目總結

水準分庫說明

資料庫拆分有兩種:

1) 垂直分庫

資料庫裡的表太多,拿出部分到新的庫裡,一般是根據業務劃分表,關系密切的表放同一資料庫,應用修改資料庫連接配接即可,比較簡單。

2) 水準分庫

某張表太大,單個資料庫存儲不下或通路性能有壓力,把一張表拆成多張,每張表存放部分記錄,儲存在不同的資料庫裡,水準分庫需要對系統做大的改造。

sqlite3資料存儲最多存儲多少條資料?達到上限如何處理?_史上最接地氣的水準分庫方案,手把手教你如何落地...

1号店核心的訂單表存儲在Oracle資料庫,記錄有上億條,字段有上百個,通路的模式也是複雜多樣,随着業務快速增長,無論存儲空間或通路性能都面臨巨大挑戰,特别在大促時,訂單庫已成為系統瓶頸。

通常有兩種解決辦法:

1) Scale up,更新Oracle資料庫所在的實體機,提升記憶體/存儲/IO性能,但這種更新費用昂貴,并且隻能滿足短期需要。

2) Scale out,把訂單庫拆分為多個庫,分散到多台機器進行存儲和通路,這種做法支援水準擴充,可以滿足長遠需要。

1号店采取後一種做法,它的訂單庫主要包括訂單主表/訂單明細表(記錄商品明細)/訂單擴充表,水準分庫即把這3張表的記錄分到多個資料庫中,訂單水準分庫效果如下圖所示:

sqlite3資料存儲最多存儲多少條資料?達到上限如何處理?_史上最接地氣的水準分庫方案,手把手教你如何落地...

原來一個Oracle庫被多個MySQL庫取代,支援1主多備和讀寫分離,主備之間通過MySQL自帶的資料同步機制(SLA<1秒),所有應用通過訂單服務通路訂單資料。

分庫次元

水準分庫首先要考慮根據哪個字段作為分庫次元,選擇标準是盡量避免應用代碼和SQL性能受影響,這就要求目前SQL在分庫後,通路盡量落在單個庫裡,否則單庫通路變成多庫掃描,讀寫性能和應用邏輯都會受較大影響,。

對于訂單拆分,大家首先想到的是按照使用者Id拆分,結論沒錯,但最好還是資料說話,不能拍腦袋。好的做法是首先收集所有SQL,挑選where語句最常出現的過濾字段,比如使用者Id/訂單Id/商家Id,每個字段在SQL中有三種情況:

1. 單Id過濾,如使用者Id=?

2. 多Id過濾,如使用者Id IN(?,?,?)

3. 該Id不出現

然後進一步統計,假設共有500個SQL通路訂單庫,3個過濾字段出現情況如下:

sqlite3資料存儲最多存儲多少條資料?達到上限如何處理?_史上最接地氣的水準分庫方案,手把手教你如何落地...

結論很明顯,應該選擇使用者Id進行分庫。

等一等,這隻是靜态分析,每個SQL通路的次數是不一樣的,是以還要分析每個SQL的通路量。我們分析了Top15執行最多的SQL (它們占總執行次數85%),如果按照使用者Id分庫,這些SQL 85%落到單個資料庫, 13%落到多個資料庫,隻有2%需要周遊所有資料庫,明顯優于使用其他Id進行分庫。

通過量化分析,我們知道按照使用者Id分庫是最優的,同時也大緻知道分庫對現有系統的影響,比如這個例子中,85%的SQL會落到單個資料庫,這部分的通路性能會優化,堅定了各方對分庫的信心。

分庫政策

分庫次元确定後,如何把記錄分到各個庫裡呢?一般有兩種方式:

1. 根據數值範圍,比如使用者Id為1-9999的記錄分到第一個庫,10000-20000的分到第二個庫,以此類推。

2. 根據數值取模,比如使用者Id mod n,餘數為0的記錄放到第一個庫,餘數為1的放到第二個庫,以此類推。

兩種分法的優劣比較如下:

sqlite3資料存儲最多存儲多少條資料?達到上限如何處理?_史上最接地氣的水準分庫方案,手把手教你如何落地...

實踐中,為了處理簡單,選擇mod分庫的比較多。同時二次分庫時,為了資料遷移友善,一般是按倍數增加,比如初始4個庫,二次分裂為8個,再16個。這樣對于某個庫的資料,一半資料移到新庫,剩餘不動,對比每次隻增加一個庫,所有資料都要大規模變動。

補充下,mod分庫一般每個庫記錄數比較均勻,但也有些資料庫,存在超級Id,這些Id的記錄遠遠超過其他Id,比如在廣告場景下,某個大廣告主的廣告數可能占總體很大比例。如果按照廣告主Id取模分庫,某些庫的記錄數會特别多,對于這些超級Id,需要提供單獨庫來存儲記錄。

分庫數量

分庫數量首先和單庫能處理的記錄數有關,一般來說,Mysql 單庫超過5000萬條記錄,Oracle單庫超過1億條記錄,DB壓力就很大(當然處理能力和字段數量/通路模式/記錄長度有進一步關系)。

在滿足上述前提下,如果分庫數量少,達不到分散存儲和減輕DB性能壓力的目的;如果分庫的數量多,好處是每個庫記錄少,單庫通路性能好,但對于跨多個庫的通路,應用程式需要通路多個庫,如果是并發模式,要消耗寶貴的線程資源;如果是串行模式,執行時間會急劇增加。

最後分庫數量還直接影響硬體的投入,一般每個分庫跑在單獨實體機上,多一個庫意味多一台裝置。是以具體分多少個庫,要綜合評估,一般初次分庫建議分4-8個庫。

路由透明

分庫從某種意義上來說,意味着DBschema改變了,必然影響應用,但這種改變和業務無關,是以要盡量保證分庫對應用代碼透明,分庫邏輯盡量在資料通路層處理。當然完全做到這一點很困難,具體哪些應該由DAL負責,哪些由應用負責,這裡有一些建議:

1) 對于單庫通路,比如查詢條件指定使用者Id,則該SQL隻需通路特定庫。此時應該由DAL層自動路由到特定庫,當庫二次分裂時,也隻要修改mod 因子,應用代碼不受影響。

2) 對于簡單的多庫查詢,DAL負責彙總各個資料庫傳回的記錄,此時仍對上層應用透明。

3) 對于帶聚合運算的多庫查詢,如帶groupBy/orderby/min/max/avg等關鍵字,建議DAL彙總單個庫傳回的結果,上層應用做進一步處理。一方面DAL全面支援各種case,實作很複雜;另一方面,從1号店實踐來看,這樣的例子不多,在上層應用作針對性處理,更加靈活。

DAL可進一步細分為JDBC和DAL兩層,基于JDBC層面實作分庫路由,系統開發難度大,靈活性低,目前也沒有很好的成功案例;一般是基于持久層架構進一步封裝成DDAL(分布式資料通路層),實作分庫路由,1号店DAL即基于iBatis進行上層封裝而來。

分頁處理

分庫後,有些分頁查詢需要周遊所有庫,這些case是分庫最大的受害者L。

舉個分頁的例子,比如要求按時間順序展示某個商家的訂單,每頁100條記錄,由于是按商家查詢,需要周遊所有資料庫,假設庫數量是8,我們來看下分頁處理邏輯:

1) 如果取第1頁資料,則需要從每個庫裡按時間順序取前100條記錄,8個庫彙總後有800條,然後對這800條記錄在應用裡進行二次排序,最後取前100條。

2) 如果取第10頁資料,則需要從每個庫裡取前1000(100*10)條記錄,彙總後有8000條記錄,然後對這8000條記錄二次排序後取(900,1000)條記錄。

分庫情況下,對于第k頁記錄,每個庫要多取100*(k-1)條記錄,所有庫加起來,多取的記錄更多,是以越是靠後的分頁,系統要耗費更多記憶體和執行時間。

對比沒分庫的情況,無論取那一頁,都隻要從單個DB裡取100條記錄,而且無需在應用内部做二次排序,非常簡單。

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

1) 如果是在前台應用提供分頁,則限定使用者隻能看前面n頁,這個限制在業務上也是合理的,一般看後面的分頁意義不大(如果一定要看,可以要求使用者縮小範圍重新查詢)。

2) 如果是背景批處理任務要求分批擷取資料,則可以加大page size,比如每次擷取5000條記錄,有效減少分頁數(當然離線通路一般走備庫,避免沖擊主庫)。

3) 分庫設計時,一般還有配套大資料平台彙總所有分庫的記錄,有些分頁查詢可以考慮走大資料平台。

Lookup映射

分庫字段隻有一個,比如這裡是使用者Id,但訂單表還有其他字段可唯一區分記錄,比如訂單Id,給定一個訂單Id,相應記錄一定在某個庫裡。如果盲目地查詢所有分庫,則帶來不必要的開銷,Lookup映射可根據訂單Id,找到相應的使用者Id,進而實作單庫定位。

可以事先檢索所有訂單Id和使用者Id,儲存在Lookup表裡,Lookup表的記錄數和訂單庫記錄總數相等,但它隻有2個字段,是以存儲和查詢性能都不是問題。實際使用時,一般通過分布式緩存來優化Lookup性能。對于新增的訂單,除了寫訂單表,同時要寫Lookup表。

整體架構

1号店訂單水準分庫的總體技術架構如下圖所示:

sqlite3資料存儲最多存儲多少條資料?達到上限如何處理?_史上最接地氣的水準分庫方案,手把手教你如何落地...

1) 上層應用通過訂單服務/分庫代理和DAL通路資料庫。

2) 代理對訂單服務實作功能透明,包括聚合運算,非使用者Id到使用者Id的映射。

3) Lookup表用于訂單Id/使用者Id映射,保證按訂單Id通路時,可以直接落到單個庫,Cache是Lookup的記憶體資料映像,提升性能,cache故障時,直接通路Lookup表。

4) DAL提供庫的路由,根據使用者Id定位到某個庫,對于多庫通路,DAL支援可選的并發通路模式,并支援簡單記錄彙總。

5) Lookup表初始化資料來自于現有分庫資料,新增記錄時,直接由代理異步寫入。

上線步驟

訂單表是核心業務表,它的水準拆分影響很多業務,本身的技術改造也很大,很容易出纰漏,上線時,必須謹慎考慮,1号店整個方案實施過程如下:

首先實作Oracle和MySQL兩套庫并行,所有資料通路指向Oracle庫,通過資料同步程式把資料從Oracle拆分到多個MySQL分庫,比如3分鐘增量同步一次。

1) 按照上述架構圖搭建整個體系,選擇幾個對資料實時性不高的通路例子(如通路曆史訂單),轉向MySQL分庫通路,然後逐漸增加更多非實時case,以檢驗整套體系可行性。

2) 如果性能和功能都沒問題,再一次性把所有實時讀寫通路轉向MySQL,廢棄Oracle。

這個上線步驟多了資料同步程式的開發(大約1人周工作量,風險很低),但分散了風險,把第一步的技術風險(Lookup/DAL等基礎設施改造)和第二步的業務功能風險(Oracle改MySQL文法)分開。1号店兩階段上線都是一次性成功,特别是第二階段上線,100多個依賴方應用簡單重新開機即完成更新,中間沒有出現一例較大問題。

項目總結

1号店完成訂單水準分庫的同時,把訂單庫從Oralce遷到Mysql,裝置從小型機換成X86伺服器,通過水準分庫和去IOE,不但支援訂單量未來增長,并且總體成本也大幅下降。

由于去IOE和訂單分庫一起實施,帶來雙重的性能影響,我們花了很大精力做性能測試,為了模拟真實場景,大家通過Tcpcopy把線上實際的查詢流量引到測試環境,先後經過13輪的性能測試,最終6個mysql庫相對一個Oracle,平均SQL執行時間基本持平,性能不降低的情況下,優化了架構,節省了成本。

對核心表做水準分庫之前,必須先做好服務化,即外部系統通過統一的訂單服務通路相關表,不然很容易遺漏一些SQL。

1号店最終是根據使用者Id後三位取模,初始分6個庫,理論上支援多達768個庫,并且對訂單Id生成規則做了改造,使其包括使用者Id後三位,這樣新訂單Id本身包含庫定位所需資訊,無需走Lookup機制,随着老訂單歸檔到曆史庫,上述架構中lookup部分可廢棄。

水準分庫是一項系統性工作,首先需要在理論模式指導下,結合實際情況,每個方面做出最優選擇。其次對于特殊場景,如跨庫分頁,沒有銀彈,可以靈活處理,不走正常路。最後控制好節奏,系統改造、資料遷移、上線實施等各個環節做好銜接,全局一盤棋。

大膽設計,小心求證,謹慎實施,水準分庫并不難。