Flickr 網站架構分析

Flickr 網站架構分析

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網站)算法

  1. 天天多達40億次的查詢請求
  2. squid總計約有3500萬張照片(硬盤+內存)
  3. squid內存中約有200萬張照片
  4. 總計有大約4億7000萬張照片,每張圖片又生成不一樣尺寸大小的4-5份圖片
  5. 每秒38,000次Memcached請求 (Memcached總共存儲了1200萬對象)
  6. 超過2 PB 存儲,其中數據庫12TB
  7. 天天新增圖片超過 40萬(週日峯值超過200萬,約1.5TB)
  8. 超過8百50萬註冊用戶
  9. 超過1千萬的惟一標籤(tags)
  10. 響應4萬個照片訪問請求
  11. 處理10萬個緩存操做
  12. 運行13萬個數據庫查詢
這張是Flickr的網站架構圖,咱們這裏只做一些簡要的描述,具體的分析請靜待後續文章。

點看全圖

 

  1. Pair of ServerIron's作負載均衡方案
  2. Squid Caches 代理,用於緩存靜態的HTML和照片
  3. Net App公司的Filer, NAS存儲設備,用於存儲照片
  4. PHP App Servers
    - 運行REDHAT LINUX,Apache上的PHP應用,Flickr網站的主體是大約6萬行PHP代碼
    - 沒有使用PHP session, 應用是stateless,便於擴展,並避免PHP Server故障所帶來的Session失效。
    - 每一個頁面有大約27~35個查詢(不知道爲何這麼設計,我的以爲沒有必要)
    - 另有專門的Apache Web Farm 服務於靜態文件(HTML和照片)的訪問
  5. Storage Manager 運行私有的,適用於海量文件存儲的Flickr File System
  6. Dual Tree Central Database
    - MySQL 數據庫,存放用戶表,記錄的信息是用戶主鍵以及此用戶對以的數據庫Shard區,從中心用戶表中查出用戶數據所在位置,而後直接從目標Shard中取出數據。
    - 「Dual Tree"架構是」Master-Master"和「Master-Slave"的有效結合,雙Master 避免了「單點故障」,Master-Slave又提升了讀取速度,由於用戶表的操做90%以上是讀。
  7. Master-master shards
    - MySQL 數據庫,存儲實際的用戶數據和照片的元數據(Meta Data),每一個Shard 大約40萬個用戶,120GB 數據。每一個用戶的全部數據存放在同一個shard中。
    - Shard中的每個server的負載只是其可最大負載的50%,這樣在須要的時候能夠Online停掉一半的server進行升級或維護而不影響系統性能。
    - 爲了不跨Shard查詢所帶來的性能影響,一些數據有在不一樣的Shard有兩份拷貝,好比用戶對照片的評論,經過事務來保證其同步。
  8. Memcached Cluster 中間層緩存服務器,用於緩存數據庫的SQL查詢結果等。
  9. Big Search Engine  
    - 複製部分Shard數據(Owner’s single tag)到Search Engine Farm以響應實時的全文檢索。
    - 其餘全文檢索請求利用Yahoo的搜索引擎處理(Flickr是Yahoo旗下的公司F
  10. 服務器的硬件配置:
    - Intel或AMD 64位CPU,16GB RAM
    - 6-disk 15K RPM RAID-10.
    - 2U boxes.
  11. 服務器數量:(數據來源:April 2008 MySQL Conference & Expo)
    166 DB servers, 244 web servers(不知道是否包括 squid server?), 14 Memcached servers

數據庫最初的擴展-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
。。。。。。。。。。。。。。。。。。。。
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>
最終,Flickr採用了一種很是「輕量」但有效的「簡易」PHP實現,基本的代碼只有10幾行:
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;
}
在上述代碼中,若是須要對特定的Slave賦予更高的負載,只要在$hosts中多出現一次或屢次就能夠了。這段代碼只要稍稍改進,就能夠實現更復雜的功能,如當connect失敗時自動將host從hosts列表中去除等。
「Master」-"Slave"模式的缺點是它並無對於「寫'操做提供擴展能力,並且存在單點故障,即一旦Master故障,整個網站將喪失「更新」 的能力。解決的辦法採用「Master"-"Master"模式,即兩臺服務器互爲」Master「-"Slave",這樣不只」讀/寫「能力擴展了一 倍,並且有效避免了」單點故障「,結合已有的「Master"-"Slave",整個數據庫的架構就變成了下面的」雙樹「結構,
點看全圖

「雙樹」架構並無支撐過久的時間,大概6個月後,隨着用戶的激增,系統再一次達到了極限,不只」寫」操做成爲了瓶頸,並且「異步複製"也因爲 」Slave「服務器過於繁忙而出現了嚴重的滯後而形成讀數據的不一致。那麼,能不能在現有架構加以解決,好比說增長新的」Master「服務器和考慮採 用」同步複製「呢?答案是否認的,在Master超過兩臺的設置中,只能採用」閉環鏈「的方式進行復制,在大數據量的生產環境中,很容易形成在任意時刻沒 有一個Master或Slave節點是具備所有最新數據的(有點相似於」人一次也不能踏進同一條河「?),這樣很難保障數據的一致性,並且一旦其中一個Master出現故障,將中斷整個複製鏈;而對於」同步複製「,固然這是消除」複製滯後「的最好辦法,不過在當時MySQL的同步複製還遠沒有成熟到能夠運用在投產環境中。

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();
GetDatabaseFor()函數完成根據用戶ID獲取Shard connectionString的做用。
對應咱們前面所提到過的Flickr架構
點看全圖

Dual Tree Central Database就是中央數據庫,存放用戶表,記錄的信息是用戶主鍵以及此用戶對以的數據庫Shard區;而Master-Master Shards就是一個個的Shard用戶數據庫,存儲實際的用戶數據和照片的元數據(Meta Data)。

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 );
咱們看到在每一次數據更新都須要更新Memcached,並且數據庫或Memcached任何一點寫錯誤應用就可能取得「過時」的數據而獲得錯誤的結果,如何保證數據庫和Memcached的同步呢?

複製滯後和同步問題的解決

咱們知道複製滯後的主要緣由是數據庫負載過大而形成異步複製的延遲,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

相關文章
相關標籤/搜索