一、引言
隨着互聯網應用的廣泛普及,海量數據的存儲和訪問成為了系統設計的瓶頸問題。對於一個大型的互聯網應用,每天幾十億的PV無疑對數據庫造成了相當高的負載。對於系統的穩定性和擴展性造成了極大的問題。通過數據切分來提高網站性能,橫向擴展數據層已經成為架構研發人員首選的方式。水平切分數據庫,可以降低單台機器的負載,同時最大限度的降低了了宕機造成的損失。通過負載均衡策略,有效的降低了單台機器的訪問負載,降低了宕機的可能性;通過集群方案,解決了數據庫宕機帶來的單點數據庫不能訪問的問題;通過讀寫分離策略更是最大限度了提高了應用中讀取(Read)數據的速度和並發量。目前國內的大型互聯網應用中,大量的采用了這樣的數據切分方案,Taobao,Alibaba,Tencent,它們大都實現了自己的分布式數據訪問層(DDAL)。以實現方式和實現的層次來划分,大概分為兩個層次(Java應用為例):JDBC層的封裝,ORM框架層的實現。下面兄弟連教育來解析基本原理和概念
二、基本原理和概念
2.1基本原理:
人類認知問題的過程總是這樣的:what(什么)-?why(為什么)-?how(怎么做),接下來,本文將就這三個問題展開討論和研究:
2.1.1什么是數據切分
"Shard"這個詞英文的意思是"碎片",而作為數據庫相關的技術用語,似乎最早見於大型多人在線角色扮演游戲中。"Sharding"姑且稱之為"分片"。Sharding不是一門新技術,而是一個相對簡朴的軟件理念。眾所周知,MySQL5之后才有了數據表分區功能,那么在此之前,很多MySQL的潛在用戶都對MySQL的擴展性有所顧慮,而是否具備分區功能就成了衡量一個數據庫可擴展性與否的一個關鍵指標(當然不是唯一指標)。數據庫擴展性是一個永恆的話題,MySQL的推廣者經常會被問到:如在單一數據庫上處理應用數據捉襟見肘而需要進行分區化之類的處理,是如何辦到的呢?答案是:Sharding。Sharding不是一個某個特定數據庫軟件附屬的功能,而是在具體技術細節之上的抽象處理,是水平擴展(ScaleOut,亦或橫向擴展、向外擴展)的解決方案,其主要目的是為突破單節點數據庫服務器的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的設計。