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