Flickr 网站架构分析(转)
原文:http://www.itivy.com/ivy/archive/2011/3/7/634351294385186067.html
Flickr.com 是網上最受歡迎的照片共享網站之一,還記得那位給Windows Vista拍攝壁紙的Hamad Darwish嗎?他就是將照片上傳到Flickr,后而被微軟看中成為Vista壁紙御用攝影師。
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夫婦現在還在后悔吧。
在2005年溫哥華PHP協會的簡報以及隨后的一系列會議上,Flickr的架構師Cal Henderson公開了大部分Flickr所使用的后臺技術,使得我們能有機會來分享和研究其在構建可擴展Web站點的經驗。本文大部分資料來自互聯網和自己的一點點心得,歡迎大家參與討論,要是能夠起到拋磚引玉的作用,本人將不勝榮幸。
Flickr整體框架圖
在討論Flickr 網站架構之前,讓我們先來看一組統計數據(數據來源:April 2007 MySQL Conf and Expo和Flickr網站)
這張是Flickr的網站架構圖,我們這里只作一些簡要的描述,具體的分析請靜待后續文章。
?
數據庫最初的擴展-Replication
也許有人不相信,不過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 | 。。。。。。。。。。。。。。 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. |
最終,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網站的架構,需要一次大的變化來解決長期持續擴展的問題。
Shard - 大型網站數據庫擴展的終極武器?
2005年7月,另一位大牛(MySQL 2005、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系統吧。
轉載于:https://www.cnblogs.com/dew1860/articles/3240971.html
總結
以上是生活随笔為你收集整理的Flickr 网站架构分析(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 投票系统(投票问题可多选、带柱状图)
- 下一篇: mysql jdbc dao_MYSQL