Flickr.com 是網上最受歡迎的照片共享網站之一,還記得那位給Windows Vista拍攝壁紙的Hamad Darwish嗎?他就是將照片上傳到Flickr,後而被微軟看中成爲Vista壁紙御用攝影師。php
Flickr.com 是最初由位於溫哥華的Ludicorp公司開發設計並於2004年2月正式發佈的,因爲大量應用了WEB 2.0技術,注重用戶體驗,使得其迅速得到了大量的用戶,2007年11月,Flickr迎來了第20億張照片,一年後,這個數字就達到了30億,而且還在以加速度增加。2005年3月,雅虎公司以3千500萬美圓收購了Ludicorp公司和Flickr.com。雖然Flickr並非最大的照片共享網站(Facebook以超過100億張照片排名第一),但這筆收購仍然被認爲是WEB 2.0浪潮中最精明的收購,由於僅僅一年後,Google就以16億美圓的高價收購了YouTube,而2007年10月,微軟斥資2.4億美圓收購Facebook 1.6%股份,此舉使Facebook估值高達150億美圓。估計Ludicorp公司的創始人Stewart Butterfield和Caterina Fake夫婦如今還在後悔吧。html
在2005年溫哥華PHP協會的簡報以及隨後的一系列會議上,Flickr的架構師Cal Henderson公開了大部分Flickr所使用的後臺技術,使得咱們能有機會來分享和研究其在構建可擴展Web站點的經驗。本文大部分資料來自互聯網和本身的一點點心得,歡迎你們參與討論,要是可以起到拋磚引玉的做用,本人將不勝榮幸。mysql
Flickr總體框架圖
web
在討論Flickr 網站架構以前,讓咱們先來看一組統計數據(數據來源:April 2007 MySQL Conf and Expo和Flickr網站)算法
數據庫最初的擴展-Replicationsql
也許有人不相信,不過Flickr確實是從一臺服務器起步的,即Apache/PHP和MySQL是運行在同一臺服務器上的,很快MySQL服務器就獨立 了出來,成了雙服務器架構。隨着用戶和訪問量的快速增加,MySQL數據庫開始承受愈來愈大的壓力,成爲應用瓶頸,致使網站應用響應速度變慢,MySQL 的擴展問題就擺在了Flickr的技術團隊面前。
不幸的是,在當時,他們的選擇並很少。通常來講,數據庫的擴展無外是兩條路,Scale-Up和Scale-Out,所謂Scale-Up,簡單的說就是 在同一臺機器內增長CPU,內存等硬件來增長數據庫系統的處理能力,通常不須要修改應用程序;而Scale-Out,就是咱們一般所說的數據庫集羣方式, 即經過增長運行數據庫服務器的數量來提升系統總體的能力,而應用程序則通常須要進行相應的修改。在常見的商業數據庫中,Oracle具備很強的 Scale-Up的能力,很早就可以支持幾十個甚至數百個CPU,運行大型關鍵業務應用;而微軟的SQL SERVER,早期受Wintel架構所限,以Scale-Out著稱,但自從幾年前突破了Wintel體系架構8路CPU的的限制,Scale-Up的 能力一路日新月異,最近更是發佈了SQL 2008在Windows 2008 R2版運行256個CPU核心(core)的測試結果,開始挑戰Oracle的高端市場。而MySQL,直到今年4月,在最終採納了GOOGLE公司貢獻 的SMP性能加強的代碼後,發佈了MySQL5.4後,纔開始支持16路CPU的X86系統和64路CPU的CMT系統(基於Sun UltraSPARC 的系統)。
從另外一方面來講,Scale-Up受軟硬件體系的限制,不可能無限增長CPU和內存,相反Scale-Out倒是能夠"幾乎"無限的擴展,以Google 爲例,2006年Google一共有超過45萬臺服務器(誰能告訴我如今他們有多少?!);並且大型SMP服務器的價格遠遠超過普通的雙路服務器,對於很 多剛剛起步或是業務增加很難預測的網站來講,不可能也不必一次性投資購買大型的硬件設備,於是雖然Scale-Out會隨着服務器數量的增多而帶來管 理,部署和維護的成本急劇上升,但確是大多數大型網站固然也包括Flickr的惟一選擇。
通過統計,Flickr的技術人員發現,查詢即SELECT語句的數量要遠遠大於添加,更新和
刪除的數量,比例達到了大約13:1甚至更多,因此他們採用了「Master-Slave」的複製模式,即全部的「寫」操做都在發生在「Master", 而後」異步「複製到一臺或多臺「Slave"上,而全部的」讀「操做都轉到」Slave"上運行,這樣隨着「讀」交易量的增長,只需增長Slave服務器 就能夠了。
讓咱們來看一下應用系統應該如何修改來適應這樣的架構,除了」讀/寫「分離外,對於」讀「操做最基本的要求是:1)應用程序可以在多個」Slave「上進 行負載均分;2)當一個或多個」slave"出現故障時,應用程序能自動嘗試下一個「slave」,若是所有「Slave"失效,則返回錯誤。 Flickr曾經考慮過的方案是在Web應用和」Slave「羣之間加入一個硬件或軟件的」Load Balancer「,以下圖
這樣的好處是應用所需的改動最小,由於對於應用來講,全部的讀操做都是經過一個虛擬的Slave來進行,添加和刪除「Slave"服務器對應用透 明,Load Balancer 實現對各個Slave服務器狀態的監控並將出現故障的Slave從可用節點列表裏刪除,並能夠實現一些複雜的負載分擔策略,好比新買的服務器處理能力要高 過Slave羣中其餘的老機器,那麼咱們能夠給這個機器多分配一些負載以最有效的利用資源。一個簡單的利用Apache proxy_balancer_module的例子以下:
數據庫
1
2
3
4
5
6
7
8
9
10
11
|
<strong>。。。。。。。。。。。。。。
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_http_module modules/mod_proxy_http.so
。。。。。。。。。。。。。。。。。。。。
<Proxy balancer://mycluster>
BalancerMember "http://slave1:8008/App" loadfactor=4
BalancerMember "http://slave2:8008/App" loadfactor=3
BalancerMember "http://slave3:8008/App" loadfactor=3
....................
///slave load ratio 4:3:3. </strong>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function
db_connect(
$hosts
,
$user
,
$pass
){
shuffle(
$hosts
);
//shuffle()是PHP函數,做用是將數組中每一個元素的順序隨機打亂。
foreach
(
$hosts
as
$host
){
debug(
"Trying to connect to $host..."
);
$dbh
= @mysql_connect(
$host
,
$user
,
$pass
, 1);
if
(
$dbh
){
debug(
"Connected to $host!"
);
return
$dbh
;
}
debug(
"Failed to connect to $host!"
);
}
debug(
"Failed to connect to all hosts in list - giving up!"
);
return
0;
}
|
Flickr網站的架構,須要一次大的變化來解決長期持續擴展的問題。swift
Shard - 大型網站數據庫擴展的終極武器?數組
2005年7月,另外一位大牛(MySQL 200五、2006年度 "Application of the Year Award"得到者)Dathan Pattishall加入了Flickr團隊。一個星期以內,Dathan解決了Flickr數據庫40%的問題,更重要的是,他爲Flickr引進了 Shard架構,從而使Flickr網站具有了真正「線性」Scale-Out的增加能力,並一直沿用至今,取得了巨大的成功。
Shard主要是爲了解決傳統數據庫Master/Slave模式下單一Master數據庫的「寫」瓶頸而出現的,簡單的說Shard就是將一個大表分 割成多個小表,每一個小表存儲在不一樣機器的數據庫上,從而將負載分散到多個機器並行處理而極大的提升整個系統的「寫」擴展能力。相比傳統方式,因爲每一個數據 庫都相對較小,不只讀寫操做更快,甚至能夠將整個小數據庫緩存到內存中,並且每一個小數據庫的備份,恢復也變得相對容易,同時因爲分散了風險,單個小數據庫 的故障不會影響其餘的數據庫,使整個系統的可靠性也獲得了顯著的提升。
對於大多數網站來講,以用戶爲單位進行Shard分割是最合適不過的,常見的分割方法有按地域(好比郵編),按Key值(好比Hash用戶ID),這些 方法能夠簡單的經過應用配置文件或算法來實現,通常不須要另外的數據庫,缺點是一旦業務增長,須要再次分割Shard時要修改現有的應用算法和從新計算所 有的Shard KEY值;而最爲靈活的作法是以「目錄」服務爲基礎的分割,即在Shard以前加一箇中央數據庫(Global Lookup Cluster),應用要先根據用戶主鍵值查詢中央數據庫,得到用戶數據所在的Shard,隨後的操做再轉向Shard所在數據庫,例以下圖:緩存
而應用的主要修改在於要添加一個Lookup訪問層,例如將如下的代碼:
1
2
3
|
string
connectionString =
@"Driver={MySQL};SERVER=dbserver;DATABASE=CustomerDB;"
;
OdbcConnection conn =
new
OdbcConnection(connectionString);
conn.Open();
|
1
2
3
|
string
connectionString = GetDatabaseFor(customerId);
OdbcConnection conn =
new
OdbcConnection(connectionString);
conn.Open();
|
Flickr Shard的設計咱們在Flickr 網站架構研究(1)中已經總結過了,在此再也不贅述。咱們在此談一下Shard架構的主要問題和Flickr的解決辦法:1)Shard只適用於不須要 join操做的表,由於跨Shard join操做的開銷太大,解決的辦法是將一個用戶的全部數據所有存放在同一個Shard裏,對於一些傳統方式下須要 跨Shard查詢的數據,只能採起冗餘的方法,好比Shard1的用戶A對Shard2的用戶B的照片進行了評論,那麼這條評論將同時存放在Shard1 和Shard2中。這樣就存在一個數據一致性的問題,常規的作法是用數據庫事務(Transaction)、」兩階段提交「(2 phase commit)來解決,但作過兩階段提交(2PC)應用的都知道,2PC的效率相對較差,並且實際上也不能100%保證數據的完整性和一致性;另外,一旦 因爲其中一個Shard故障而提交失敗回滾,用戶只能放棄或再試一遍,用戶體驗較差。Flickr對於數據一致性的解決方案是Queue(Flickr用 PHP開發了一個強大的Queue系統,將全部能夠異步的任務都用Queue來實現,天天處理高達1千萬以上的任務。),事實上當用戶A對用戶B的照片進 行評論時,他並不關心這條評論何時出如今用戶B的界面上,即將這條評論添加到用戶B的交易是能夠異步的,容許必定的遲延,經過Queue處理,既保證 了數據的一致性,又縮短了用戶端的相應時間,提升了系統性能。2)Shard的另外一個主要問題Rebalancing,既當現有Shard的負載達到必定 的閥值,如何將現有數據再次分割,Flickr目前的方式依然是手工的,既人工來肯定哪些用戶須要遷移,而後運行一個後臺程序進行數據遷移,遷移的過程用 戶帳戶將被鎖住。(聽說Google作到了徹底自動的Rebalancing,本着」薩大「坑裏再也不挖坑的原則,若是有機會的話,留到下一個系列再研究 吧)
Memcached的應用和爭論
你們應該已經注意到,Flickr爲中央數據庫配置了Memcached做爲數據庫緩存,接下來的問題是,爲何用Memcached?爲何 Shard不須要Memcached?Memcached和Master,Slave的關係怎樣?筆者將試圖回答這些問題供你們參考,網上的相關爭論很 多,有些問題還沒有有定論。
Memecached是一個高性能的,分佈式的,開源的內存對象緩存系統,顧名思義,它的主要目的是將常常讀取的對象放入內存以提升整個系統,尤爲是數 據庫的擴展能力。Memcached的主要結構是兩個Hash Table,Server端的HashTable以key-value pair的方式存放對象值,而Client端的HashTable的則決定某一對象存放在哪個Memcached Server.舉個例子說,後臺有3個Memecached Server,A、B、C,Client1須要將一個對象名爲」userid123456「,值爲「魯丁"的存入,通過Client1的Hash計 算,"userid123456"的值應該放入Memcached ServerB, 而這以後,Client2須要讀取"userid123456"的值,通過一樣的Hash計算,得出"userid123456"的值若是存在的話應該在 Memcached ServerB,並從中取出。最妙的是Server之間彼此是徹底獨立的,徹底不知道對方的存在,沒有一個相似與Master或Admin Server的存在,增長和減小Server只需在Client端"註冊"並從新Hash就能夠了。
Memcached做爲數據庫緩存的做用主要在於減輕甚至消除高負載數據庫狀況下頻繁讀取所帶來的Disk I/O瓶頸,相對於數據庫自身的緩存來講,具備如下優勢:1)Memecached的緩存是分佈式的,而數據庫的緩存只限於本機;2)Memcached 緩存的是對象,能夠是通過複雜運算和查詢的最終結果,而且不限於數據,能夠是任何小於1MB的對象,好比html文件等;而數據庫緩存是以"row"爲單 位的,一旦"row"中的任何數據更新,整個「row"將進行多是對應用來講沒必要要的更新;3)Memcached的存取是輕量的,而數據庫的則相對較 重,在低負載的狀況下,一對一的比較,Memcached的性能未必能超過數據庫,而在高負載的狀況下則優點明顯。
Memcached並不適用於更新頻繁的數據,由於頻繁更新的數據致使大量的Memcached更新和較低的緩衝命中率,這可能也是爲何Shard沒 有集成它的緣由;Memcached更多的是擴展了數據庫的」讀「操做,這一點上它和Slave的做用有重疊,以致於有人爭論說應該 讓"Relication"回到它最初的目的」Online Backup"數據庫上,而經過Memcached來提供數據庫的「讀」擴展。(固然也有人說,考慮到Memcached的對應用帶來的複雜性,仍是慎 用。)
然而,在體系架構中增長Memecached並非沒有代價的,現有的應用要作適當的修改來同步Memcached和數據庫中的數據,同時Memcached不提供任何冗餘和「failover」功能,這些複雜的控制都須要應用來實現。基本的應用邏輯以下:
對於讀操做:
1
2
3
4
5
|
$data
= memcached_fetch(
$id
);
return
$data
if
$data
$data
= db_fetch(
$id
);
memcached_store(
$id
,
$data
);
return
$data
;
|
1
2
|
db_store(
$id
,
$data
);
memcached_store(
$id
,
$data
);
|
複製滯後和同步問題的解決
咱們知道複製滯後的主要緣由是數據庫負載過大而形成異步複製的延遲,Shard架構有效的分散了系統負載,從而大大減輕了這一現象,可是並不能從根本上消除,解決這一問題仍是要靠良好的應用設計。
當用戶訪問並更新Shard數據時,Flickr採用了將用戶「粘」到某一機器的作法,
$id = intval(substr($user_id, -10));
$id % $count_of_hosts_in_shard
即同一用戶每次登陸的全部操做其實都是在Shard中的一個Master上運行的,這樣即便複製到Slave,也就是另外一臺Master的時候有延時,也不會對用戶有影響,除非是用戶剛剛更新,還沒有複製而這臺Master就出現故障了,不過這種概率應該很小吧。
對於Central Database的複製滯後和同步問題,Flickr採用了一種複雜的「Write Through Cache"的機制來處理:
"Write Through Cache"就是將全部的數據庫」寫「操做都先寫入」Cache",而後由Cache統一去更新數據庫的各個Node,「Write Through Cache"維護每個Node的更新狀態,當有讀請求時,即將請求轉向狀態爲」已同步「的Node,這樣即避免了複製滯後和Memcached的同步問 題,但缺點是其實現極爲複雜,「Write Throug Cache"層的代碼須要考慮和實現全部」journal","Transaction「,「failover」,和「recovery"這些數據庫已經 實現的功能,另外還要考慮自身的"failover"問題。我沒有找到有關具體實現的說明,只能猜想這一部分的處理可能也是直接利用或是實現了相似於 Flickr的Queue系統吧。
原文地址:http://www.itivy.com/ivy/archive/2011/3/7/634351294385186067.html