發(fā)表時間:2009-03-05

       今天看了“Database Sharding at Netlog, with MySQL and PHP”一文,和去年我們討論擴展的思路很類似(不過這種分布式擴展,計算,存儲的思路都很類似),但是這片文章的作者是在日益爆炸式增長的用戶數(shù)據(jù)下實踐的分享,因此這里將文中的一些思想記錄下來分享一下。

       Netlog擁有4000萬活躍用戶,每個月有超過5000萬的獨立用戶訪問網(wǎng)站,每個月有5億多的PV。數(shù)據(jù)量應(yīng)該算是比較大的。作者是Jurriaan Persyn,他從一個開發(fā)者角度而非DBA或者SA角度來談Netlog是如何通過數(shù)據(jù)切分來提高網(wǎng)站性能,橫向擴展數(shù)據(jù)層的。原文在:http://www.jurriaanpersyn.com/archives/2009/02/12/database-sharding-at-netlog-with-mysql-and-php/

 

       首先,還是先談到關(guān)于數(shù)據(jù)庫在數(shù)據(jù)日益龐大的情況下一個演變過程。

第一階段:讀寫同在一臺數(shù)據(jù)庫服務(wù)器

 

 

 

 

第二階段:讀寫分離(可以解決讀寫比例均衡或者讀居多的情況,但是帶入了數(shù)據(jù)復(fù)制同步的問題)

 

 

 

 

      

第三階段:部分?jǐn)?shù)據(jù)獨立部署結(jié)合讀寫分離。(部分?jǐn)?shù)據(jù)根據(jù)其業(yè)務(wù)獨立性情況,可以將所有的數(shù)據(jù)獨立存儲到數(shù)據(jù)庫服務(wù)器,分擔(dān)數(shù)據(jù)讀寫壓力,前提是要求數(shù)據(jù)具有較高的業(yè)務(wù)獨立性)

 

 

 

 

 

       第四階段:數(shù)據(jù)分拆結(jié)合讀寫分離(三階段的增強)

 

 

 

      

       第五階段:問題出現(xiàn),分拆也無法解決數(shù)據(jù)爆炸性增長,同時讀寫處于同等比例。

 

 

 

       解決問題兩種方式:DB Scale up DB Scale out。前者投入以及后期擴展有限,因此需要進行數(shù)據(jù)切分。

 

 

 

       上圖就是將photo的數(shù)據(jù)切分到了10臺數(shù)據(jù)庫服務(wù)器上。

 

       切分?jǐn)?shù)據(jù)的兩個關(guān)鍵點:

1.  如何根據(jù)存儲的數(shù)據(jù)內(nèi)容判斷數(shù)據(jù)的存儲歸屬,也就是什么是內(nèi)容的分區(qū)主鍵。

2.  采用什么算法可以根據(jù)不同的主鍵將內(nèi)容存儲到不同的分區(qū)中。

 

分區(qū)主鍵的選擇還是要根據(jù)自身的業(yè)務(wù)場景來決定,Netblog選擇的是用戶ID

采用什么方式將分區(qū)主鍵映射到對應(yīng)的分區(qū)可以通過以下四種方式:

1.  根據(jù)數(shù)據(jù)表來切分。(前提就是數(shù)據(jù)獨立性較強,和前面提到的三階段類似)

2.  基于內(nèi)容區(qū)間范圍的分區(qū)。(就好比前1000個用戶的信息存儲在A服務(wù)器,1000-2000存儲在B服務(wù)器)

3.  采用Hash算法結(jié)合虛擬節(jié)點的方式。(這類在memcached等等分布式場景中最常見,其實也是一個難點),缺點就是在于動態(tài)增加存儲節(jié)點會導(dǎo)致數(shù)據(jù)部分或者全部失效。

4.  目錄式的分區(qū)。最簡單也是最直接的方式,key和分區(qū)的對應(yīng)關(guān)系被保存,通過查找目錄可以得到分區(qū)信息。適合擴展,就是增加查詢損耗。

 

如何將數(shù)據(jù)分布的盡量均勻,如何平衡各個服務(wù)器之間的負載,如何在新增存儲機器和刪除存儲機器的時候不影響原有數(shù)據(jù),同時能夠?qū)?shù)據(jù)均攤,都是算法的關(guān)鍵。在分布式系統(tǒng)中DHTDistribute Hash Table)被很多人研究,并且有很多的論文是關(guān)于它的。

 

數(shù)據(jù)的橫向切分給應(yīng)用帶來的問題:

1.  跨區(qū)的數(shù)據(jù)查詢變得很困難。(對于復(fù)雜的關(guān)聯(lián)性數(shù)據(jù)查詢無法在一個請求中完成)

2.  數(shù)據(jù)一致性和引用完整性較難保證。(多物理存儲的情況下很難保證兼顧效率、可用性、一致性)

3.  數(shù)據(jù)分區(qū)之間的負載均衡問題。(數(shù)據(jù)本身的不均衡性,訪問和讀寫的不均衡性都會給數(shù)據(jù)分區(qū)的負載均衡帶來困難)

4.  網(wǎng)絡(luò)配置的復(fù)雜性。(需要保證服務(wù)器之間的大數(shù)據(jù)量頻繁的交互和同步)

5.  數(shù)據(jù)備份策略將會變得十分復(fù)雜。

解決這些問題當(dāng)前已經(jīng)有的一些開源項目:

1.  MySql Cluster,解決讀寫分離問題已經(jīng)十分成熟。

2.  MySql Partitioning,可以將一個大表拆分為很多小表,提高訪問速度,但是限制與這些小表必須在同一臺服務(wù)器上。

3.  HSCALESpock Proxy都是建立與MySql Proxy基礎(chǔ)上的開源項目,MySql Proxy采用LUA腳本來進行數(shù)據(jù)分區(qū)。

4.  HiveDBMySql分區(qū)框架的java實現(xiàn)。

5.  另外還有HyperTable,HBase,BigTable等等。

 

Netblog幾個需求:

1.              需要靈活的可擴展性。對于存儲增加減少需要能夠動態(tài)的及時實施,因為數(shù)據(jù)量增長很快,如果策略會導(dǎo)致數(shù)據(jù)失效或者部署需要重新啟動,則就不能滿足需求。

2.              不想引入全新的數(shù)據(jù)層和與原有系統(tǒng)不匹配的抽象層,因為并不是所有數(shù)據(jù)都需要切分,僅僅在需要的情況下通過API的方式來透明切分?jǐn)?shù)據(jù)。

3.              分區(qū)的主鍵需要可配置。

4.              需要封裝API,對開發(fā)人員透明數(shù)據(jù)切分的工作。

 

      Netblog Sharding的實現(xiàn)

 

 

 

上圖就是NetblogSharding的結(jié)構(gòu)圖,主要分成了三部分:Shard,Sharddb,Sharddbhost。Shard就是一個表,里面存放了部分用戶數(shù)據(jù)。Sharddb是一個表的組合就像一個虛擬的DB。Sharddbhost是具體的存儲分區(qū)。Shard,Sharddb可以根據(jù)負載的情況被移動到不同的host中去。

       對于Shard的管理,Netblog采用的是目錄查詢的管理方式。目錄信息也存儲在MySql中,同時會通過互備,Memcache,集群來確保安全性和高效性。

       Shard Table API采用了多一層的映射模式來適合各種不同屬性的查詢情況。數(shù)據(jù)和記錄在數(shù)據(jù)庫中存儲除了UserID以外還有對應(yīng)的ItemID,ItemID的作用就是定義了具體獲取數(shù)據(jù)的字段信息,例如關(guān)聯(lián)照片表時,ItemID就是PhotoId,關(guān)聯(lián)視頻表時,ItemID就是videoID。

       一個獲取用戶id26博客信息的范例:

1Where is user 26?

   User 26 is on shard 5.

2On shard 5; Give me all the $blogIDs ($itemIDs) of user 26.

That user's $blogIDs are: array(10,12,30);

3On shard 5; Give me all details about the items array(10,12,30) of user 26.

Those items are: array(array('title' => "foo", 'message' => "bar"), array('title' => "milk", 'message' => "cow"));

 

對于Shard的管理Netblog采取的措施主要有這些:

1.  服務(wù)器之間的負載均衡根據(jù)用戶數(shù),數(shù)據(jù)庫文件大小,讀寫次數(shù),cpu load等等作為參數(shù)來監(jiān)控和維護。根據(jù)最后的結(jié)果來遷移數(shù)據(jù)和分流數(shù)據(jù)。

2.  移動數(shù)據(jù)時會監(jiān)控用戶是否在操作數(shù)據(jù),防止不一致性。

3.  對于數(shù)據(jù)庫的可用性,采用集群,master-master,master-slave復(fù)制等手段。

 

最后通過三種技術(shù)來解決三個問題:

 

1.  Memcached解決shard多次查詢的效率問題。

根據(jù)上面的范例可以看到,一次查詢現(xiàn)在被分割成為了三部分:shard查詢,item查詢,最終結(jié)果查詢。通過memcached可以緩存三部分內(nèi)容,由前到后數(shù)據(jù)的穩(wěn)定性以及命中率逐漸降低,同時通過結(jié)合有效期(內(nèi)容存儲時效)和修改更新機制(add,update,delete觸發(fā)緩存更新),可以極大地解決效率問題。甚至通過緩存足夠信息減少大量的數(shù)據(jù)庫交互。

 

2.  并行計算處理。

由于數(shù)據(jù)的分拆,有時候需要得到對于多Shard數(shù)據(jù)處理的結(jié)果匯總,因此就會將一個請求分拆為多個請求,分別交由多個服務(wù)器處理,最后將結(jié)果匯總。(類似于Map-reduce

 

3.  采用Sphinx全文搜索引擎解決多數(shù)據(jù)分區(qū)數(shù)據(jù)匯總查詢,例如察看網(wǎng)站用戶的最新更新情況或者最熱門日至。這個采用單獨系統(tǒng)部署,通過建立全局信息索引,來查詢數(shù)據(jù)情況。

 

以上是技術(shù)上的全部內(nèi)容,作者在最后的幾個觀點十分值得學(xué)習(xí),同時也不僅僅限于數(shù)據(jù)切分,任何框架設(shè)計都可以參考。

 

“Don't do it, if you don't need to!" (37signals.com)

"Shard early and often!" (startuplessonslearned.blogspot.com)

 

看起來矛盾的兩句話,卻是說出了對于數(shù)據(jù)切分的一些考慮。

首先在沒有必要的情況下就不要考慮數(shù)據(jù)切分,切分帶來的復(fù)雜性直接影響可用性,可維護性和一致性。在能夠采用Scale up的情況下,可以選擇Scale up降低框架復(fù)雜度。

另一方面,如果發(fā)現(xiàn)了業(yè)務(wù)增長情況出現(xiàn)必須要擴展的趨勢,那么就要盡早著手去實施和規(guī)劃擴展的工作,并且在切分和擴展過程中要不斷地去優(yōu)化和重構(gòu)。

 

后話:

       其實任何架構(gòu)設(shè)計首要就是簡單直接,不過度設(shè)計,不濫竽充數(shù)。其實就是要平衡好:可用性、高效性、一致性、可擴展性這四者之間的關(guān)系。良性循環(huán)、應(yīng)時應(yīng)事作出取舍和折中。用的好要比學(xué)會用更重要,更關(guān)鍵。