分散式資料庫的架構演變之路

擱淺記憶發表於2018-05-10

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

1,單資料庫架構

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


從上圖可以看到,我們在一個專案中集中了註冊、登陸、購物三個模組的業務程式碼,並且這三個業務模組都讀取同一個業務資料庫。

但隨著專案的不斷推進,使用者量不斷增長,單臺應用伺服器已經無法承受如此巨大的流量了。此時常見的做法是把專案進行分散式部署,分散單臺伺服器的流量,從而可以暫時緩解使用者增長帶來的應用伺服器壓力。此時的專案架構圖如下所示:

但隨著我們部署的應用伺服器越來越多,後端的單臺資料庫伺服器已經無法承受如此巨大的流量了。為了儘快緩解使用者訪問壓力,我們一般是在應用伺服器與資料庫伺服器中間加多一個快取層,通過快取可以抵消掉一部分的資料庫查詢操作。此時的專案架構圖如下所示:


但是增加資料庫快取層只能緩解資料庫訪問壓力,攔截部分資料庫訪問請求。隨著使用者訪問量的進一步增長,資料庫訪問的瓶頸還是會進一步凸顯。這個時候,我們不得不對資料層的架構進行改造。

2,主從資料庫架構

這個時候常用的解決方案就是將原本單臺資料庫伺服器變成主從模式的資料庫伺服器,即一臺資料庫作為主庫支援寫入資料,一臺資料庫作為讀庫支援查詢資料。此時專案的架構圖如下所示:


我們通過資料庫主從同步實現了讀寫分離,將所有讀操作都引導到從庫進行,將所有寫操作都引導到主庫進行。

因為我們對資料庫層進行了改造,規定所有讀資料庫操作要訪問從庫,所有寫資料庫操作要訪問主庫,那麼我們就必須對原來的程式碼進行改造。


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 伺服器。

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

  • 當我們修改了註冊模組的時候,我們需要整個專案都發布一次,這樣會影響到登入、購物模組的正常使用。

  • 即使每次改動的程式碼即使很小,我們還是需要釋出整個專案包,這使得每次釋出的程式碼包非常巨大。

  • 隨著業務量的不斷增長,我們會發現即使實現了主從的讀寫分離,資料庫的壓力也是非常大,似乎快要承受不了了。

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

1,垂直切分資料庫架構

此時為了各個業務模組不互相影響,我們把應用層進行垂直拆分,即把註冊模組、登陸模組、購物模組都單獨作為一個應用系統,分別讀寫獨立的資料庫伺服器。此時,我們的系統架構圖如下圖所示:


實現了垂直拆分之後,我們可以成功解決上面說到的三個問題:業務模組相互影響問題、單資料庫壓力問題。

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

  • 使用者資料庫  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 通過自身配置可以完成這個任務。

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

4,水平切分資料庫架構

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

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

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

當我們在程式碼中查詢使用者資料時,我們先根據使用者 ID 取餘判斷其應該操作的表,之後再查詢對應的表。例如 UserId 為 90749738 的使用者就應該查詢 User_38 表,UserId 為 74847383 的使用者就應該查詢 User_83 表。

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

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

6,總結

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

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

通過這篇文章,我們瞭解了 MyCat 的誕生背景以及其最基本的作用。

後續將持續介紹MyCat的具體使用。


相關文章