資料庫的讀寫分離與負載均衡策略

_Hotown發表於2019-02-18

最近工作室開始了一個專案,由於需求方面的問題,資料庫的設計開始往中型電商系統靠近。也趁此機會,學習一下資料庫的優化策略,。

原文地址:blog.csdn.net/seudongnan/…

資料庫的優化策略

隨著網際網路的普及,電商行業的發展,一個大型的電商平臺將對資料庫造成極大的負載。為了維持系統的穩定性和擴充性,通過資料切分來提高網站效能,橫向擴充套件資料層已經成為架構人員首選方式。

  • 水平切分資料庫:可以降低單臺機器的負載,同時最大限度的降低了當機產生的損失。
  • 負載均衡策略:可以降低單臺機器的訪問負載,降低當機的可能性。
  • 叢集方案:解決了資料庫當機帶來的單點資料庫不能訪問的問題。
  • 讀寫分離策略:最大限度提高了應用中讀取資料的訪問量和併發量

基本原理

資料切分

資料切分(Sharding)是水平擴充套件(Scale Out,或叫做橫向擴充套件)的解決方案。

Sharding的主要目的

突破單節點資料庫伺服器的I/O能力限制,解決資料庫擴充套件性的問題。

Sharding的實現策略

Sharding的實現是通過一系列的切分策略,將資料水平切分到不同的Database或者table中。在查詢過程中,通過一定的路由策略,找到需要查詢的具體Database或table,進行Query操作。

舉個例子:

我們要對一張article表進行切分,article中有兩個主要欄位,article_iduser_id。我們可以採用這樣的切分策略:將user_id在1~10000的資料寫入DB1,10001~20000的資料寫入DB2,以此類推,這就是資料庫的切分。

當然,我們將切分策略反轉,即可從一個給定的user_id來查詢到具體的記錄,這個過程被稱為DB路由

資料切分的方式

資料切分可以是物理上的,也就是對資料進行一系列的切分策略,分佈到不同的DB伺服器上,通過DB路由規則訪問相應的資料庫。以此降低單臺機器的負載壓力。

資料切分也可以是資料庫內的,對資料進行一系列的切分策略,將資料分佈到一個資料庫不同的表中,比如將article分為article_001article_002,若干個子表水平拼合有組成了邏輯上一個完整的article表,這樣做的目的其實也是很簡單的。舉個例子說明,比如article表中現在有5000w條資料,此時我們需要在這個表中增加(insert)一條新的資料,insert完畢後,資料庫會針對這張表重新建立索引,5000w行資料建立索引的系統開銷還是不容忽視的。但是反過來,假如我們將這個表分成100 個table呢,從article_001一直到article_100,5000w行資料平均下來,每個子表裡邊就只有50萬行資料,這時候我們向一張 只有50w行資料的table中insert資料後建立索引的時間就會呈數量級的下降,極大了提高了DB的執行時效率,提高了DB的併發量。當然分表的好處還不知這些,還有諸如寫操作的鎖操作等,都會帶來很多顯然的好處。

由此可見:分庫降低了單點機器的負載;分表,提高了資料操作的效率

接下來簡單瞭解一下分庫的方式和規則:

依然沿用之前的article表的例子

  • 號段分割槽

    user_id為1~1000在DB1,1001~2000在DB2,以此類推

    • 優點:可部分遷移
    • 缺點:資料分佈不均
  • hash取模分割槽

    user_id進行hash,然後用一個數字對應一個具體的DB。比如有4個資料庫,就將user_id%4,結果為0的對應DB1,結果為1的對應DB2,以此類推。這樣一來就可以將資料均勻分佈。

    • 優點:資料分佈均勻
    • 缺點:資料遷移麻煩,不能按照機器效能分攤資料
  • 在認證庫中儲存資料庫配置

    就是建立一個DB,這個DB單獨儲存user_id到DB的對映關係,每次訪問資料庫的時候都要先查詢一次這個資料庫,以得到具體的DB資訊,然後才能進行我們需要的查詢操作。

    • 優點:靈活性強,一對一關係
    • 缺點:每次查詢之前都要多一次查詢,效能大打折扣

分散式資料方案

  • 提供分庫規則和路由規則(RouteRule簡稱RR)

  • 引入叢集(Group)的概念,保證資料的高可用性

  • 引入負載均衡策略(LoadBalancePolicy簡稱LB)

  • 引入叢集節點可用性探測機制,對單點機器的可用性進行定時的偵測,以保證LB策略的正確實施,以確保系統的高度穩定性

  • 引入讀/寫分離,提高資料的查詢速度。

叢集

僅僅是分庫分表的資料層設計也是不夠完善的,當我們採用了資料庫切分方案,也就是說有N臺機器組成了一個完整的DB 。如果有一臺機器當機的話,也僅僅是一個DB的N分之一的資料不能訪問而已,這是我們能接受的,起碼比切分之前的情況好很多了,總不至於整個DB都不能訪問。

一般的應用中,這樣的機器故障導致的資料無法訪問是可以接受的,假設我們的系統是一個高併發的電子商務網站呢?單節點機器當機帶來的經濟損失是非常嚴重的。也就是說,現在我們這樣的方案還是存在問題的,容錯效能是經不起考驗的。

問題總是有解決方案的。我們引入叢集的概念,在此我稱之為Group,也就是每一個分庫的節點我們引入多臺機器,每臺機器儲存的資料是一樣的,一般情況下這多臺機器分攤負載,當出現當機情況,負載均衡器將分配負載給這臺當機的機器。這樣一來,就解決了容錯性的問題。

資料庫的讀寫分離與負載均衡策略

如上圖所示,整個資料層有Group1Group2Group3三個叢集組成,這三個叢集就是資料水平切分的結果,當然這三個叢集也就組成了一個包含完整資料的DB。

每一個Group包括1個Master(當然Master也可以是多個)和 N個Slave,這些Master和Slave的資料是一致的。 如果Group1中的一個slave發生了當機現象,那麼還有兩個slave是可以用的,這樣的模型總是不會造成某部分資料不能訪問的問題,除非整個 Group裡的機器全部宕掉。

在沒有引入叢集以前,我們的一次查詢的過程大致如下:

  • 請求資料層,並傳遞必要的分庫區分欄位 (通常情況下是user_id)
  • 資料層根據區分欄位Route到具體的DB,在這個確定的DB內進行資料操作。

引入叢集以後,我們的路由器上規則和策略其實只能路由到具體的Group,也就是隻能路由到一個虛擬的Group,這個Group並不是某個特定的物理伺服器。接下來需要做的工作就是找到具體的物理的DB伺服器,以進行具體的資料操作。

負載均衡器

基於這個環節的需求,我們引入了負載均衡器的概念 (LB),負載均衡器的職責就是定位到一臺具體的DB伺服器。

具體的規則如下:負載均衡器會分析當前sql的讀寫特性,如果是寫操作或者是要求實時性很強的操作的話,直接將查詢負載分到Master,如果是讀操作則通過負載均衡策略分配一個Slave

我們的負載均衡器的主要研究方向也就是負載分發策略,通常情況下負載均衡包括隨機負載均衡和加權負載均衡。隨機負載均衡很好理解,就是從N個Slave中隨機選取一個Slave。這樣的隨機負載均衡是不考慮機器效能的,它預設為每臺機器的效能是一樣的。假如真實的情況是這樣的,這樣做也是無可厚非的。假如實際情況並非如此呢?每個Slave的機器物理效能和配置不一樣的情況,再使用隨機的不考慮效能的負載均衡,是非常不科學的,這樣一來會給機器效能差的機器帶來不必要的高負載,甚至帶來當機的危險,同時高效能的資料庫伺服器也不能充分發揮其物理效能。基於此考慮從,我們引入了加權負載均衡,也就是在我們的系統內部通過一定的介面,可以給每臺DB伺服器分配一個權值,然後再執行時LB根據權值在叢集中的比重,分配一定比例的負載給該DB伺服器。當然這樣的概念的引入,無疑增大了系統的複雜性和可維護性。有得必有失,我們也沒有辦法逃過的。

叢集節點的可用性探測

有了分庫,有了叢集,有了負載均衡器,是不是就萬事大吉了呢? 事情遠沒有我們想象的那麼簡單。雖然有了這些東西,基本上能保證我們的資料層可以承受很大的壓力,但是這樣的設計並不能完全規避資料庫當機的危害。假如Group1中的slave2當機了,那麼系統的LB並不能得知,這樣的話其實是很危險的,因為LB不知道,它還會以為slave2為可用狀態,所以還是會給slave2分配負載。這樣一來,問題就出來了,客戶端很自然的就會發生資料操作失敗的錯誤或者異常。

怎樣解決這樣的問題呢?我們引入叢集節點的可用性探測機制,或者是可用性的資料推送機制。這兩種機制有什麼不同呢?首先說探測機制吧,顧名思義,探測即使,就是我的資料層客戶端,不定時對叢集中各個資料庫進行可用性的嘗試,實現原理就是嘗試性連結,或者資料庫埠的嘗試性訪問,都可以做到。

那資料推送機制又是什麼呢?其實這個就要放在現實的應用場景中來討論這個問題了,一般情況下應用的DB 資料庫當機的話我相信DBA肯定是知道的,這個時候DBA手動的將資料庫的當前狀態通過程式的方式推送到客戶端,也就是分散式資料層的應用端,這個時候在更新一個本地的DB狀態的列表。並告知LB,這個資料庫節點不能使用,請不要給它分配負載。一個是主動的監聽機制,一個是被動的被告知的機制。兩者各有所長。但是都可以達到同樣的效果。這樣一來剛才假設的問題就不會發生了,即使就是發生了,那麼發生的概率也會降到最低。

上面的文字中提到的MasterSlave ,我們並沒有做太多深入的講解。一個Group由1個Master和N個Slave組成。為什麼這麼做呢?其中Master負責寫操作的負載,也就是說一切寫的操作都在Master上進行,而讀的操作則分攤到Slave上進行。這樣一來的可以大大提高讀取的效率。在一般的網際網路應用中,經過一些資料調查得出結論,讀/寫的比例大概在 10:1左右 ,也就是說大量的資料操作是集中在讀的操作,這也就是為什麼我們會有多個Slave的原因。

但是為什麼要分離讀和寫呢?熟悉DB的研發人員都知道,寫操作涉及到鎖的問題,不管是行鎖還是表鎖還是塊鎖,都是比較降低系統執行效率的事情。我們這樣的分離是把寫操作集中在一個節點上,而讀操作其其他 的N個節點上進行,從另一個方面有效的提高了讀的效率,保證了系統的高可用性。

相關文章