Flickr的架構學習
Flickr.com 是最初由Ludicorp公司開發設計并于2004年2月正式發布的,2007年11月,Flickr迎來了第20億張照片,一年后,這個數字就達到了 30億,并且還 在以加速度增長。 2005年3月,雅虎公司以3千500萬美元收購了Ludicorp公司和Flickr.com。
在討論Flickr 網站架構之前,讓我們先來看一組統計數據(數據來源:April 2007 MySQL Conf and Expo和Flickr網站)
- 每天多達40億次的查詢請求
- squid總計約有3500萬張照片(硬盤+內存)
- squid內存中約有200萬張照片
- 總計有大約4億7000萬張照片,每張圖片又生成不同尺寸大小的4-5份圖片
- 每秒38000次Memcached請求 (Memcached總共存儲了1200萬對象)
- 超過2PB 存儲,其中數據庫12TB(星期天要消費~1.5TB)
- 每天新增圖片超過 40萬(周日峰值超過200萬,約1.5TB)
- 超過8百50萬注冊用戶
- 超過1千萬的唯一標簽(tags)
- 響應4萬個照片訪問請求
- 處理10萬個緩存操作
- 運行13萬個數據庫查詢
2009年Flickr 的數據相比2007的時候又有了顯著的增長:
- 24 TB 的 MySQL 數據
- 每秒鐘 MySQL 有 3.2 萬次寫操作
- 每秒鐘 MySQL 有 12萬次讀操作
- 圖片容量 6PB
- 每天要用掉 10TB 存儲
- 超過 15000 個服務監控點
以下內容是網上信息的拼湊,由于相關的信息比較早,所以有些信息可能已經過期了。
Flickr用到的技術:
- PHP App Servers
- 運行REDHAT LINUX、Apache上的PHP應用,Flickr網站的主體是大約6萬行PHP代碼
- 沒有使用PHP session, 應用是stateless,便于擴展,并避免PHP Server故障所帶來的Session失效。
- 每個頁面有大約27~35個查詢
- 另有專門的Apache Web Farm 服務于靜態文件(HTML和照片)的訪問 - MySQL,Master- Master結構,mysql的常見的master-slave結構,大家都知道存在”single point of failure”(單點故障的問題),且只對讀操作有好處,對于寫頻繁的網站卻不是一個好的解決方案,Flickr的雙master方案據我推測用的就是這個http://code.google.com/p/mysql-master-master/,原理就是master輪詢,保證同時只有一個master負責寫,解決了單點故障的問題。
- Shards
- Memcached 作為中間緩存層
- Squid 作反向代理服務器(reverse-proxy for html and images).
- Smarty 作為模板解析
- Perl 估計用perl做一些系統層面的東西吧,比如日志處理(猜測)
- PEAR 做XML和Email解析
- 前期使用的是ImageMagick 進行圖像處理,在 2004 轉移到 GraphicsMagick, 處理速度提升了 15%。
- Java 作為節點服務
- Apache
- SystemImager 作為服務器部署
- Ganglia 分布式系統監控
- Subcon 用SVN維護服務器配置文件并且可以部署不同的配置文件到服務器集群中去
- Cvsup 用做文件分發、更新
- Wackamole前端負載均衡
- Pair of ServerIron’s 做負載均衡方案
- Squid Caches 代理,用于緩存靜態的HTML和照片
- Dual Tree Central Database
- MySQL 數據庫,存放用戶表,記錄的信息是用戶主鍵以及此用戶對以的數據庫Shard區,從中心用戶表中查出用戶數據所在位置,然后直接從目標Shard中取出數據。
- “Dual Tree”架構是”Master-Master”和“Master-Slave”的有效結合,雙Master 避免了“單點故障”,Master-Slave又提高了讀取速度,因為用戶表的操作90%以上是讀。 - Master-master shards
- MySQL 數據庫,存儲實際的用戶數據和照片的元數據(Meta Data),每個Shard 大約40萬個用戶,120GB 數據。每個用戶的所有數據存放在同一個shard中。
- Shard中的每一個server的負載只是其可最大負載的50%,這樣在需要的時候可以Online停掉一半的server進行升級或維護而不影響系統性能。
- 為了避免跨Shard查詢所帶來的性能影響,一些數據有在不同的Shard有兩份拷貝,比如用戶對照片的評論,通過事務來保證其同步。 - Memcached Cluster 中間層緩存服務器,用于緩存數據庫的SQL查詢結果等。
- Big Search Engine
– 復制部分Shard數據(Owner’s single tag)到Search Engine Farm以響應實時的全文檢索。
– 其他全文檢索請求利用Yahoo的搜索引擎處理 - Storage Manager 運行私有的,適用于海量文件存儲的Flickr File System
- Net App公司的Filer, NAS存儲設備,用于存儲照片
- 服務器的硬件配置:
– Intel或AMD 64位CPU,16GB RAM
– 6-disk 15K RPM RAID-10.
– 2U boxes. - 服務器數量:(數據來源:April 2008 MySQL Conference & Expo)
166 DB servers, 244 web servers(不知道是否包括 squid server?), 14 Memcached servers - 使用SystemImager/SystemConfigurator 自動化安裝、軟件分發,
- Subcon作為配置管理工具提高部署效率。
- Ganglia 來進行容量數據的展現。Ganglia 最主要的優點還是管理的方便性: Client/Server 結構, 各自跑 Demon 進行數據交互(XML形式)。相比 Cacti + Collectd 需要進行很多手工配置,更加方便。John Allspaw很強調測量(measurement)的重要性;他也很鄙視benchmark,無論是對開源軟件比如Cassandra的benchmark,或是自己開發的進程的性能測試,都與上線后運營的負載差異太大,以致對容量規劃幾乎沒幫助。基本上需要在灰度發布后根據實際應用負載才能做比較靠譜的規劃。
Flickr的數據庫從“Master-Slave”的復制模式到Shard架構
也許有人不相信,Flickr是從一臺服務器起步的,即Apache/PHP和MySQL是運行在同一臺服務器上的,很快MySQL服務器就獨立 了出來,成了雙服務器架構。隨著用戶和訪問量的快速增長,MySQL數據庫開始承受越來越大的壓力,成為應用瓶頸,導致網站應用響應速度變慢。一般來說,數據庫的擴展無外是兩條路,Scale-Up和Scale-Out。Scale-Up,簡單的說就是在同一臺機器內增加CPU、內存等硬件來增加數據庫系統的處理能力,一般不需要修改應用程序;Scale-Out,就是我們通常所說的數據庫集群方式, 即通過增加運行數據庫服務器的數量來提高系統整體的能力,而應用程序則一般需要進行相應的修改。Flickr的技術人員發現,查詢即SELECT語句的數量要遠遠大于添加,更新和刪除的數量,比例達到了大約13:1甚至更多,所以他們采用了“Master-Slave”的復制模式,即所有的“寫”操作都在發生在“Master”, 然后”異步“復制到一臺或多臺“Slave”上,而所有的”讀“操作都轉到”Slave”上運行,這樣隨著“讀”交易量的增加,只需增加Slave服務器就可以了。
所以Frickr最初的數據庫架構為:應用程序能夠在多個”Slave“上進行負載均分;2)當一個或多個”slave”出現故障時,應用程序能自動嘗試下一個“slave”,如果全部“Slave”失效,則返回錯誤。 Flickr曾經考慮過的方案是在Web應用和”Slave“群之間加入一個硬件或軟件的”Load Balancer“,這樣的好處是應用所需的改動最小,因為對于應用來說,所有的讀操作都是通過一個虛擬的Slave來進行,添加和刪除“Slave”服務器對應用透明,Load Balancer 實現對各個Slave服務器狀態的監控并將出現故障的Slave從可用節點列表里刪除,并可以實現一些復雜的負載分擔策略,比如新買的服務器處理能力要高 過Slave群中其他的老機器,那么我們可以給這個機器多分配一些負載以最有效的利用資源。
“Master”-“Slave”模式的缺點是它并沒有對于“寫’操作提供擴展能力,而且存在單點故障,即一旦Master故障,整個網站將喪失 “更新” 的能力。解決的辦法采用“Master”-“Master”模式,即兩臺服務器互為”Master“-“Slave”,這樣不僅”讀/寫“能力擴展了一 倍,而且有效避免了”單點故障“,結合已有的“Master”-“Slave”,整個數據庫的架構就變成了下面的”雙樹“結構。“雙樹”架構并沒有支撐太久的時間,大概6個月后,隨著用戶的激增,系統再一次達到了極限,不僅”寫”操作成為了瓶頸,而且“異步復制”也由于 ”Slave“服務器過于繁忙而出現了嚴重的滯后而造成讀數據的不一致。那么,能不能在現有架構加以解決,比如說增加新的”Master“服務器和考慮采用”同步復制“呢?答案是否定的,在Master超過兩臺的設置中,只能采用”閉環鏈“的方式進行復制,在大數據量的生產環境中,很容易造成在任意時刻沒 有一個Master或Slave節點是具有全部最新數據的,這樣很難保障數據的一致性,而且一旦其中一個Master出現故障,將中斷整個復制鏈;而對于”同步復制“,當然這是消除”復制滯后“的最好辦法,不過在當時MySQL的同步復制還遠沒有成熟到可以運用在投產環境中。Flickr網站的架構,需要一次大的變化來解決長期持續擴展的問題。
2005年7月Dathan Pattishall(MySQL 2005、2006年度 “Application of the Year Award”獲得者)加入了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所在數據庫,Dual Tree Central Database就是中央數據庫,存放用戶表,記錄的信息是用戶主鍵以及此用戶對以的數據庫Shard區;而Master-Master Shards就是一個個的Shard用戶數據庫,存儲實際的用戶數據和照片的元數據(Meta Data)。
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目前的方式依然是手工的,既人工來確定哪些用戶需要遷移,然后運行一個后臺程序進行數據遷移,遷移的過程用戶賬戶將被鎖住。
Memcached在Flickr的應用
Flickr為中央數據庫配置了Memcached,但是沒有給Shard配置,原因是什么?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 Server B,并從中取出。最妙的是Server之間彼此是完全獨立的,完全不知道對方的存在,沒有一個類似與Master或Admin Server的存在,增加和減少Server只需在Client端”注冊”并重新Hash就可以了。
Memcached作為數據庫緩存的作用主要在于減輕甚至消除高負載數據庫情況下頻繁讀取所帶來的Disk I/O瓶頸,相對于數據庫自身的緩存來說,具有以下優點:
- Memecached的緩存是分布式的,而數據庫的緩存只限于本機;
- Memcached 緩存的是對象,可以是經過復雜運算和查詢的最終結果,并且不限于數據,可以是任何小于1MB的對象,比如html文件等;而數據庫緩存是以”row”為單 位的,一旦”row”中的任何數據更新,整個“row”將進行可能是對應用來說不必要的更新;
- Memcached的存取是輕量的,而數據庫的則相對較 重,在低負載的情況下,一對一的比較,Memcached的性能未必能超過數據庫,而在高負載的情況下則優勢明顯。
Memcached并不適用于更新頻繁的數據,因為頻繁更新的數據導致大量的Memcached更新和較低的緩沖命中率,這可能也是為什么 Shard沒有集成它的原因;Memcached更多的是擴展了數據庫的”讀“操作,這一點上它和Slave的作用有重疊,以至于有人爭論說應該 讓”Relication”回到它最初的目的”Online Backup”數據庫上,而通過Memcached來提供數據庫的“讀”擴展。然而,在體系架構中增加Memecached并不是沒有代價的,現有的應用要做適當的修改來同步Memcached和數據庫中的數據,同時Memcached不提供任何冗余和“failover”功能,這些復雜的控制都需要應用來實現。
我們看到在每一次數據更新都需要更新Memcached,而且數據庫或Memcached任何一點寫錯誤應用就可能取得“過期”的數據而得到錯誤的結果,如何保證數據庫和Memcached的同步呢?我們知道復制滯后的主要原因是數據庫負載過大而造成異步復制的延遲,Shard架構有效的分散了系統負載,從而大大減輕了這一現象,但是并不能從根本上消除,解決這一問題還是要靠良好的應用設計。當用戶訪問并更新Shard數據時,Flickr采用了將用戶“粘”到某一機器的做法,即同一用戶每次登錄的所有操作其實都是在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也用了Redis,在他的那個offline tasks system,對Redis評價很高。
對于PHP session 的棄用
除圖片外,Flickr所有的數據都存在數據庫中。
php的session是存儲在服務器文件系統的,而且默認沒有做hash目錄,這就意味著如果你的網站訪問量大,比如有10萬個人在線,你的 session目錄下就有10萬個文件,如果你的文件格式是NTFS(windows)或者Ext3(Linux),你要定位到某個文件,系統基本上會假死。使用默認的php session還有另外一個問題:服務器session同步,用戶在A服務器登錄后,session存儲在A服務器上,然后應用跳轉到B服務器,B服務器上的session沒有同步就出問題。
Flickr的架構不能說是完美的,沒有完美的架構,ebay對于擴展有以下建議:
- 不要預先去為性能擴展,出現問題之后找到問題再尋擴展;
- 不要想尋找到一個一勞永逸的方案,因為你不知道下一個瓶頸在哪里;
- 訪問量大了,出了問題,修改架構,穩定運行,訪問量再大了,又出問題了,再修改,這個是解決問題的唯一方案。
Todd Hoff總結的經驗:
- 不要把你的應用簡單的看成一個Web應用,可能會有REST APIs, SOAP APIs, RSS feeds, Atom feeds等等的應用
- “無界限”設計,不要把你的用戶死死的綁定在某個服務器上
- 產品設計時需要做擴容的計劃以及預算
- 慢慢來,不要一開始就買一堆服務器
- 實地考察,不要臆想,獲得實際數據之后再做決定
- 內建日志系統,記錄服務器和應用日志
- Cache,緩存是必不可少的
- 抽象層,由于你的架構隨時可能變,架構的變化必定要帶來底層的變化,這就需要你在底層的基礎上根據業務封裝一層中間層,這樣底層的改動不至于影響業務(這個太重要了,不要因為擴展把原來的程序推倒重來)
- 迭代開發,隨時改進
- 忘記那些調優的小技巧吧,比如很多人對與PHP里面的require和require_once的性能差別,這些性能的差異和架構上的短板比起來根本不足為道
- 在線上測試你的效果
- 忘記用工具測試出來的結果,這些結果只能給你一個大概的印象而已
- 找出你的系統短板,一臺服務器的最大處理能力是多少?現在離最大負載還有多遠?mysql的瓶頸在哪里?是不是磁盤IO?memcache的瓶頸在哪里?CPU還是網絡傳輸?
- 注意你的用戶使用規律,比如Flickr發現每年的第一個工作日比平時多20%~40%的上傳量,周日的訪問量比平時要多40%~50%
- 要注意指數型的增長
- 你的計劃是為你訪問的峰值設計的
除了技術手段的優化,Flickr 充分利用硬件本身的更新換代帶來的好處,曾經用 18 臺新機器替換掉原來的 67 臺 Web 服務器,用 8 臺新機器替換掉原來的 23 臺圖片處理的機器。無論從機架占用還是電力使用都節省了很多,而整理處理能力并沒有削弱。
Flickr 技術團隊隨著網站的快速發展并沒有增加大量人手,個人生產力的產出是相當的高。如何做到的呢?給出了四個非常有趣的原則:
- 使得機器自動構建 (Teach machines to build themselves)
- 使得機器自監控(Teach machines to watch themselves)
- 使得機器自修復(Teach machines to fix themselves)
- 通過流程減少 MTTR (Reduce MTTR by streamlining)
自動購建上,Flickr 使用了 OpsCode 、Puppet 以及 System Imager/Configurator 等。或許這幾個工具值得我們關注一下。
Flickr 團隊內部溝通工具也挺有意思,除了內部的 IRC 用于討論之外,還利用 Yahoo! Messenger 的IM Bot 記錄更多的系統變化,并且,重要的是,將這些信息弄到搜索引擎里面 … “信息查找”,是國內多數團隊交流工具忽視的地方。
參考信息:
- ardware Layouts for LAMP Installations
- capacity planning for LAMP
- Federation at Flickr: Doing Billions of Queries Per Day
- Ticket Servers: Distributed Unique Primary Keys on the Cheap
- The Evolution of the Flickr Architecture
- http://highscalability.com/flickr-architecture
- expo08nyc_moving_pictures.pps
- Operational Efficiency Hacks