您現在的位置是:首頁 > 網路遊戲首頁網路遊戲

終於明白為什麼要“分庫分表”了

簡介按照時間做的資料分表需要注意的是由於分表造成一系列記錄級別的問題,例如 Join 和 ID 生成,事務處理,同時存在這些表需要跨資料庫的可能性:Join:需要做兩次查詢,把兩次查詢的結果在應用層做合併

必須分間分庫儲存的物品是什麼

【51CTO。com原創稿件】 隨著網際網路產業的蓬勃發展,在網際網路應用上產生的資料也是與日俱增。產生大量的交易記錄和行為記錄,它們的存放和分析是我們需要面對的問題。

終於明白為什麼要“分庫分表”了

圖片來自 Pexels

例如:單表中出現了,動輒百萬甚至千萬級別的資料。“分表分庫”就成為解決上述問題的有效工具。今天和大家一起看看,如何進行分表分庫以及期間遇到的問題吧。

為什麼會分表分庫

資料庫資料會隨著業務的發展而不斷增多,因此資料操作,如增刪改查的開銷也會越來越大。

再加上物理伺服器的資源有限(CPU、磁碟、記憶體、IO 等)。最終資料庫所能承載的資料量、資料處理能力都將遭遇瓶頸。

換句話說需要合理的資料庫架構來存放不斷增長的資料,這個就是分庫分表的設計初衷。目的就是為了緩解資料庫的壓力,最大限度提高資料操作的效率。

資料分表

如果單表的資料量過大,例如千萬級甚至更多,那麼在操作表的時候就會加大系統的開銷。

每次查詢會消耗資料庫大量資源,如果需要多表的聯合查詢,這種劣勢就更加明顯了。

以 MySQL 為例,在插入資料的時候,會對錶進行加鎖,分為表鎖定和行鎖定。

無論是哪種鎖定方式,都意味著前面一條資料在操作表或者行的時候,後面的請求都在排隊,當訪問量增加的時候,都會影響資料庫的效率。

那麼既然一定要分表,那麼每張表分配多大的資料量比較合適呢?這裡建議根據業務場景和實際情況具體分析。

一般來說 MySQL 資料庫單表記錄最好控制在 500 萬條(這是個經驗數字)。既然需要將資料從一個表分別存放到多個表中,那麼來看看下面兩種分表方式吧。

垂直分表

根據業務把一個表中的欄位(Field)分到不同的表中。這些被分出去的資料通常根據業務需要,例如分出去一些不是經常使用的欄位,一些長度較長的欄位。

一般被拆分的表的欄位數比較多。主要是避免查詢的時候出現因為資料量大而造成的“跨頁”問題。

一般這種拆分在資料庫設計之初就會考慮,儘量在系統上線之前考慮調整。已經上線的專案,做這種操作是要慎重考慮的。

水平分表

將一個表中的資料,按照關鍵字(例如:ID)(或取 Hash 之後)對一個具體的數字取模,得到的餘數就是需要存放到的新表的位置。

終於明白為什麼要“分庫分表”了

用 ID 取模的分表方式分配記錄

ID 分別為 01-04 的四條記錄,如果分配到 3 個表中,那麼對 3 取模得到的餘數分別是:

ID:01 對 3 取模餘數為 1 ,存到“表 1”。

ID:02 對 3 取模餘數為 2 ,存到“表 2”。

ID:03 對 3 取模餘數為 3 ,存到“表 3”。

ID:04 對 3 取模餘數為 1 ,存到“表 1”。

當然這裡只是一個例子,實際情況需要對 ID 做 Hash 之後再計算。同時還可以針對不同表所在的不同的資料庫的資源來設定儲存資料的多少。針對每個表所在的庫的資源設定權值。

用這種方式存放資料以後,在訪問具體資料的時候需要透過一個 Mapping Table 獲取對應要響應的資料來自哪個資料表。目前比較流行的資料庫中介軟體已經幫助我們實現了這部分的功能。

也就是說不用大家自己去建立這個 Mapping Table,在做查詢的時候中介軟體幫助你實現了 Mapping Table 的功能。所以,我們這裡只需要瞭解其實現原理就可以了。

終於明白為什麼要“分庫分表”了

Mapping Table 協助分表

水平拆分還有一種情況是根據資料產生的前後順序來拆分存放。例如,主表只存放最近 2 個月的資訊,其他比較老舊的資訊拆分到其他的表中。透過時間來做資料區分。更有甚者是透過服務的地域來做資料區分的。

終於明白為什麼要“分庫分表”了

按照時間做的資料分表

需要注意的是由於分表造成一系列記錄級別的問題,例如 Join 和 ID 生成,事務處理,同時存在這些表需要跨資料庫的可能性:

Join:需要做兩次查詢,把兩次查詢的結果在應用層做合併。這種做法是最簡單的,在應用層設計的時候需要考慮。

ID:可以使用 UUID,或者用一張表來存放生成的 Sequence,不過效率都不算高。UUID 實現起來比較方便,但是佔用的空間比較大。

Sequence 表的方式節省了空間,但是所有的 ID 都依賴於單表。這裡介紹一個大廠用的 Snowflake 的方式。

Snowflake 是 Twitter 開源的分散式 ID 生成演算法,結果是一個 long 型的 ID。

其核心思想是:使用 41bit 作為毫秒數,10bit 作為機器的 ID(5 個 bit 是資料中心,5 個 bit 的機器 ID),12bit 作為毫秒內的流水號(意味著每個節點在每毫秒可以產生 4096 個 ID),最後還有一個符號位,永遠是 0。

終於明白為什麼要“分庫分表”了

Snowflake 示意圖

排序/分頁:資料分配到水平的幾個表中的時候,做排序和分頁或者一些集合操作是不容易的。

這裡根據經驗介紹兩種方法。對分表的資料先進行排序/分頁/聚合,再進行合併。對分表的資料先進行合併再做排序/分頁/聚合。

事務:存在分散式事務的可能,需要考慮補償事務或者用 TCC(Try Confirm Cancel)協助完成,這部分的內容我們下面會為大家介紹。

資料分庫

說完了分表,再來談談分庫。每個物理資料庫支援資料都是有限的,每一次的資料庫請求都會產生一次資料庫連結,當一個庫無法支援更多訪問的時候,我們會把原來的單個數據庫分成多個,幫助分擔壓力。

這裡有幾類分庫的原則,可以根據具體場景進行選擇:

根據業務不同分庫,這種情況都會把主營業務和其他功能分開。例如可以分為訂單資料庫,核算資料庫,評論資料庫。

根據冷熱資料進行分庫,用資料訪問頻率來劃分,例如:近一個月的交易資料屬於高頻資料,2-6 個月的交易資料屬於中頻資料,大於 6 個月的資料屬於低頻資料。

根據訪問資料的地域/時間範圍進行分庫。

終於明白為什麼要“分庫分表”了

單個表會分到不同的資料庫中

通常資料分庫之後,每一個數據庫包含多個數據表,多個數據庫會組成一個 Cluster/Group,提高了資料庫的可用性,並且可以把讀寫做分離。

Master 庫主要負責寫操作,Slave 庫主要負責讀操作。在應用訪問資料庫的時候會透過一個負載均衡代理,透過判斷讀寫操作把請求路由到對應的資料庫。

如果是讀操作,也會根據資料庫設定的權重或者平均分配請求。另外,還有資料庫健康監控機制,定時傳送心跳檢測資料庫的健康狀況。

如果 Slave 出現問題,會啟動熔斷機制停止對其的訪問;如果 Master 出現問題,透過選舉機制選擇新的 Master 代替。

終於明白為什麼要“分庫分表”了

主從資料庫簡圖

資料庫擴容

分庫之後的資料庫會遇到資料擴容或者資料遷移的情況。這裡推薦兩種資料庫擴容的方案。

主從資料庫擴容

我們這裡假設有兩個資料庫叢集,每個叢集分別有 M1 S1 和 M2 S2 互為主備。

終於明白為什麼要“分庫分表”了

兩個資料庫叢集示意圖

由於 M1 和 S1 互為主備所以資料是一樣的,M2 和 S2 同樣。把原有的 ID %2 模式切換成 ID %4 模式,也就是把兩個資料叢集擴充到 4 個數據庫叢集。

負載均衡器直接把資料路由到原來兩個 S1 和 S2 上面,同時 S1 和 S2 會停止與 M1 和 M2 的資料同步,單獨作為主庫(寫操作)存在。

這些修改不需要重啟資料庫服務,只需要修改代理配置就可以完成。由於 M1 M2 S1 S2 中會存在一些冗餘的資料,可以後臺起服務將這些冗餘資料刪除,不會影響資料使用。

終於明白為什麼要“分庫分表”了

兩個叢集中的兩個主從,分別擴充套件成四個叢集中的四個主機

此時,再考慮資料庫可用性,將擴充套件後的 4 個主庫進行主備操作,針對每個主庫都建立對應的從庫,前者負責寫操作,後者負責讀操作。下次如果需要擴容也可以按照類似的操作進行。

終於明白為什麼要“分庫分表”了

從兩個叢集擴充套件成四個叢集

雙寫資料庫擴容

在沒有資料庫主從配置的情況下的擴容,假設有資料庫 M1 M2 如下圖:

終於明白為什麼要“分庫分表”了

擴充套件前的兩個主庫

需要對目前的兩個資料庫做擴容,擴容之後是 4 個庫如下圖。新增的庫是 M3,M4 路由的方式分別是 ID%2=0 和 ID%2=1。

終於明白為什麼要“分庫分表”了

新增兩個主庫

這個時候新的資料會同時進入 M1 M2 M3 M4 四個庫中,而老資料的使用依舊從 M1 M2 中獲取。

與此同時,後臺服務對 M1 M3,M2 M4 做資料同步,建議先做全量同步再做資料校驗。

終於明白為什麼要“分庫分表”了

老庫給新庫做資料同步

當完成資料同步之後,四個庫的資料保持一致了,修改負載均衡代理的配置為 ID%4 的模式。此時擴容就完成了,從原來的 2 個數據庫擴充套件成 4 個數據庫。

當然會存在部分的資料冗餘,需要像上面一個方案一樣通過後臺服務刪除這些冗餘資料,刪除的過程不會影響業務。

終於明白為什麼要“分庫分表”了

資料同步以後做 Hash 切分

分散式事務原理

架構設計的分表分庫帶來的結果是我們不得不考慮分散式事務,今天我們來看看分散式事務需要記住哪兩個原理。

CAP

網際網路應用大多會使用分表分庫的操作,這個時候業務程式碼很可能會同時訪問兩個不同的資料庫,做不同的操作。同時這兩個操作有可能放在同一個事務中處理。

終於明白為什麼要“分庫分表”了

這裡引出分散式系統的 CAP 理論,他包括以下三個屬性:

一致性(Consistency):分散式系統中的所有資料,同一時刻有同樣的值。

業務程式碼往資料庫 01 這個節點寫入記錄 A,資料庫 01 把 A 記錄同步到資料庫 02,業務程式碼再從資料庫 02 中讀出的記錄也是 A。那麼兩個資料庫存放的資料就是一致的。

終於明白為什麼要“分庫分表”了

一致性簡圖

可用性(Availability):分散式系統中一部分節點出現故障,分散式系統仍舊可以響應使用者的請求。

假設資料庫 01 和 02 同時存放記錄 A,由於資料庫 01 掛掉了,業務程式碼不能從中獲取資料。

那麼業務程式碼可以從資料庫 02 中獲取記錄 A。也就是在節點出現問題的時候,還保證資料的可用性。

終於明白為什麼要“分庫分表”了

可用性簡圖

分割槽容錯性(Partition tolerance):假設兩個資料庫節點分別在兩個區,而兩個區的通訊發生了問題。就不能達成資料一致,這就是分割槽的情況,我就需要從 C 和 A 之間做出選擇。

是選擇可用性(A),獲取其中一個區的資料。還是選擇一致性(C),等待兩個區的資料同步了再去獲取資料。

這種情況的前提是兩個節點的通訊失敗了,寫入資料庫 01 記錄的時候,需要鎖住資料庫 02 記錄不讓其他的業務程式碼修改,直到資料庫 01 記錄完成修改。因此 C 和 A 在此刻是矛盾的。兩者不能兼得。

終於明白為什麼要“分庫分表”了

分割槽容錯簡圖

BASE

Base 原理廣泛應用在資料量大,高併發的網際網路場景。一起來看看都包含哪些:

基本可用(Basically Available): 不會因為某個節點出現問題就影響使用者的請求。

即使在流量激增的情況下,也會考慮透過限流降級的辦法保證使用者的請求是可用的。

比如,電商系統在流量激增的時候,資源會向核心業務傾斜,其他的業務降級處理。

軟狀態( Soft State):一條資料如果存在多個副本,允許副本之間同步的延遲,在較短時間內能夠容忍不一致。這個正在同步並且還沒有完成同步的狀態稱為軟狀態。

終於明白為什麼要“分庫分表”了

最終一致性( Eventual Consistency):最終一致性是相對於強一致性來說的,強一致性是要保證所有的資料都是一致的,是實時同步。

而最終一致性會容忍一小段時間資料的不一致,但過了這段時間以後資料會保證一致。其包含以下幾種“一致性”:

①因果一致性(Causal Consistency)

如果有兩個程序 1 和 2 都對變數 X 進行操作,“程序 1” 寫入變數 X,“程序 2”需要讀取變數 X,然後用這個 X 來計算 X+2。

這裡“程序 1”和“程序 2” 的操作就存在因果關係。“程序 2” 的計算依賴於程序 1 寫入的 X,如果沒有 X 的值,“程序 2”無法計算。

終於明白為什麼要“分庫分表”了

兩個程序對同一變數進行操作

②讀己之所寫(Read Your Writes)

“程序 1”寫入變數 X 之後,該程序可以獲取自己寫入的這個值。

終於明白為什麼要“分庫分表”了

程序寫入的值的同時獲取值

③會話一致性(Session Consistency)

如果一個會話中實現來讀己之所寫。一旦資料更新,客戶端只要在同一個會話中就可以看到這個更新的值。

終於明白為什麼要“分庫分表”了

多程序在同一會話需要看到相同的值

④單調寫一致性(Monotonic Write Consistency)

“程序 1”如果有三個操作分別是 1,2,3。“程序 2”有兩個操作分別是 1,2。當程序請求系統時,系統會保證按照程序中操作的先後順序來執行。

終於明白為什麼要“分庫分表”了

多程序多操作透過佇列方式執行

分散式事務方案

說完了分散式的原理,再來提一下分散式的方案。由於所處場景不一樣,所以方案也各有不同,這裡介紹兩種比較流行的方案,兩段式和 TCC(Try,Confirm,Cancel)。

兩階段提交

顧名思義,事務會進行兩次提交。這裡需要介紹兩個概念,一個是事務協調者,也叫事物管理器。

它是用來協調事務的,所有事務什麼時候準備好了,什麼時候可以提交了,都由它來協調和管理。

另一個是參與者,也叫資源管理器。它主要是負責處理具體事務的,管理者需要處理的資源。例如:訂票業務,扣款業務。

第一階段(準備階段):事務協調者(事務管理器)給每個參與者(資源管理器)傳送 Prepare 訊息,發這個訊息的目的是問“大家是不是都準備好了,我們馬上就要執行事務了”。

參與者會根據自身業務和資源情況進行檢查,然後給出反饋。這個檢查過程根據業務內容不同而不同。

例如:訂票業務,就要檢查是否有剩餘票。扣款業務就要檢查,餘額是否足夠。一旦檢查通過了才能返回就緒(Ready)資訊。

否則,事務將終止,並且等待下次詢問。由於這些檢查需要做一些操作,這些操作可能再之後回滾時用到,所以需要寫 redo 和 undo 日誌,當事務失敗重試,或者事務失敗回滾的時候使用。

第二階段(提交階段):如果協調者收到了參與者失敗或者超時的訊息,會給參與者傳送回滾(rollback)訊息;否則,傳送提交(commit)訊息。兩種情況處理如下:

情況 1,當所有參與者均反饋 yes,提交事務:

協調者向所有參與者發出正式提交事務的請求(即 commit 請求)。

參與者執行 commit 請求,並釋放整個事務期間佔用的資源。

各參與者向協調者反饋 ack(應答)完成的訊息。

協調者收到所有參與者反饋的 ack 訊息後,即完成事務提交。

情況 2,當有一個參與者反饋 no,回滾事務:

協調者向所有參與者發出回滾請求(即 rollback 請求)。

參與者使用第一階段中的 undo 資訊執行回滾操作,並釋放整個事務期間佔用的資源。

各參與者向協調者反饋 ack 完成的訊息。

協調者收到所有參與者反饋的 ack 訊息後,即完成事務。

終於明白為什麼要“分庫分表”了

兩個階段提交事務示意圖

TCC(Try,Confirm,Cancel)

對於一些要求高一致性的分散式事務,例如:支付系統,交易系統,我們會採用 TCC。

它包括,Try 嘗試,Confirm 確認,Cancel 取消。看下面一個例子能否幫助大家理解。

假設我們有一個轉賬服務,需要把“A 銀行”“A 賬戶”中的錢分別轉到“B銀行”“B 賬戶”和“C 銀行”“C 賬戶”中去。

假設這三個銀行都有各自的轉賬服務,那麼這次轉賬事務就形成了一次分散式事務。

我們來看看用 TCC 的方式如何解決:

終於明白為什麼要“分庫分表”了

轉賬業務示意圖

首先是 Try 階段,主要檢測資源是否可用,例如檢查賬戶餘額是否足夠,快取,資料庫,佇列是否可用等等。

並不執行具體的邏輯。如上圖,這裡從“A 賬戶”轉出之前要檢查,賬戶的總金額是否大於 100,並且記錄轉出金額和剩餘金額。

對於“B 賬戶”和“C 賬戶”來說需要知道賬戶原有總金額和轉入的金額,從而可以計算轉入後的金額。

這裡的交易資料庫設計除了有金額欄位,還要有轉出金額或者轉入金額的欄位,在 Cancel 回滾的時候使用。

終於明白為什麼要“分庫分表”了

Try 階段示意圖

如果 Try 階段成功,那麼就進入 Confirm 階段,也就是執行具體的業務邏輯。

這裡從“A 賬戶”轉出 100 元成功,剩餘總金額=220-100=120,把這個剩餘金額寫入到總金額中儲存,並且把交易的狀態設定為“轉賬成功”。

“B 賬戶”和“C 賬戶”分別設定總金額為 80=50+30 和 130=60+70,也把交易狀態設定為“轉賬成功”。則整個事務完成。

終於明白為什麼要“分庫分表”了

Confirm 階段示意圖

如果 Try 階段沒有成功,那麼服務 A B C 都要做回滾的操作。對於“A賬戶”來說需要把扣除的 100 元加回,所以總金額 220=120+100。

那麼“B 服務”和“C 服務”需要把入賬的金額從總金額裡面減去,也就是 50=80-30 和 60=130-70。

終於明白為什麼要“分庫分表”了

Cancel 階段示意圖

TCC 介面實現

這裡需要注意的是,需要針對每個服務去實現 Try,Confirm,Cancel 三個階段的程式碼。

例如上面所說的檢查資源,執行業務,回滾業務等操作。目前有很多開源的架構例如:ByteTCC、TCC-transaction 可以借鑑。

終於明白為什麼要“分庫分表”了

TCC 實現介面示意圖

TCC 可靠性

TCC 透過記錄事務處理日誌來保證可靠性。一旦 Try,Confirm,Cancel 操作的時候服務掛掉或者出現異常,TCC 會提供重試機制。另外如果服務存在非同步的情況可以採用訊息佇列的方式通訊保持事務一致。

終於明白為什麼要“分庫分表”了

重試機制示意圖

分庫表中介軟體介紹

如果覺得分表分庫之後,需要考慮的問題很多,可以使用市面上的現成的中介軟體幫我們實現。

這裡介紹幾個比較常用的中介軟體:

基於代理方式的有 MySQL Proxy 和 Amoeba。

基於 Hibernate 框架的有 Hibernate Shards。

基於 JDBC 的有當當 Sharding-JDBC。

基於 MyBatis 的類似 Maven 外掛式的蘑菇街 TSharding。

另外著重介紹 Sharding-JDBC 的架構,它的構成和“服務註冊中心”很像。

Sharding-JDBC 會提供一個 Sharding-Proxy 做代理,他會連線一個註冊中心(registry center),一旦資料庫的節點掛接到系統中,會在這個中心註冊,同時也會監控資料庫的健康狀況做心跳檢測。

而 Sharding-Proxy 本身在業務程式碼(Business Code)請求資料庫的時候可以協助做負載均衡和路由。

同時 Sharding-Proxy 本身也可以支援被 MySQL Cli 和 MySQL Workbench 檢視。

實際上如果我們理解了分表分庫的原理之後,實現並不難,很多大廠都提供了產品。

終於明白為什麼要“分庫分表”了

Sharding-Proxy 實現原理圖

總結

因為資料量的上升,為了提高效能會對系統進行分表分庫。從分表來說,有水平分表和垂直分表兩種方式。

可以根據業務,冷熱資料等來進行分庫,分庫以後透過主從庫來實現讀寫分離。

如果對分庫之後資料庫做擴容,有兩種方式,主從資料庫擴容和雙寫資料庫擴容。

分表分庫會帶來分散式事務,我們需要掌握 CAP 和 BASE 原理,同時介紹了兩階段提交和 TCC 兩個分散式事務方案。最後,介紹了流行的分表分庫中介軟體,以及其實現原理。

作者:崔皓

簡介:十六年開發和架構經驗,曾擔任過惠普武漢交付中心技術專家,需求分析師,專案經理,後在創業公司擔任技術/產品經理。善於學習,樂於分享。目前專注於技術架構與研發管理。

【51CTO原創稿件,合作站點轉載請註明原文作者和出處為51CTO。com】

Top