分佈式數據庫架構的演變之路

MyCat 是一個數據庫分庫分表中間件,使用 MyCat 可以非常方便地實現數據庫的分庫分表查詢,並且減少項目中的業務代碼。今天我們將通過數據庫架構發展的演變來介紹 MyCat 的誕生背景,以及 MyCat 在其中扮演的角色,從而使得大家對 MyCat 的誕生及其作用有深入的理解。

  • 單數據庫架構
    一個項目在初期的時候,爲了儘可能快地驗證市場,其對業務系統的最大要求是快速實現。在這個階段,代碼開發人員爲了能快速實現業務系統,一般都是將所有層級(MVC)的業務代碼都寫在同一個項目中,所有的業務數據都存放在同一個數據庫中。此時,項目的整體架構圖如下所示:
    單架構

從上圖可以看到,我們在一個項目中集中了註冊、登陸、購物三個模塊的業務代碼,並且這三個業務模塊都讀取同一個業務數據庫。

隨着項目的不斷推進,用戶量不斷增長,單臺應用服務器已經無法承受如此巨大的流量了。此時常見的做法是把項目進行分佈式部署,分散單臺服務器的流量,從而可以暫時緩解用戶增長帶來的應用服務器壓力。此時的項目架構圖如下所示:
服務器分佈式
但隨着我們部署的應用服務器越來越多,後端的單臺數據庫服務器已經無法承受如此巨大的流量了。爲了儘快緩解用戶訪問壓力,我們一般是在應用服務器與數據庫服務器中間加多一個緩存層,通過緩存可以抵消掉一部分的數據庫查詢操作。此時的項目架構圖如下所示:
加上一個緩存層
但是增加數據庫緩存層只能緩解數據庫訪問壓力,攔截部分數據庫訪問請求。隨着用戶訪問量的進一步增長,數據庫訪問的瓶頸還是會進一步凸顯。這個時候,我們不得不對數據層的架構進行改造。

  • 主從數據庫架構
    這個時候常用的解決方案就是將原本單臺數據庫服務器變成主從模式的數據庫服務器,即一臺數據庫作爲主庫支持寫入數據,一臺數據庫作爲讀庫支持查詢數據。此時項目的架構圖如下所示:
    數據庫主從架構
    我們通過數據庫主從同步實現了讀寫分離,將所有讀操作都引導到從庫進行,將所有寫操作都引導到主庫進行。
    因爲我們對數據庫層進行了改造,規定所有讀數據庫操作要訪問從庫,所有寫數據庫操作要訪問主庫,那麼我們就必須對原來的代碼進行改造。
public User selectUser(){
    dataTemplate.selectById(...);
}
public User insertUser(){
    dataTemplate.insert(user);
}

上面是改造前的代碼,無論是讀操作還是寫操作,我們都使用同一個數據源進行操作。但爲了適應新的數據庫架構,我們必須在代碼中手動判斷應該請求哪個數據源。

public User selectUser(){
    readTemplate.selectById(...);
}
public User insertUser(){
    writeTemplate.insert(user);
}

經過修改後的代碼,開發根據自身經驗判斷應該選擇哪個數據源進行操作。當是讀操作的時候,我們選擇 readTemplate。當是寫操作的時候,我們選擇 writeTemplate。

但作爲一個程序員,我們隱隱約約覺得識別應該用哪個數據源這個判斷不應該人工判斷,而應該自動讓代碼去判斷。畢竟這個判斷的模式很簡單 —— 如果是 select 那麼就用讀的數據源,如果是其他那麼就用寫的數據源。

其實這個就是 MyCat 的用途之一,即作爲一個數據庫中間件去解決數據源判斷問題。如果我們使用 MyCat 作爲數據庫中間件,那麼我們不需要關心我應該使用哪個數據源。MyCat 幫我們屏蔽了不同數據源的差異,對於我們來說就只有一個數據源,這個數據源能處理寫操作,也能處理讀操作。上面查詢和插入的代碼就可以變成下面這樣:

public User selectUser(){
    dataTemplate.selectById(...);
}
public User insertUser(){
    dataTemplate.insert(user);
}

實現了主從數據庫架構,再使用 MyCat,你發現我們並不需要去修改太多的代碼,只需要將數據源改爲 MyCat 地址即可。MyCat 自動把我們所有的語句發送給後端的 MySQL 服務器。

當我們使用了主從數據庫架構之後,我們會發現我們能支撐更多的用戶訪問和請求了。但隨着業務的進一步發展,其實可以發現會存在一些問題:

  • 當我們修改了註冊模塊的時候,我們需要整個項目都發布一次,這樣會影響到登錄、購物模塊的正常使用。
  • 即使每次改動的代碼即使很小,我們還是需要發佈整個項目包,這使得每次發佈的代碼包非常巨大。
  • 隨着業務量的不斷增長,我們會發現即使實現了主從的讀寫分離,數據庫的壓力也是非常大,似乎快要承受不了了。

上面說的這些問題只是實戰中遇到的一部分問題,事實上遇到的問題只會更多不會更少,而且隨着業務的不斷髮展會愈加凸顯。

  • 垂直切分數據庫架構

此時爲了各個業務模塊不互相影響,我們把應用層進行垂直拆分,即把註冊模塊、登陸模塊、購物模塊都單獨作爲一個應用系統,分別讀寫獨立的數據庫服務器。此時,我們的系統架構圖如下圖所示:
垂直切分數據庫架構
實現了垂直拆分之後,我們可以成功解決上面說到的三個問題:業務模塊相互影響問題、單數據庫壓力問題。

但是隨着業務的進一步擴大,我們又增加了許多業務模塊:客服模塊、錢包模塊、個人中心模塊、收藏夾模塊、訂單模塊等。按照我們之前所設計的數據庫架構,我們會存在許多個數據源,這些數據源分散在各個項目中:

用戶數據庫 192.168.0.1

商品數據庫 192.168.0.2

短信數據庫 192.168.0.3

客服數據庫 192.168.0.4

錢包數據庫 192.168.0.5

對於一個項目管理者來說,這麼多的數據源分散在不同項目中,怎麼統一管理是一個問題。很多時候我們都很難記住這個項目連接的是哪個數據庫,那個項目連接的是哪個數據庫。

但如果你使用了 MyCat 作爲數據庫中間件的話,MyCat 就可以幫你解決這個問題。對於所有項目來說,它們只需要統一連接 MyCat 對外提供的一個地址,而 MyCat 則幫這些項目聯繫所有後端的 MySQL 數據庫。對於前端的項目倆說,它們只知道 MyCat 這個數據庫中間件,而不需要去理會我到底連接哪個數據庫,MyCat 通過自身配置可以完成這個任務。

哪個表的冗餘代碼,從而讓開發人員更專注於業務邏輯的開發。

  • 水平切分數據庫架構

    當數據庫架構經歷了主從架構、垂直拆分架構之後,應對一般的業務讀寫是沒有什麼問題了。但對於一些核心的業務數據,可能還是會有瓶頸問題,例如用戶模塊。

對於一些用戶量高達一個億的用戶系統來說,即使經過主從架構、垂直拆分架構的優化,但其用戶數據庫的單個表裏需要存儲的數據還是高達一個億的大小。如果我們把所有的數據都存放在一個表裏,無論是註冊時的插入數據,或者是登陸時的查詢數據,勢必會變得很慢。

這時候,我們就不得不對這些高數據量的核心業務表進行水平拆分,即將海量的數據記錄拆分到多張表中保存。例如我們一開始可能只有一張 User 表,我們將 User 表按照用戶 ID 對 1000 取餘進行拆分,那麼我們就會有 1000 張表,分別是 User_000 至 User_999。此時,項目的架構圖如下所示:
水平切分數據庫架構

當我們在代碼中查詢用戶數據時,我們先根據用戶 ID 取餘判斷其應該操作的表,之後再查詢對應的表。例如 UserId 爲 90749738 的用戶就應該查詢 User_38 表,UserId 爲 74847383 的用戶就應該查詢 User_83 表。

通過水平拆分,我們成功解決了海量數據核心業務表的讀寫瓶頸問題。但此時在代碼層面上有一個問題出現了,那就是我們需要在查詢數據庫之前,根據 UserId 去判斷應該查詢哪個表,這個操作對於所有業務模塊來說都是高度一致的,應該抽離成一個公用的項目。

與判斷應該使用讀數據源還是寫數據源一致,我們都覺得這樣機械的任務不應該丟給程序員做,應該讓機器去做。這其實就是 MyCat 可以幫我們做的事情:MyCat 通過配置一系列的分庫分表規則,讓 MyCat 幫我們自動判斷應該查詢哪一個分表。通過使用 MyCat 數據庫中間件,我們可以省去在代碼層判斷查詢哪個表的冗餘代碼,從而讓開發人員更專注於業務邏輯的開發。

  • 最後
    從單一的數據庫架構,到主從讀寫分離的數據庫架構,再到垂直拆分、水平拆分的數據庫架構。我們可以看到 MyCat 幫我們解決了讀寫數據源判斷、繁雜數據源地址、分表判斷這三個機械的重複性的問題。

但 MyCat 發展至今,其功能已經遠遠超過上面說的這三個。例如 MyCat 支持主從切換功能,當數據庫主庫發生網絡問題或其他故障時,MyCat 可以自動切換到從庫,從而保證正常讀寫功能的進行。MyCat 的定位是一個數據庫中間件,但凡所有處於應用層和數據層之間的事情,MyCat 都可以做。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章