天天看點

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

在高并發系統當中,分庫分表是必不可少的技術手段之一,同時也是BAT等大廠面試時,經常考的熱門考題。

你知道我們為什麼要做分庫分表嗎?

這個問題要從兩條線說起:<code>垂直方向</code> 和 <code>水準方向</code>。

<code>垂直方向</code>主要針對的是<code>業務</code>,下面聊聊業務的發展跟分庫分表有什麼關系。

在系統初期,業務功能相對來說比較簡單,系統子產品較少。

為了快速滿足疊代需求,減少一些不必要的依賴。更重要的是減少系統的複雜度,保證開發速度,我們通常會使用<code>單庫</code>來儲存資料。

系統初期的資料庫架構如下:

此時,使用的資料庫方案是:<code>一個資料庫</code>包含<code>多張業務表</code>。 使用者讀資料請求和寫資料請求,都是操作的同一個資料庫。

系統上線之後,随着業務的發展,不斷的添加新功能。導緻單表中的字段越來越多,開始變得有點不太好維護了。

一個使用者表就包含了幾十甚至上百個字段,管理起來有點混亂。

這時候該怎麼辦呢?

答:<code>分表</code>。

将<code>使用者表</code>拆分為:<code>使用者基本資訊表</code> 和 <code>使用者擴充表</code>。

使用者基本資訊表中存的是使用者最主要的資訊,比如:使用者名、密碼、别名、手機号、郵箱、年齡、性别等核心資料。

這些資訊跟使用者息息相關,查詢的頻次非常高。

而使用者擴充表中存的是使用者的擴充資訊,比如:所屬機關、戶口所在地、所在城市等等,非核心資料。

這些資訊隻有在特定的業務場景才需要查詢,而絕大數業務場景是不需要的。

是以通過分表把核心資料和非核心資料分開,讓表的結構更清晰,職責更單一,更便于維護。

除了按實際業務分表之外,我們還有一個常用的分表原則是:把調用頻次高的放在一張表,調用頻次低的放在另一張表。

有個非常經典的例子就是:訂單表和訂單詳情表。

不知不覺,系統已經上線了一年多的時間了。經曆了N個疊代的需求開發,功能已經非常完善。

系統功能完善,意味着系統各種關聯關系,錯綜複雜。

此時,如果不趕快梳理業務邏輯,後面會帶來很多隐藏問題,會把自己坑死。

這就需要按業務功能,劃分不同領域了。把相同領域的表放到同一個資料庫,不同領域的表,放在另外的資料庫。

具體拆分過程如下:

将使用者、産品、物流、訂單相關的表,從原來一個資料庫中,拆分成單獨的使用者庫、産品庫、物流庫和訂單庫,一共四個資料庫。

在這裡為了看起來更直覺,每個庫我隻畫了一張表,實際場景可能有多張表。

這樣按領域拆分之後,每個領域隻用關注自己相關的表,職責更單一了,一下子變得更好維護了。

有時候按業務,隻分庫,或者隻分表是不夠的。比如:有些财務系統,需要按月份和年份彙總,所有使用者的資金。

這就需要做:<code>分庫分表</code>了。

每年都有個單獨的資料庫,每個資料庫中,都有12張表,每張表存儲一個月的使用者資金資料。

這樣分庫分表之後,就能非常高效的查詢出某個使用者每個月,或者每年的資金了。

此外,還有些比較特殊的需求,比如需要按照地域分庫,比如:華中、華北、華南等區,每個區都有一個單獨的資料庫。

甚至有些遊戲平台,按接入的遊戲廠商來做分庫分表。

<code>水分方向</code>主要針對的是<code>資料</code>,下面聊聊資料跟分庫分表又有什麼關系。

在系統初期,由于使用者非常少,是以系統并發量很小。并且存在表中的資料量也非常少。

這時的資料庫架構如下:

此時,使用的資料庫方案同樣是:<code>一個master資料庫</code>包含<code>多張業務表</code>。

使用者讀資料請求和寫資料請求,都是操作的同一個資料庫,該方案比較适合于并發量很低的業務場景。

系統上線一段時間後,使用者數量增加了。

此時,你會發現使用者的請求當中,讀資料的請求占據了大部分,真正寫資料的請求占比很少。

衆所周知,<code>資料庫連接配接是有限的</code>,它是非常寶貴的資源。而每次資料庫的讀或寫請求,都需要占用至少一個資料庫連接配接。

如果寫資料請求需要的資料庫連接配接,被讀資料請求占用完了,不就寫不了資料了?

這樣問題就嚴重了。

為了解決該問題,我們需要把<code>讀庫</code>和<code>寫庫</code>分開。

于是,就出現了主從讀寫分離架構:

考慮剛開始使用者量還沒那麼大,選擇的是<code>一主一從</code>的架構,也就是常說的一個master一個slave。

所有的寫資料請求,都指向主庫。一旦主庫寫完資料之後,立馬異步同步給從庫。這樣所有的讀資料請求,就能及時從從庫中擷取到資料了(除非網絡有延遲)。

讀寫分離方案可以解決上面提到的單節點問題,相對于單庫的方案,能夠更好的保證系統的穩定性。

因為如果主庫挂了,可以更新從庫為主庫,将所有讀寫請求都指向新主庫,系統又能正常運作了。

讀寫分離方案其實也是分庫的一種,它相對于為資料做了備份,它已經成為了系統初期的首先方案。

但這裡有個問題就是:如果使用者量确實有些大,如果master挂了,更新slave為master,将所有讀寫請求都指向新master。

但此時,如果這個新master根本扛不住所有的讀寫請求,該怎麼辦?

這就需要<code>一主多從</code>的架構了:

上圖中我列的是<code>一主兩從</code>,如果master挂了,可以選擇從庫1或從庫2中的一個,更新為新master。假如我們在這裡更新從庫1為新master,則原來的從庫2就變成了新master的的slave了。

調整之後的架構圖如下:

這樣就能解決上面的問題了。

除此之外,如果查詢請求量再增大,我們還可以将架構更新為一主三從、一主四從...一主N從等。

上面的讀寫分離方案确實可以解決讀請求大于寫請求時,導緻master節點扛不住的問題。但如果某個領域,比如:使用者庫。如果注冊使用者的請求量非常大,即寫請求本身的請求量就很大,一個master庫根本無法承受住這麼大的壓力。

這時該怎麼辦呢?

答:建立多個使用者庫。

使用者庫的拆分過程如下:

在這裡我将使用者庫拆分成了三個庫(真實場景不一定是這樣的),每個庫的表結構是一模一樣的,隻有存儲的資料不一樣。

使用者請求量上來了,帶來的勢必是資料量的成本上升。即使做了分庫,但有可能單個庫,比如:使用者庫,出現了5000萬的資料。

根據經驗值,單表的資料量應該盡量控制在1000萬以内,性能是最佳的。如果有幾千萬級的資料量,用單表來存,性能會變得很差。

如果資料量太大了,需要建立的索引也會很大,從小到大檢索一次資料,會非常耗時,而且非常消耗cpu資源。

答:<code>分表</code>,這樣可以控制每張表的資料量,和索引大小。

表拆分過程如下:

我在這裡将使用者庫中的使用者表,拆分成了四張表(真實場景不一定是這樣的),每張表的表結構是一模一樣的,隻是存儲的資料不一樣。

如果以後使用者資料量越來越大,隻需再多分幾張使用者表即可。

當系統發展到一定的階段,使用者并發量大,而且需要存儲的資料量也很多。這時該怎麼辦呢?

答:需要做<code>分庫分表</code>。

如下圖所示:

圖中将使用者庫拆分成了三個庫,每個庫都包含了四張使用者表。

如果有使用者請求過來的時候,先根據使用者id路由到其中一個使用者庫,然後再定位到某張表。

路由的算法挺多的:

<code>根據id取模</code>,比如:id=7,有4張表,則7%4=3,模為3,路由到使用者表3。

<code>給id指定一個區間範圍</code>,比如:id的值是0-10萬,則資料存在使用者表0,id的值是10-20萬,則資料存在使用者表1。

<code>一緻性hash算法</code>

這篇文章就不過多介紹了,後面會有文章專門介紹這些路由算法的。

接下來,廢話不多說,給大家分享三個我參與過的分庫分表項目經曆,給有需要的朋友一個參考。

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

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

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

這裡有兩個問題:

每個遊戲廠商的接入方式可能都不一樣,賬号體系映射關系也有差異。

使用者都從我們平台登入,成功之後跳轉到遊戲廠商的遊戲頁面。當時有N個遊戲廠商接入了,活躍的遊戲玩家比較多,登入接口的并發量不容小觑。

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

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

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

還是在那家遊戲平台公司,我們還有另外一個業務就是:<code>金鑽會員</code>。

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

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

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

積分用什麼用途呢?

退換實物禮物

兌換遊戲币

抽獎

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

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

我們當時考慮了,水準方向的資料量可能會很大,但是使用者并發量并不大,不像登入接口那樣。

是以采用的方案是:<code>分表</code>。

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

需要特别注意的是,分表的數量最好是2的幂次方,友善以後擴容。

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

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

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

是以,綜合考慮了一下,當時我們采用的技術方案是:<code>分庫分表</code>。

經過調研之後,覺得使用了當當網開源的基于jdbc的中間件架構:<code>sharding-jdbc</code>。

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

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

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

在水準方向(即資料方向)上,<code>分庫</code>和<code>分表</code>的作用,其實是有差別的,不能混為一談。

<code>分庫</code>:是為了解決資料庫連接配接資源不足問題,和磁盤IO的性能瓶頸問題。

<code>分表</code>:是為了解決單表資料量太大,sql語句查詢資料時,即使走了索引也非常耗時問題。此外還可以解決消耗cpu資源問題。

<code>分庫分表</code>:可以解決 資料庫連接配接資源不足、磁盤IO的性能瓶頸、檢索資料耗時 和 消耗cpu資源等問題。

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

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

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

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

是不是有點意猶未盡?

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

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

分庫分表的具體實作方案有哪些?

分庫分表後如何平滑擴容?

分庫分表後帶來了哪些問題?

如何在項目中實作分庫分表功能?

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

最後說一句(求關注,别白嫖我)

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

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

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