掃描二維碼
隨時(shí)隨地手機(jī)看文章
垂直方向和 水平方向。
1 垂直方向
垂直方向主要針對(duì)的是業(yè)務(wù),下面聊聊業(yè)務(wù)的發(fā)展跟分庫(kù)分表有什么關(guān)系。
1.1 單庫(kù)
在系統(tǒng)初期,業(yè)務(wù)功能相對(duì)來說比較簡(jiǎn)單,系統(tǒng)模塊較少。
為了快速滿足迭代需求,減少一些不必要的依賴。更重要的是減少系統(tǒng)的復(fù)雜度,保證開發(fā)速度,我們通常會(huì)使用單庫(kù)來保存數(shù)據(jù)。
系統(tǒng)初期的數(shù)據(jù)庫(kù)架構(gòu)如下:此時(shí),使用的數(shù)據(jù)庫(kù)方案是:一個(gè)數(shù)據(jù)庫(kù)包含多張業(yè)務(wù)表。用戶讀數(shù)據(jù)請(qǐng)求和寫數(shù)據(jù)請(qǐng)求,都是操作的同一個(gè)數(shù)據(jù)庫(kù)。
1.2 分表
系統(tǒng)上線之后,隨著業(yè)務(wù)的發(fā)展,不斷的添加新功能。導(dǎo)致單表中的字段越來越多,開始變得有點(diǎn)不太好維護(hù)了。
一個(gè)用戶表就包含了幾十甚至上百個(gè)字段,管理起來有點(diǎn)混亂。
這時(shí)候該怎么辦呢?
答:分表。
將用戶表拆分為:用戶基本信息表和 用戶擴(kuò)展表。
用戶基本信息表中存的是用戶最主要的信息,比如:用戶名、密碼、別名、手機(jī)號(hào)、郵箱、年齡、性別等核心數(shù)據(jù)。
這些信息跟用戶息息相關(guān),查詢的頻次非常高。
而用戶擴(kuò)展表中存的是用戶的擴(kuò)展信息,比如:所屬單位、戶口所在地、所在城市等等,非核心數(shù)據(jù)。
這些信息只有在特定的業(yè)務(wù)場(chǎng)景才需要查詢,而絕大數(shù)業(yè)務(wù)場(chǎng)景是不需要的。
所以通過分表把核心數(shù)據(jù)和非核心數(shù)據(jù)分開,讓表的結(jié)構(gòu)更清晰,職責(zé)更單一,更便于維護(hù)。
除了按實(shí)際業(yè)務(wù)分表之外,我們還有一個(gè)常用的分表原則是:把調(diào)用頻次高的放在一張表,調(diào)用頻次低的放在另一張表。
有個(gè)非常經(jīng)典的例子就是:訂單表和訂單詳情表。
1.3 分庫(kù)
不知不覺,系統(tǒng)已經(jīng)上線了一年多的時(shí)間了。經(jīng)歷了N個(gè)迭代的需求開發(fā),功能已經(jīng)非常完善。
系統(tǒng)功能完善,意味著系統(tǒng)各種關(guān)聯(lián)關(guān)系,錯(cuò)綜復(fù)雜。
此時(shí),如果不趕快梳理業(yè)務(wù)邏輯,后面會(huì)帶來很多隱藏問題,會(huì)把自己坑死。
這就需要按業(yè)務(wù)功能,劃分不同領(lǐng)域了。把相同領(lǐng)域的表放到同一個(gè)數(shù)據(jù)庫(kù),不同領(lǐng)域的表,放在另外的數(shù)據(jù)庫(kù)。
具體拆分過程如下:
將用戶、產(chǎn)品、物流、訂單相關(guān)的表,從原來一個(gè)數(shù)據(jù)庫(kù)中,拆分成單獨(dú)的用戶庫(kù)、產(chǎn)品庫(kù)、物流庫(kù)和訂單庫(kù),一共四個(gè)數(shù)據(jù)庫(kù)。
在這里為了看起來更直觀,每個(gè)庫(kù)我只畫了一張表,實(shí)際場(chǎng)景可能有多張表。
這樣按領(lǐng)域拆分之后,每個(gè)領(lǐng)域只用關(guān)注自己相關(guān)的表,職責(zé)更單一了,一下子變得更好維護(hù)了。
1.4 分庫(kù)分表
有時(shí)候按業(yè)務(wù),只分庫(kù),或者只分表是不夠的。比如:有些財(cái)務(wù)系統(tǒng),需要按月份和年份匯總,所有用戶的資金。
這就需要做:分庫(kù)分表了。
每年都有個(gè)單獨(dú)的數(shù)據(jù)庫(kù),每個(gè)數(shù)據(jù)庫(kù)中,都有12張表,每張表存儲(chǔ)一個(gè)月的用戶資金數(shù)據(jù)。這樣分庫(kù)分表之后,就能非常高效的查詢出某個(gè)用戶每個(gè)月,或者每年的資金了。
此外,還有些比較特殊的需求,比如需要按照地域分庫(kù),比如:華中、華北、華南等區(qū),每個(gè)區(qū)都有一個(gè)單獨(dú)的數(shù)據(jù)庫(kù)。
甚至有些游戲平臺(tái),按接入的游戲廠商來做分庫(kù)分表。
2 水平方向
水分方向主要針對(duì)的是數(shù)據(jù),下面聊聊數(shù)據(jù)跟分庫(kù)分表又有什么關(guān)系。
2.1 單庫(kù)
在系統(tǒng)初期,由于用戶非常少,所以系統(tǒng)并發(fā)量很小。并且存在表中的數(shù)據(jù)量也非常少。
這時(shí)的數(shù)據(jù)庫(kù)架構(gòu)如下:此時(shí),使用的數(shù)據(jù)庫(kù)方案同樣是:一個(gè)master數(shù)據(jù)庫(kù)包含多張業(yè)務(wù)表。
用戶讀數(shù)據(jù)請(qǐng)求和寫數(shù)據(jù)請(qǐng)求,都是操作的同一個(gè)數(shù)據(jù)庫(kù),該方案比較適合于并發(fā)量很低的業(yè)務(wù)場(chǎng)景。
2.2 主從讀寫分離
系統(tǒng)上線一段時(shí)間后,用戶數(shù)量增加了。
此時(shí),你會(huì)發(fā)現(xiàn)用戶的請(qǐng)求當(dāng)中,讀數(shù)據(jù)的請(qǐng)求占據(jù)了大部分,真正寫數(shù)據(jù)的請(qǐng)求占比很少。
眾所周知,數(shù)據(jù)庫(kù)連接是有限的,它是非常寶貴的資源。而每次數(shù)據(jù)庫(kù)的讀或?qū)懻?qǐng)求,都需要占用至少一個(gè)數(shù)據(jù)庫(kù)連接。
如果寫數(shù)據(jù)請(qǐng)求需要的數(shù)據(jù)庫(kù)連接,被讀數(shù)據(jù)請(qǐng)求占用完了,不就寫不了數(shù)據(jù)了?
這樣問題就嚴(yán)重了。
為了解決該問題,我們需要把讀庫(kù)和寫庫(kù)分開。
于是,就出現(xiàn)了主從讀寫分離架構(gòu):考慮剛開始用戶量還沒那么大,選擇的是一主一從的架構(gòu),也就是常說的一個(gè)master一個(gè)slave。
所有的寫數(shù)據(jù)請(qǐng)求,都指向主庫(kù)。一旦主庫(kù)寫完數(shù)據(jù)之后,立馬異步同步給從庫(kù)。這樣所有的讀數(shù)據(jù)請(qǐng)求,就能及時(shí)從從庫(kù)中獲取到數(shù)據(jù)了(除非網(wǎng)絡(luò)有延遲)。
讀寫分離方案可以解決上面提到的單節(jié)點(diǎn)問題,相對(duì)于單庫(kù)的方案,能夠更好的保證系統(tǒng)的穩(wěn)定性。
因?yàn)槿绻鲙?kù)掛了,可以升級(jí)從庫(kù)為主庫(kù),將所有讀寫請(qǐng)求都指向新主庫(kù),系統(tǒng)又能正常運(yùn)行了。
讀寫分離方案其實(shí)也是分庫(kù)的一種,它相對(duì)于為數(shù)據(jù)做了備份,它已經(jīng)成為了系統(tǒng)初期的首先方案。
但這里有個(gè)問題就是:如果用戶量確實(shí)有些大,如果master掛了,升級(jí)slave為master,將所有讀寫請(qǐng)求都指向新master。
但此時(shí),如果這個(gè)新master根本扛不住所有的讀寫請(qǐng)求,該怎么辦?
這就需要一主多從的架構(gòu)了:
上圖中我列的是一主兩從,如果master掛了,可以選擇從庫(kù)1或從庫(kù)2中的一個(gè),升級(jí)為新master。假如我們?cè)谶@里升級(jí)從庫(kù)1為新master,則原來的從庫(kù)2就變成了新master的的slave了。
調(diào)整之后的架構(gòu)圖如下:這樣就能解決上面的問題了。
除此之外,如果查詢請(qǐng)求量再增大,我們還可以將架構(gòu)升級(jí)為一主三從、一主四從...一主N從等。
2.3 分庫(kù)
上面的讀寫分離方案確實(shí)可以解決讀請(qǐng)求大于寫請(qǐng)求時(shí),導(dǎo)致master節(jié)點(diǎn)扛不住的問題。但如果某個(gè)領(lǐng)域,比如:用戶庫(kù)。如果注冊(cè)用戶的請(qǐng)求量非常大,即寫請(qǐng)求本身的請(qǐng)求量就很大,一個(gè)master庫(kù)根本無法承受住這么大的壓力。
這時(shí)該怎么辦呢?
答:建立多個(gè)用戶庫(kù)。
用戶庫(kù)的拆分過程如下:在這里我將用戶庫(kù)拆分成了三個(gè)庫(kù)(真實(shí)場(chǎng)景不一定是這樣的),每個(gè)庫(kù)的表結(jié)構(gòu)是一模一樣的,只有存儲(chǔ)的數(shù)據(jù)不一樣。
2.4 分表
用戶請(qǐng)求量上來了,帶來的勢(shì)必是數(shù)據(jù)量的成本上升。即使做了分庫(kù),但有可能單個(gè)庫(kù),比如:用戶庫(kù),出現(xiàn)了5000萬的數(shù)據(jù)。
根據(jù)經(jīng)驗(yàn)值,單表的數(shù)據(jù)量應(yīng)該盡量控制在1000萬以內(nèi),性能是最佳的。如果有幾千萬級(jí)的數(shù)據(jù)量,用單表來存,性能會(huì)變得很差。
如果數(shù)據(jù)量太大了,需要建立的索引也會(huì)很大,從小到大檢索一次數(shù)據(jù),會(huì)非常耗時(shí),而且非常消耗cpu資源。
這時(shí)該怎么辦呢?
答:分表,這樣可以控制每張表的數(shù)據(jù)量,和索引大小。
表拆分過程如下:
我在這里將用戶庫(kù)中的用戶表,拆分成了四張表(真實(shí)場(chǎng)景不一定是這樣的),每張表的表結(jié)構(gòu)是一模一樣的,只是存儲(chǔ)的數(shù)據(jù)不一樣。
如果以后用戶數(shù)據(jù)量越來越大,只需再多分幾張用戶表即可。
2.5 分庫(kù)分表
當(dāng)系統(tǒng)發(fā)展到一定的階段,用戶并發(fā)量大,而且需要存儲(chǔ)的數(shù)據(jù)量也很多。這時(shí)該怎么辦呢?
答:需要做分庫(kù)分表。
如下圖所示:圖中將用戶庫(kù)拆分成了三個(gè)庫(kù),每個(gè)庫(kù)都包含了四張用戶表。
如果有用戶請(qǐng)求過來的時(shí)候,先根據(jù)用戶id路由到其中一個(gè)用戶庫(kù),然后再定位到某張表。
路由的算法挺多的:
-
根據(jù)id取模,比如:id=7,有4張表,則7%4=3,模為3,路由到用戶表3。
-
給id指定一個(gè)區(qū)間范圍,比如:id的值是0-10萬,則數(shù)據(jù)存在用戶表0,id的值是10-20萬,則數(shù)據(jù)存在用戶表1。
-
一致性hash算法
這篇文章就不過多介紹了,后面會(huì)有文章專門介紹這些路由算法的。
3 真實(shí)案例
接下來,廢話不多說,給大家分享三個(gè)我參與過的分庫(kù)分表項(xiàng)目經(jīng)歷,給有需要的朋友一個(gè)參考。
3.1 分庫(kù)
我之前待過一家公司,我們團(tuán)隊(duì)是做游戲運(yùn)營(yíng)的,我們公司提供平臺(tái),游戲廠商接入我們平臺(tái),推廣他們的游戲。
游戲玩家通過我們平臺(tái)登錄,成功之后跳轉(zhuǎn)到游戲廠商的指定游戲頁面,該玩家就能正常玩游戲了,還可以充值游戲幣。
這就需要建立我們的賬號(hào)體系和游戲廠商的賬號(hào)的映射關(guān)系,游戲玩家通過登錄我們平臺(tái)的游戲賬號(hào),成功之后轉(zhuǎn)換成游戲廠商自己平臺(tái)的賬號(hào)。
這里有兩個(gè)問題:
-
每個(gè)游戲廠商的接入方式可能都不一樣,賬號(hào)體系映射關(guān)系也有差異。
-
用戶都從我們平臺(tái)登錄,成功之后跳轉(zhuǎn)到游戲廠商的游戲頁面。當(dāng)時(shí)有N個(gè)游戲廠商接入了,活躍的游戲玩家比較多,登錄接口的并發(fā)量不容小覷。
為了解決這兩個(gè)問題,我們當(dāng)時(shí)采用的方案是:分庫(kù)。即針對(duì)每一個(gè)游戲都單獨(dú)建一個(gè)數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)中的表結(jié)構(gòu)允許存在差異。我們當(dāng)時(shí)沒有進(jìn)一步分表,是因?yàn)楫?dāng)時(shí)考慮每種游戲的用戶量,還沒到大到離譜的地步。不像王者榮耀這種現(xiàn)象級(jí)的游戲,有上億的玩家。
其中有個(gè)比較關(guān)鍵的地方是:登錄接口中需要傳入游戲id字段,通過該字段,系統(tǒng)就知道要操作哪個(gè)庫(kù),因?yàn)閹?kù)名中就包含了游戲id的信息。
3.2 分表
還是在那家游戲平臺(tái)公司,我們還有另外一個(gè)業(yè)務(wù)就是:金鉆會(huì)員。
說白了就是打造了一套跟游戲相關(guān)的會(huì)員體系,為了保持用戶的活躍度,開通會(huì)員有很多福利,比如:送游戲幣、充值有折扣、積分兌換、抽獎(jiǎng)、專屬客服等等。
在這套會(huì)員體系當(dāng)中,有個(gè)非常重要的功能就是:積分。
用戶有很多種途徑可以獲取積分,比如:簽到、充值、玩游戲、抽獎(jiǎng)、推廣、參加活動(dòng)等等。
積分用什么用途呢?
-
退換實(shí)物禮物
-
兌換游戲幣
-
抽獎(jiǎng)
說了這么多,其實(shí)就是想說,一個(gè)用戶一天當(dāng)中,獲取積分或消費(fèi)積分都可能有很多次,那么,一個(gè)用戶一天就可能會(huì)產(chǎn)生幾十條記錄。
如果用戶多了的話,積分相關(guān)的數(shù)據(jù)量其實(shí)挺驚人的。
我們當(dāng)時(shí)考慮了,水平方向的數(shù)據(jù)量可能會(huì)很大,但是用戶并發(fā)量并不大,不像登錄接口那樣。
所以采用的方案是:分表。
當(dāng)時(shí)使用一個(gè)積分?jǐn)?shù)據(jù)庫(kù)就夠了,但是分了128張表。然后根據(jù)用戶id,進(jìn)行hash除以128取模。
需要特別注意的是,分表的數(shù)量最好是2的冪次方,方便以后擴(kuò)容。
3.3 分庫(kù)分表
后來我去了一家從事餐飲軟件開發(fā)的公司。這個(gè)公司有個(gè)特點(diǎn)是在每天的中午和晚上的就餐高峰期,用戶的并發(fā)量很大。
用戶吃飯前需要通過我們系統(tǒng)點(diǎn)餐,然后下單,然后結(jié)賬。當(dāng)時(shí)點(diǎn)餐和下單的并發(fā)量挺大的。
餐廳可能會(huì)有很多人,每個(gè)人都可能下多個(gè)訂單。這樣就會(huì)導(dǎo)致用戶的并發(fā)量高,并且數(shù)據(jù)量也很大。
所以,綜合考慮了一下,當(dāng)時(shí)我們采用的技術(shù)方案是:分庫(kù)分表。
經(jīng)過調(diào)研之后,覺得使用了當(dāng)當(dāng)網(wǎng)開源的基于jdbc的中間件框架:sharding-jdbc。
當(dāng)時(shí)分了4個(gè)庫(kù),每個(gè)庫(kù)有32張表。
4 總結(jié)
上面主要從:垂直和水平,兩個(gè)方向介紹了我們的系統(tǒng)為什么要分庫(kù)分表。
說實(shí)話垂直方向(即業(yè)務(wù)方向)更簡(jiǎn)單。
在水平方向(即數(shù)據(jù)方向)上,分庫(kù)和分表的作用,其實(shí)是有區(qū)別的,不能混為一談。
-
分庫(kù):是為了解決數(shù)據(jù)庫(kù)連接資源不足問題,和磁盤IO的性能瓶頸問題。
-
分表:是為了解決單表數(shù)據(jù)量太大,sql語句查詢數(shù)據(jù)時(shí),即使走了索引也非常耗時(shí)問題。此外還可以解決消耗cpu資源問題。
-
分庫(kù)分表:可以解決 數(shù)據(jù)庫(kù)連接資源不足、磁盤IO的性能瓶頸、檢索數(shù)據(jù)耗時(shí) 和 消耗cpu資源等問題。
如果在有些業(yè)務(wù)場(chǎng)景中,用戶并發(fā)量很大,但是需要保存的數(shù)據(jù)量很少,這時(shí)可以只分庫(kù),不分表。
如果在有些業(yè)務(wù)場(chǎng)景中,用戶并發(fā)量不大,但是需要保存的數(shù)量很多,這時(shí)可以只分表,不分庫(kù)。
如果在有些業(yè)務(wù)場(chǎng)景中,用戶并發(fā)量大,并且需要保存的數(shù)量也很多時(shí),可以分庫(kù)分表。
好了,今天的內(nèi)容就先到這里。
是不是有點(diǎn)意猶未盡?
沒關(guān)系,其實(shí)分庫(kù)分表相關(guān)內(nèi)容挺多的,本文作為分庫(kù)分表系列的第一彈,作為一個(gè)開胃小菜吧,分享給大家。
在文章末尾順便提幾個(gè)問題:
-
分庫(kù)分表的具體實(shí)現(xiàn)方案有哪些?
-
分庫(kù)分表后如何平滑擴(kuò)容?
-
分庫(kù)分表后帶來了哪些問題?
-
如何在項(xiàng)目中實(shí)現(xiàn)分庫(kù)分表功能?
歡迎關(guān)注,敬請(qǐng)期待我的下一篇文章。