1. 程式人生 > 其它 >阿里二面:我們為什麼要做分庫分表?

阿里二面:我們為什麼要做分庫分表?

前言

在高併發系統當中,分庫分表是必不可少的技術手段之一,同時也是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 分庫

我之前待過一家公司,我們團隊是做遊戲運營的,我們公司提供平臺,遊戲廠商接入我們平臺,推廣他們的遊戲。

遊戲玩家通過我們平臺登入,成功之後跳轉到遊戲廠商的指定遊戲頁面,該玩家就能正常玩遊戲了,還可以充值遊戲幣。

這就需要建立我們的賬號體系和遊戲廠商的賬號的對映關係,遊戲玩家通過登入我們平臺的遊戲賬號,成功之後轉換成遊戲廠商自己平臺的賬號。

這裡有兩個問題:

  1. 每個遊戲廠商的接入方式可能都不一樣,賬號體系對映關係也有差異。
  2. 使用者都從我們平臺登入,成功之後跳轉到遊戲廠商的遊戲頁面。當時有N個遊戲廠商接入了,活躍的遊戲玩家比較多,登入介面的併發量不容小覷。

為了解決這兩個問題,我們當時採用的方案是:分庫。即針對每一個遊戲都單獨建一個數據庫,資料庫中的表結構允許存在差異。

我們當時沒有進一步分表,是因為當時考慮每種遊戲的使用者量,還沒到大到離譜的地步。不像王者榮耀這種現象級的遊戲,有上億的玩家。

其中有個比較關鍵的地方是:登入介面中需要傳入遊戲id欄位,通過該欄位,系統就知道要操作哪個庫,因為庫名中就包含了遊戲id的資訊。

3.2 分表

還是在那家遊戲平臺公司,我們還有另外一個業務就是:金鑽會員

說白了就是打造了一套跟遊戲相關的會員體系,為了保持使用者的活躍度,開通會員有很多福利,比如:送遊戲幣、充值有折扣、積分兌換、抽獎、專屬客服等等。

在這套會員體系當中,有個非常重要的功能就是:積分

使用者有很多種途徑可以獲取積分,比如:簽到、充值、玩遊戲、抽獎、推廣、參加活動等等。

積分用什麼用途呢?

  1. 退換實物禮物
  2. 兌換遊戲幣
  3. 抽獎

說了這麼多,其實就是想說,一個使用者一天當中,獲取積分或消費積分都可能有很多次,那麼,一個使用者一天就可能會產生幾十條記錄。

如果使用者多了的話,積分相關的資料量其實挺驚人的。

我們當時考慮了,水平方向的資料量可能會很大,但是使用者併發量並不大,不像登入介面那樣。

所以採用的方案是:分表

當時使用一個積分資料庫就夠了,但是分了128張表。然後根據使用者id,進行hash除以128取模。

需要特別注意的是,分表的數量最好是2的冪次方,方便以後擴容。

3.3 分庫分表

後來我去了一家從事餐飲軟體開發的公司。這個公司有個特點是在每天的中午和晚上的就餐高峰期,使用者的併發量很大。

使用者吃飯前需要通過我們系統點餐,然後下單,然後結賬。當時點餐和下單的併發量挺大的。

餐廳可能會有很多人,每個人都可能下多個訂單。這樣就會導致使用者的併發量高,並且資料量也很大。

所以,綜合考慮了一下,當時我們採用的技術方案是:分庫分表

經過調研之後,覺得使用了當當網開源的基於jdbc的中介軟體框架:sharding-jdbc

當時分了4個庫,每個庫有32張表。

4 總結

上面主要從:垂直和水平,兩個方向介紹了我們的系統為什麼要分庫分表。

說實話垂直方向(即業務方向)更簡單。

在水平方向(即資料方向)上,分庫分表的作用,其實是有區別的,不能混為一談。

  • 分庫:是為了解決資料庫連線資源不足問題,和磁碟IO的效能瓶頸問題。
  • 分表:是為了解決單表資料量太大,sql語句查詢資料時,即使走了索引也非常耗時問題。此外還可以解決消耗cpu資源問題。
  • 分庫分表:可以解決 資料庫連線資源不足、磁碟IO的效能瓶頸、檢索資料耗時 和 消耗cpu資源等問題。

如果在有些業務場景中,使用者併發量很大,但是需要儲存的資料量很少,這時可以只分庫,不分表。

如果在有些業務場景中,使用者併發量不大,但是需要儲存的數量很多,這時可以只分表,不分庫。

如果在有些業務場景中,使用者併發量大,並且需要儲存的數量也很多時,可以分庫分表。

好了,今天的內容就先到這裡。

是不是有點意猶未盡?

沒關係,其實分庫分表相關內容挺多的,本文作為分庫分表系列的第一彈,作為一個開胃小菜吧,分享給大家。

在文章末尾順便提幾個問題:

  1. 分庫分表的具體實現方案有哪些?
  2. 分庫分表後如何平滑擴容?
  3. 分庫分表後帶來了哪些問題?
  4. 如何在專案中實現分庫分表功能?

歡迎關注,敬請期待我的下一篇文章。

最後說一句(求關注,別白嫖我)
如果這篇文章對您有所幫助,或者有所啟發的話,幫忙掃描下發二維碼關注一下,您的支援是我堅持寫作最大的動力。

求一鍵三連:點贊、轉發、在看。

關注公眾號:【蘇三說技術】,在公眾號中回覆:面試、程式碼神器、開發手冊、時間管理有超讚的粉絲福利,另外回覆:加群,可以跟很多BAT大廠的前輩交流和學習。