數據庫水平切分(拆庫拆表)的實現原理

第1章  引言 
隨着互聯網應用的普遍普及,海量數據的存儲和訪問成爲了系統設計的瓶頸問題。對於一個大型的互聯網應用,天天幾十億的PV無疑對數據庫形成了至關 高的負載。對於系統的穩定性和擴展性形成了極大的問題。經過數據切分來提升網站性能,橫向擴展數據層已經成爲架構研發人員首選的方式。水平切分數據庫,可 以下降單臺機器的負載,同時最大限度的下降了了宕機形成的損失。經過負載均衡策略,有效的下降了單臺機器的訪問負載,下降了宕機的可能性;經過集羣方案, 解決了數據庫宕機帶來的單點數據庫不能訪問的問題;經過讀寫分離策略更是最大限度了提升了應用中讀取(Read)數據的速度和併發量。目前國內的大型互聯 網應用中,大量的採用了這樣的數據切分方案,Taobao,Alibaba,Tencent,它們大都實現了本身的分佈式數據訪問層(DDAL)。以實現 方式和實現的層次來劃分,大概分爲兩個層次(Java應用爲例):JDBC層的封裝,ORM框架層的實現。就JDBC層的直接封裝而言,如今國內發展較好 的一個項目是被稱做「變形蟲」(Amoeba)的項目,由阿里集團的研究院開發,如今仍然處於測試階段(beta版),其運行效率和生產時效性有待考究。 就ORM框架層的實現而言,好比Taobao的基於ibatis和Spring的的分佈式數據訪問層,已有多年的應用,運行效率和生產實效性獲得了開發人 員和用戶的確定。本文就是以ORM框架層爲基礎而實現的分佈式數據訪問層。本課題的難點在於分庫後,路由規則的制定和選擇以及後期的擴展性,好比:如何作 到用最少的數據遷移量,達到擴充數據庫容量(增長機器節點)的目的。核心問題將圍繞數據庫分庫分表的路由規則和負載均衡策略展開。 

第2章 基本原理和概念 

2.1基本原理: 

人類認知問題的過程老是這樣的:what(什麼)-?why(爲何)-?how(怎麼 
作),接下來,本文將就這三個問題展開討論和研究: 

2.1.1什麼是數據切分 

"Shard" 這個詞英文的意思是"碎片",而做爲數據庫相關的技術用語,彷佛最先見於大型多人在線角色扮演遊戲中。"Sharding" 姑且稱之爲"分片"。Sharding 不是一門新技術,而是一個相對簡樸的軟件理念。衆所周知,MySQL 5 以後纔有了數據表分區功能,那麼在此以前,不少 MySQL 的潛在用戶都對 MySQL 的擴展性有所顧慮,而是否具有分區功能就成了衡量一個數據庫可擴展性與否的一個關鍵指標(固然不是惟一指標)。數據庫擴展性是一個永恆的話題,MySQL 的推廣者常常會被問到:如在單一數據庫上處理應用數據捉襟見肘而須要進行分區化之類的處理,是如何辦到的呢? 答案是:Sharding。  Sharding 不是一個某個特定數據庫軟件附屬的功能,而是在具體技術細節之上的抽象處理,是水平擴展(Scale Out,亦或橫向擴展、向外擴展)的解決方案,其主要目的是爲突破單節點數據庫服務器的 I/O 能力限制,解決數據庫擴展性問題。 
經過一系列的切分規則將數據水平分佈到不一樣的DB或table中,在經過相應的DB路由或者table路由規則找到須要查詢的具體的DB或者 table,以進行Query操做。這裏所說的「sharding」一般是指「水平切分」,這也是本文討論的重點。具體將有什麼樣的切分方式呢和路由方式 呢?行文至此,讀者不免有所疑問,接下來舉個簡單的例子:咱們針對一個Blog應用中的日誌來講明, 好比日誌文章(article)表有以下字段: 

article_id(int), title(varchar(128)), content(varchar(1024),user_id(int) 

面對這樣的一個表,咱們怎樣切分呢?怎樣將這樣的數據分佈到不一樣的數據庫中的表中去呢?其實分析blog的應用,咱們不可貴出這樣的結 論:blog的應用中,用戶分爲兩種:瀏覽者和blog的主人。瀏覽者瀏覽某個blog,其實是在一個特定的用戶的blog下進行瀏覽的,而blog的 主人管理本身的blog,也一樣是在特定的用戶blog下進行操做的(在本身的空間下)。所謂的特定的用戶,用數據庫的字段表示就是「user_id」。 就是這個「user_id」,它就是咱們須要的分庫的依據和規則的基礎。咱們能夠這樣作,將user_id爲1~10000的全部的文章信息放入DB1中 的article表中,將user_id爲10001~20000的全部文章信息放入DB2中的article表中,以此類推,一直到DBn。這樣一來, 文章數據就很天然的被分到了各個數據庫中,達到了數據切分的目的。接下來要解決的問題就是怎樣找到具體的數據庫呢?其實問題也是簡單明顯的,既然分庫的時 候咱們用到了區分字段user_id,那麼很天然,數據庫路由的過程固然仍是少不了user_id的。考慮一下咱們剛纔呈現的blog應用,無論是訪問別 人的blog仍是管理本身的blog,總之我都要知道這個blog的用戶是誰吧,也就是咱們知道了這個blog的user_id,就利用這個 user_id,利用分庫時候的規則,反過來定位具體的數據庫,好比user_id是234,利用該才的規則,就應該定位到DB1,假如user_id是 12343,利用該才的規則,就應該定位到DB2。以此類推,利用分庫的規則,反向的路由到具體的DB,這個過程咱們稱之爲「DB路由」。 
固然考慮到數據切分的DB設計必然是很是規,不正統的DB設計。那麼什麼樣的DB設計是正統的DB設計呢? 
咱們日常規規矩矩用的基本都是。日常咱們會自覺的按照範式來設計咱們的數據庫,負載高點可能考慮使用相關的Replication機制來提升讀寫 的吞吐和性能,這可能已經能夠知足不少需求,但這套機制自身的缺陷仍是比較顯而易見的(下文會說起)。上面提到的「自覺的按照範式設計」。考慮到數據切分 的DB設計,將違背這個一般的規矩和約束,爲了切分,咱們不得不在數據庫的表中出現冗餘字段,用做區分字段或者叫作分庫的標記字段,好比上面的 article的例子中的user_id這樣的字段(固然,剛纔的例子並無很好的體現出user_id的冗餘性,由於user_id這個字段即便就是不 分庫,也是要出現的,算是咱們撿了便宜吧)。固然冗餘字段的出現並不僅是在分庫的場景下才出現的,在不少大型應用中,冗餘也是必須的,這個涉及到高效DB 的設計,本文再也不贅述。 

2.1.2爲何要數據切分 

上面對什麼是數據切分作了個概要的描述和解釋,讀者可能會疑問,爲何須要數據切分呢?像Oracle這樣成熟穩定的數據庫,足以支撐海量數據的 存儲與查詢了?爲何還須要數據切片呢?的確,Oracle的DB確實很成熟很穩定,可是高昂的使用費用和高端的硬件支撐不是每個公司能支付的起的。試 想一下一年幾千萬的使用費用和動輒上千萬元的小型機做爲硬件支撐,這是通常公司能支付的起的嗎?即便就是能支付的起,假若有更好的方案,有更廉價且水平擴 展性能更好的方案,咱們爲何不選擇呢? 
可是,事情老是不盡人意。日常咱們會自覺的按照範式來設計咱們的數據庫,負載高點可能考慮使用相關的Replication機制來提升讀寫的吞吐 和性能,這可能已經能夠知足不少需求,但這套機制自身的缺陷仍是比較顯而易見的。首先它的有效很依賴於讀操做的比例,Master每每會成爲瓶頸所在,寫 操做須要順序排隊來執行,過載的話Master首先扛不住,Slaves的數據同步的延遲也可能比較大,並且會大大耗費CPU的計算能力,由於write 操做在Master上執行之後仍是須要在每臺slave機器上都跑一次。這時候 Sharding可能會成爲雞肋了。 Replication搞不定,那麼爲何Sharding能夠工做呢?道理很簡單,由於它能夠很好的擴展。咱們知道每臺機器不管配置多麼好它都有自身的 物理上限,因此當咱們應用已經能觸及或遠遠超出單臺機器的某個上限的時候,咱們唯有尋找別的機器的幫助或者繼續升級的咱們的硬件,但常見的方案仍是橫向擴 展, 經過添加更多的機器來共同承擔壓力。咱們還得考慮當咱們的業務邏輯不斷增加,咱們的機器能不能經過線性增加就能知足需求?Sharding能夠輕鬆的將計 算,存儲,I/O並行分發到多臺機器上,這樣能夠充分利用多臺機器各類處理能力,同時能夠避免單點失敗,提供系統的可用性,進行很好的錯誤隔離。 
綜合以上因素,數據切分是頗有必要的,且咱們在此討論的數據切分也是將MySql做爲背景的。基於成本的考慮,不少公司也選擇了Free且 Open的MySql。對MySql有所瞭解的開發人員可能會知道,MySQL 5 以後纔有了數據表分區功能,那麼在此以前,不少 MySQL 的潛在用戶都對 MySQL 的擴展性有所顧慮,而是否具有分區功能就成了衡量一個數據庫可擴展性與否的一個關鍵指標(固然不是惟一指標)。數據庫擴展性是一個永恆的話題,MySQL 的推廣者常常會被問到:如在單一數據庫上處理應用數據捉襟見肘而須要進行分區化之類的處理,是如何辦到的呢? 答案也是Sharding,也就是咱們所說的數據切分方案。 
    咱們用免費的MySQL和廉價的Server甚至是PC作集羣,達到小型機+大型商業DB的效果,減小大量的資金投入,下降運營成本,何樂而不爲呢?因此,咱們選擇Sharding,擁抱Sharding。 

2.1.3怎麼作到數據切分 

說到數據切分,再次咱們講對數據切分的方法和形式進行比較詳細的闡述和說明。 
數據切分能夠是物理上的,對數據經過一系列的切分規則將數據分佈到不一樣的DB服務器上,經過路由規則路由訪問特定的數據庫,這樣一來每次訪問面對的就不是單臺服務器了,而是N臺服務器,這樣就能夠下降單臺機器的負載壓力。 
數據切分也能夠是數據庫內的,對數據經過一系列的切分規則,將數據分佈到一個數據庫的不一樣表中,好比將article分爲 article_001,article_002等子表,若干個子表水平拼合有組成了邏輯上一個完整的article表,這樣作的目的其實也是很簡單的。 舉個例子說明,好比article表中如今有5000w條數據,此時咱們須要在這個表中增長(insert)一條新的數據,insert完畢後,數據庫會 針對這張表從新創建索引,5000w行數據創建索引的系統開銷仍是不容忽視的。可是反過來,假如咱們將這個表分紅100個table呢,從 article_001一直到article_100,5000w行數據平均下來,每一個子表裏邊就只有50萬行數據,這時候咱們向一張只有50w行數據的 table中insert數據後創建索引的時間就會呈數量級的降低,極大了提升了DB的運行時效率,提升了DB的併發量。固然分表的好處還不知這些,還有 諸如寫操做的鎖操做等,都會帶來不少顯然的好處。 
綜上,分庫下降了單點機器的負載;分表,提升了數據操做的效率,尤爲是Write操做的效率。行文至此咱們依然沒有涉及到如何切分的問題。接下來,咱們將對切分規則進行詳盡的闡述和說明。 
上文中提到,要想作到數據的水平切分,在每個表中都要有相冗餘字符做爲切分依據和標記字段,一般的應用中咱們選用user_id做爲區分字段,基於此就有以下三種分庫的方式和規則:(固然還能夠有其餘的方式) 
按號段分: 
(1) user_id爲區分,1~1000的對應DB1,1001~2000的對應DB2,以此類推; 
優勢:可部分遷移 
缺點:數據分佈不均 

(2)hash取模分: 
對user_id進行hash(或者若是user_id是數值型的話直接使用user_id的值也可),而後用一個特定的數字,好比應用中須要將 一個數據庫切分紅4個數據庫的話,咱們就用4這個數字對user_id的hash值進行取模運算,也就是user_id%4,這樣的話每次運算就有四種可 能:結果爲1的時候對應DB1;結果爲2的時候對應DB2;結果爲3的時候對應DB3;結果爲0的時候對應DB4,這樣一來就很是均勻的將數據分配到4個 DB中。 
優勢:數據分佈均勻 
缺點:數據遷移的時候麻煩,不能按照機器性能分攤數據 
(3)在認證庫中保存數據庫配置 
就是創建一個DB,這個DB單獨保存user_id到DB的映射關係,每次訪問數據庫的時候都要先查詢一次這個數據庫,以獲得具體的DB信息,而後才能進行咱們須要的查詢操做。 
優勢:靈活性強,一對一關係 
缺點:每次查詢以前都要多一次查詢,性能大打折扣 
以上就是一般的開發中咱們選擇的三種方式,有些複雜的項目中可能會混合使用這三種方式。經過上面的描述,咱們對分庫的規則也有了簡單的認識和了解。固然還會有更好更完善的分庫方式,還須要咱們不斷的探索和發現。 

第3章 本課題研究的基本輪廓 

上面的文字,咱們按照人類認知事物的規律,what?why?how這樣的方式闡述了數據庫切分的一些概念和意義以及對一些常規的切分規則作了概 要的介紹。本課題所討論的分佈數據層並不只僅如此,它是一個完整的數據層解決方案,它究竟是什麼樣的呢?接下來的文字,我將詳細闡述本研究課題的完整思想 和實現方式。 
分佈式數據方案提供功能以下: 
1)提供分庫規則和路由規則(RouteRule簡稱RR),將上面的說明中提到的三中切分規則直接內嵌入本系統,具體的嵌入方式在接下來的內容中進行詳細的說明和論述; 
(2)引入集羣(Group)的概念,保證數據的高可用性; 
(3)引入負載均衡策略(LoadBalancePolicy簡稱LB); 
(4)引入集羣節點可用性探測機制,對單點機器的可用性進行定時的偵測,以保證LB策略的正確實施,以確保系統的高度穩定性; 
(5)引入讀/寫分離,提升數據的查詢速度; 
僅僅是分庫分表的數據層設計也是不夠完善的,當某個節點上的DB服務器出現了宕機的狀況的時候,會是什麼樣的呢?是的,咱們採用了數據庫切分方 案,也就是說有N太機器組成了一個完整的DB,若是有一臺機器宕機的話,也僅僅是一個DB的N分之一的數據不能訪問而已,這是咱們能接受的,起碼比切分之 前的狀況好不少了,總不至於整個DB都不能訪問。通常的應用中,這樣的機器故障致使的數據沒法訪問是能夠接受的,假設咱們的系統是一個高併發的電子商務網 站呢?單節點機器宕機帶來的經濟損失是很是嚴重的。也就是說,如今咱們這樣的方案仍是存在問題的,容錯性能是經不起考驗的。固然了,問題老是有解決方案 的。咱們引入集羣的概念,在此我稱之爲Group,也就是每個分庫的節點咱們引入多臺機器,每臺機器保存的數據是同樣的,通常狀況下這多臺機器分攤負 載,當出現宕機狀況,負載均衡器將分配負載給這臺宕機的機器。這樣一來, 
就解決了容錯性的問題。因此咱們引入了集羣的概念,並將其內嵌入咱們的框架中,成爲框架的一部分。 sql

如上圖所示,整個數據層有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分配負載。這樣一來,問題就出來了,客戶端很天然的就會發生數據操做失敗的錯 誤或者異常。這樣是很是不友好的!怎樣解決這樣的問題呢? 

    咱們引入集羣節點的可用性探測機制,或者是可用性的數據推送機制。這兩種機制有什麼不一樣呢?首先說探測機制吧,顧名思義,探測即便,就是個人數據層客戶 端,不定時對集羣中各個數據庫進行可用性的嘗試,實現原理就是嘗試性連接,或者數據庫端口的嘗試性訪問,均可以作到,固然也能夠用JDBC嘗試性連接,利 用Java的Exception機制進行可用性的判斷,具體的會在後面的文字中提到。那數據推送機制又是什麼呢?其實這個就要放在現實的應用場景中來討論 這個問題了,通常狀況下應用的DB數據庫宕機的話我相信DBA確定是知道的,這個時候 DBA手動的將數據庫的當前狀態經過程序的方式推送到客戶端,也就是分佈式數據層的應用端,這個時候在更新一個本地的DB狀態的列表。並告知LB,這個數 據庫節點不能使用,請不要給它分配負載。一個是主動的監聽機制,一個是被動的被告知的機制。二者各有所長。可是均可以達到一樣的效果。這樣一來剛纔假設的 問題就不會發生了,即便就是發生了,那麼發生的機率也會降到最低。 

    上面的文字中提到的Master和Slave,咱們並無作太多深刻的講解。如圖一所示,一個Group由1個Master和N個Slave組成。爲何 這麼作呢?其中Master負責寫操做的負載,也就是說一切寫的操做都在Master上進行,而讀的操做則分攤到Slave上進行。這樣一來的能夠大大提 高讀取的效率。在通常的互聯網應用中,通過一些數據調查得出結論,讀/寫的比例大概在10:1左右,也就是說大量的數據操做是集中在讀的操做,這也就是爲 什麼咱們會有多個Slave的緣由。可是爲何要分離讀和寫呢?熟悉DB的研發人員都知道,寫操做涉及到鎖的問題,無論是行鎖仍是表鎖仍是塊鎖,都是比較 下降系統執行效率的事情。咱們這樣的分離是把寫操做集中在一個節點上,而讀操做其其餘的N個節點上進行,從另外一個方面有效的提升了讀的效率,保證了系統的 高可用性。讀寫分離也會引入新的問題,好比個人Master上的數據怎樣和集羣中其餘的Slave機器保持數據的同步和一致呢?這個是咱們不須要過多的關 注的問題,MySql的Proxy機制能夠幫助咱們作到這點,因爲Proxy機制與本課題相關性不是太強, 
在這裏不作詳細介紹。 

    綜上所述,本課題中所研究的分佈式數據層的大致功能就是如此。以上是對基本原理的一些討論和闡述。接下來就係統設計層面,進行深刻的剖析和研究。 


第4章 系統設計 

4.1系統實現層面的選擇 

在引言部分中提到,該系統的實現層面有兩種選擇,一種是基於JDBC層面上的選擇,一種是基於現有數據持久層框架層面上的選擇,好比 Hibernate,ibatis。兩種層面各有長處,也各有不足之處。基於JDBC層面上的系統實現,系統開發難度和後期的使用難度都將大大提升。大大 增長了系統的開發費用和維護費用。本課題的定位是在成型的ibatis持久層框架的基礎上進行上層的封裝,而不是對ibatis源碼的直接修改,這樣一來 使本系統不會對現有框架有太多的侵入性,從而也增長了使用的靈活性。之因此選擇ibatis,緣由以下: 
(1)ibatis的學習成本很是低,熟練的Java Programmer可在很是的短期內熟練使用ibatis; 
(2)ibatis是輕量級的ORM,只是簡單的完成了RO,OR的映射,其查詢語句也是經過配置文件sql-map.xml文件在原生sql的 層面進行簡單的配置,也就是說咱們沒有引入諸如Hibernate那樣的HQL的概念,從而加強了sql的可控性,優秀的DBA能夠很好的從sql的層面 對sql進行優化,使數據層的應用有很強的可控性。Hibernate雖然很強大,可是因爲Hibernate是OR的一個重型封裝,且引入HQL的概 念,不便於DBA團隊對sql語句的控制和性能的調優。 
基於以上兩點理由,本課題在ORM的產品的選擇上選擇了易學易用且輕量級的持久層框架ibatis。下面的討論也都是特定於ibatis的基礎上的討論。 

4.2其餘開源框架的選擇 

在一些大型的Java應用中,咱們一般會採用Spring這樣的開源框架,尤爲是IoC(DI)這部分,有效的幫助開發人員管理對象的依賴關係和 層次,下降系統各層次之間的實體耦合。Spring的優勢和用處我相信這是開發人員衆所周知的,在此再也不贅述。本課題的數據層也將採用Spring作爲 IoC(DI)的框架。 
4.3系統開發技術和工具介紹 
開發語言:Java JDK1.5 
集成開發環境:Eclipse 3.3.4 
Web環境下測試服務器:JBoss 4.2 
構建工具:淘寶自行研發的構建工具Antx(相似於Maven),固然也能夠用Maven 
依賴的開源Jar:Spring2.0,ibaits,commons-configuration(讀取配置文件),log4j,junit等數據庫

 

 轉自:http://wentao365.iteye.com/blog/1740691 服務器

相關文章
相關標籤/搜索