最近工作室開始了一個專案,由於需求方面的問題,資料庫的設計開始往中型電商系統靠近。也趁此機會,學習一下資料庫的優化策略,。
原文地址:blog.csdn.net/seudongnan/…
資料庫的優化策略
隨著網際網路的普及,電商行業的發展,一個大型的電商平臺將對資料庫造成極大的負載。為了維持系統的穩定性和擴充性,通過資料切分來提高網站效能,橫向擴充套件資料層已經成為架構人員首選方式。
水平切分資料庫
:可以降低單臺機器的負載,同時最大限度的降低了當機產生的損失。負載均衡策略
:可以降低單臺機器的訪問負載,降低當機的可能性。叢集方案
:解決了資料庫當機帶來的單點資料庫不能訪問的問題。讀寫分離策略
:最大限度提高了應用中讀取資料的訪問量和併發量
基本原理
資料切分
資料切分
(Sharding)是水平擴充套件
(Scale Out,或叫做橫向擴充套件)的解決方案。
Sharding的主要目的
突破單節點資料庫伺服器的I/O能力限制,解決資料庫擴充套件性的問題。
Sharding的實現策略
Sharding的實現是通過一系列的切分策略,將資料水平切分到不同的Database或者table中。在查詢過程中,通過一定的路由策略,找到需要查詢的具體Database或table,進行Query操作。
舉個例子:
我們要對一張article
表進行切分,article
中有兩個主要欄位,article_id
和user_id
。我們可以採用這樣的切分策略:將user_id
在1~10000的資料寫入DB1,10001~20000的資料寫入DB2,以此類推,這就是資料庫的切分。
當然,我們將切分策略反轉,即可從一個給定的user_id
來查詢到具體的記錄,這個過程被稱為DB路由
。
資料切分的方式
資料切分可以是物理上
的,也就是對資料進行一系列的切分策略,分佈到不同的DB伺服器上,通過DB路由
規則訪問相應的資料庫。以此降低單臺機器的負載壓力。
資料切分也可以是資料庫內
的,對資料進行一系列的切分策略,將資料分佈到一個資料庫不同的表中,比如將article
分為article_001
,article_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
,也就是每一個分庫的節點我們引入多臺機器,每臺機器儲存的資料是一樣的,一般情況下這多臺機器分攤負載,當出現當機情況,負載均衡器將分配負載給這臺當機的機器。這樣一來,就解決了容錯性的問題。
如上圖所示,整個資料層有Group1
,Group2
,Group3
三個叢集組成,這三個叢集就是資料水平切分的結果,當然這三個叢集也就組成了一個包含完整資料的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,這個資料庫節點不能使用,請不要給它分配負載。一個是主動的監聽機制,一個是被動的被告知的機制。兩者各有所長。但是都可以達到同樣的效果。這樣一來剛才假設的問題就不會發生了,即使就是發生了,那麼發生的概率也會降到最低。
上面的文字中提到的Master
和Slave
,我們並沒有做太多深入的講解。一個Group
由1個Master
和N個Slave
組成。為什麼這麼做呢?其中Master
負責寫操作的負載,也就是說一切寫的操作都在Master
上進行,而讀的操作則分攤到Slave
上進行。這樣一來的可以大大提高讀取的效率。在一般的網際網路應用中,經過一些資料調查得出結論,讀/寫的比例大概在 10:1左右 ,也就是說大量的資料操作是集中在讀的操作,這也就是為什麼我們會有多個Slave
的原因。
但是為什麼要分離讀和寫呢?熟悉DB的研發人員都知道,寫操作涉及到鎖的問題,不管是行鎖還是表鎖還是塊鎖,都是比較降低系統執行效率的事情。我們這樣的分離是把寫操作集中在一個節點上,而讀操作其其他 的N個節點上進行,從另一個方面有效的提高了讀的效率,保證了系統的高可用性。