MySQL具體解釋(15)-----------海量數據解說
第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,亦或橫向擴展、向外擴展)的解決方式,其主要目的是為突破單節點數據庫server的 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能夠工作呢?道理非常easy,由於它能夠非常好的擴展。我們知道每臺機器不管配置多麽好它都有自身的物理上限,所以當我們應用已經能觸及或遠遠超出單臺機器的某個上限的時候。我們惟有尋找別的機器的幫助或者繼續升級的我們的硬件。但常見的方案還是橫向擴展, 通過加入很多其它的機器來共同承擔壓力。我們還得考慮當我們的業務邏輯不斷增長,我們的機器能不能通過線性增長就能滿足需求?Sharding能夠輕松的將計算,存儲。I/O並行分發到多臺機器上。這樣能夠充分利用多臺機器各種處理能力。同一時候能夠避免單點失敗,提供系統的可用性。進行非常好的錯誤隔離。
綜合以上因素。數據切分是非常有必要的,且我們在此討論的數據切分也是將MySql作為背景的。基於成本的考慮。非常多公司也選擇了Free且Open的MySql。
對MySql有所了解的開發者可能會知道,MySQL 5 之後才有了數據表分區功能,那麽在此之前,非常多 MySQL 的潛在用戶都對 MySQL 的擴展性有所顧慮。而是否具備分區功能就成了衡量一個數據庫可擴展性與否的一個關鍵指標(當然不是唯一指標)。數據庫擴展性是一個永恒的話題,MySQL 的推廣者常常會被問到:如在單一數據庫上處理應用數據捉襟見肘而須要進行分區化之類的處理,是怎樣辦到的呢? 答案也是Sharding,也就是我們所說的數據切分方案。
我們用免費的MySQL和便宜的Server甚至是PC做集群。達到小型機+大型商業DB的效果,降低大量的資金投入,降低運營成本。何樂而不為呢?所以。我們選擇Sharding,擁抱Sharding。
2.1.3怎麽做到數據切分
說到數據切分,再次我們講對數據切分的方法和形式進行比較具體的闡述和說明。
數據切分能夠是物理 上的。對數據通過一系列的切分規則將數據分布到不同的DBserver上。通過路由規則路由訪問特定的數據庫。這樣一來每次訪問面對的就不是單臺server了。而是N臺server。這樣就能夠減少單臺機器的負載壓力。
數 據切分也能夠是數據庫內的 ,對數據通過一系列的切分規則。將數據分布到一個數據庫的不同表中,比方將article分為article_001,article_002等子表,若幹個子表水平拼合有組成了邏輯上一個完整的article表。這樣做的目的事實上也是非常easy的。 舉個樣例說明,比方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)引入讀/寫分離,提高數據的查詢速度。
不過分庫分表的數據層設計也是不夠完好的。當某個節點上的DBserver出現了宕機的情況的時候,會是什麽樣的呢?是的,我們採用了數據庫切分方案,也就是說有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並非某個特定的物理server。
接下來須要做的工作就是找到詳細的物理的DBserver,以進行詳細的數據操作。
基於這個環節的需求。我們引入了負載均衡器的概念(LB)。負載均衡器的職責就是定位到一臺詳細的DBserver。詳細的規則例如以下:負載均衡器會分析當前sql的讀寫特性,假設是寫操作或者是要求實時性非常強的操作的話,直接將查詢負載分到Master,假設是讀操作則通過負載均衡策略分配一個Slave。我們的負載均衡器的主要研究放向也就是負載分發策略,通常情況下負載均衡包含隨機負載均衡和加權負載均衡 。 隨機負載均衡非常好理解,就是從N個Slave中隨機選取一個Slave。這種隨機負載均衡是不考慮機器性能的,它默覺得每臺機器的性能是一樣的。
假如真實的情況是這種,這樣做也是無可厚非的。假如實際情況並不是如此呢?每一個Slave的機器物理性能和配置不一樣的情況,再使用隨機的不考慮性能的負載均衡,是非常不科學的,這樣一來會給機器性能差的機器帶來不必要的高負載,甚至帶來宕機的危急。 同一時候高性能的數據庫server也不能充分發揮其物理性能。基於此考慮從。我們引入了加權負載均衡,也就是在我們的系統內部通過一定的接口,能夠給每臺DBserver分配一個權值,然後再執行時LB依據權值在集群中的比重,分配一定比例的負載給該DBserver。當然這種概念的引入,無疑增大了系統的復雜性和可維護性。有得必有失。我們也沒有辦法逃過的。
有了分庫,有了集群。有了負載均衡器,是不是就萬事大吉了呢? 事情遠沒有我們想象的那麽簡單。盡管有了這些東西。基本上能保證我們的數據層能夠承受非常大的壓力 。可是這種設計並不能全然規避數據庫宕機的危害。假如Group1中的slave2 宕機了,那麽系統的LB並不能得知,這種話事實上是非常危急的,由於LB不知道,它還會以為slave2為可用狀態,所以還是會給slave2分配負載。這樣一來。問題就出來了。client非常自然的就會發生數據操作失敗的錯誤或者異常。這樣是非常不友好的!如何解決這種問題呢? 我們引入集群節點的可用性探測機制 ,或者是可用性的數據推送機制 。
這兩種機制有什麽不同呢?首先說探測機制吧,顧名思義,探測即使,就是我的數據層client。不定時對集群中各個數據庫進行可用性的嘗試,實現原理就是嘗試性鏈接。或者數據庫port的嘗試性訪問,都能夠做到,當然也能夠用JDBC嘗試性鏈接,利用Java的Exception機制進行可用性的推斷,詳細的會在後面的文字中提到。那數據推送機制又是什麽呢?事實上這個就要放在現實的應用場景中來討論這個問題了,普通情況下應用的DB 數據庫宕機的話我相信DBA肯定是知道的,這個時候DBA手動的將數據庫的當前狀態通過程序的方式推送到client,也就是分布式數據層的應用端,這個時候在更新一個本地的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環境下測試server:JBoss 4.2
構建工具:淘寶自行研發的構建工具Antx(類似於Maven),當然也能夠用Maven
依賴的開源Jar:Spring2.0,ibaits,commons-configuration(讀取配置文件),log4j,junit等
MySQL具體解釋(15)-----------海量數據解說