Instagram 5位傳奇工程師背後的技術揭祕

Instagram是一家基於iOS和Android的社交圖片照片分享應用開發商。憑藉着獨特的運營理念,自2010年3月成立以來,短短一年的時間就吸引了1400萬用戶。而後隨着手機相機改動、圖像處理升級、與Facebook等社交靈活交互、支持Android等服務不斷升級,用戶量迅速衝擊3000萬,於2012年9月被Facebook以7.15億美元收購。而截止到今年2月底,其活躍用戶成功突破1億


Instagram兩位創始人

與高速增長相背離的是,從成立之初僅有凱文·希斯特羅姆(Kevin Systrom)和邁克·克里格(Mike Krieger)兩位創始人,到2011年獲得A輪風投700萬美元的4位員工,再到被收購時的13人團隊,Instagram人員組織一直極爲精簡

如此小規模的團隊居然可以如此自如地應對飛速增長的用戶數並提供創新服務,這不能不說是硅谷的又一個財富傳奇。以至於Instagram技術團隊撰寫的《Instagram:數百的實例 大量的技術》一經發布,就獲得了創業企業CTO們的熱烈迴應。彼時,Instagram的團隊還在尋找一個“可以馴服EC2 實例羣的DevOps”。

沒有想到,收購如此來勢洶洶。2012年4月10日,Facebook宣佈收購Instagram。兩天之後,Instagram的聯合創始人Mike Krieger公開發表《如何成爲十億美元公司》演講,第一次向外界全面地展現了Instagram創業歷程以及其中不得不說的技術“祕密.”。本文爲演講PPT全文翻譯,有助於創新技術團隊更好認識和了解Instagram13人團隊創造奇蹟所依賴的技術:

Instagram技術團隊:

2010年: 2位工程師
2011年: 3位工程師
2012年: 5位工程師

Instagram核心原則:

1 simplicity(簡潔主義)
2 optimize for minimal operational burden(爲儘量減少運維負擔而優化)
3 instrument everything(監控一切)

一、初創階段:

兩名沒有任何後端的實戰經驗的創始人;

通過託管在洛杉磯某處的一臺機器(甚至性能都沒有MacBook Pro強);

存儲採用CouchDB(Apache CouchDB 是一個面向文檔的數據庫管理系統);

產品上線第一天有25000註冊用戶。

二、上線階段:

因爲忘記favicon.ico圖標文件,在Django上引起大量404錯誤。

這是第一個經驗教訓。後面還有:

ulimit -n ,設置Linux內核可以同時打開的文件描述符的最大值,例如size爲4092。

memcached -t 4,設置用於處理請求的線程數。

prefork/postfork 線程的預加載還是後加載。

顯然,絕大多數系統擴展問題繁瑣而困難。而在不斷出現問題並解決問題的過程中,Instagram決定遷往AWS的EC2。

三、遷移階段:

“let’s move to EC2”就像是“對100碼速度行駛的汽車更換所有部件”。

具體分析:

   1.  數據庫擴展

  早期:django ORM+postgresql(PostGIS)

  因爲PostGIS而選擇了postgresql,PostGIS在對象關係型數據庫PostgreSQL上增加了存儲管理空間數據的能力,相當於Oracle的spatial部分,數據庫可部署在獨立服務器上。

   隨着照片數量的爆發式增長,最大內存爲68G的EC2顯然無法支持。

  改變:進行vertical partitioning(垂直分區),並通過django db routers使垂直分區更加容易。

   如:照片則映射到photodb

def db_for_read(self, model):<br>&nbsp; &nbsp; if app_label == 'photos':<br>&nbsp; &nbsp; &nbsp; &nbsp; return 'photodb'

    幾個月以後,photodb>60G的時候,採用horizontal partitioning(水平分區,用“分片”sharding實現)

    但sharding也帶來諸多問題:

       1). 數據檢索(多數情況下,很難知道用戶的主訪問模式)

  2). 當有分片變得太大的時候怎麼辦?


  可以採用,基於範圍的分片策略(如MongoDB一樣)


  3). 性能下降,特別是由EC2實例磁盤IO導致,解決方法是:預先切分(pre-split),即預先切分上千個邏輯切片,將它們映射到較少的物理分區節點中去。



2.  選擇合適工具

    進行緩存/反規範化數據設計

    用戶上傳圖片時:

1). 用戶上傳帶有標題信息和地理位置信息(可選)的照片;

2). 同步寫到這個用戶對應的數據庫(分片)中;

3). 進行隊列化處理

a 如果帶有地理位置信息,通過異步的POST請求,將這個圖片的信息送到Solr(Instagram 用於geo-search API的全文檢索服務器)。

b 跟隨者的信息分發(follower delivery),即告訴我的follower ,我發佈了新的照片。如何來實現的呢?每個用戶都有一個follower 列表,新照片上傳時會把照片ID發送給列表中的每一個用戶,用Redis 來處理這一業務簡直太棒了,快速插入,快速子集化。

c 當需要生成feed的時候,我們通過ID+#的格式,直接在memcached中查找信息

    Redis適合什麼樣的場景?

1).數據結構相對有限;

2).對頻繁GET的地方,對複雜對象進行緩存;

不要將自己綁定在非得以內存數據庫爲主要存儲策略的方案上。

   關於Follow圖譜

第一版:簡單的數據庫表格(source_ id, target_id, status) 

需要來回答如下查詢:我關注誰?誰關注我?我是否關注某人?某人是否關注我? 

當數據庫的壓力變大時,Instagram開始在Redis中併發存儲關注圖譜,但這也帶來了內容一致性(consistency)的問題。而不一致性一度會帶來緩存失效問題。

PostGIS結合輕量的memcached緩存,可以支撐上萬的請求量。

需要注意點:

        1). 核心數據存儲部分有一個萬能的組件支撐,就像:Redis;

        2).千萬不要試想用兩種工具去做同一個工作;

3.  保持敏捷

1). 廣泛的單元測試和功能測試

2). 堅持DRY(Don’t Repeat Yourself)原則

3). 使用通知/信號機制實現解耦

4). 我們大部分工作使用Python來完成,只有逼不得已的時候,纔會用C

5). 頻繁的代碼複查,儘量保持“智慧共享”。

6). 廣泛的系統監控

4.  往Android平臺擴展

12小時增加100萬新用戶的關鍵:

1). 偉大的工具可以使讀取更具擴展性,例如:redis: slaveof <host> <port>(SLAVEOF 命令用於在 Redis 運行時動態地修改複製(replication)功能的行爲);

2). 更短的迭代週期;

3). 不要重複發明輪子,例如想開發一個系統監控的守護進程,完全沒有必要,HAProxy完全能勝任這一工作;

4). 找強大的技術顧問;

5). 技術團隊保持開放的氛圍並積極回饋開源世界;

6). 關注優化,想辦法讓系統速度快上一倍。

7). 保持敏捷;

8).使用最少部件,最乾淨的解決方案;

9). 不要過度的優化,除非你提前知道自己的系統將如何擴展

PPT很好的保持了Instagram“simplicity”的哲學,即使提到技術,也精簡到了極致。爲了讓更多朋友明瞭,特別從其工程師博客上選擇更多細節來補足,而這裏,是這5位工程師實踐經驗的總結,其中不乏那些極爲實用的開源工具

四、其他細節技術

1. 操作系統/主機

  在Amazon EC2上跑Ubuntu Linux 11.04 (“Natty Narwhal”),這個版本經過驗證在 EC2 上夠穩定。但之前的版本在EC2上高流量的時候都會出現各種不可預測的問題。

2. 負載均衡

     每一個對Instagram 服務器的訪問都會通過負載均衡服務器;我們使用2臺Nginx機器做DNS輪詢。這種方案的缺點是當其中一臺退役時,需要花時間更新DNS。最近,轉而使用Amazon的ELB(Elastic Load Balancer)負載均衡器,使用3個Nginx 實例可以實現調入調出(而當某個Nginx實例通不過故障檢測,系統會自動將其從循環中抽離);同時在 ELB 層停掉了 SSL , 以緩解nginx的 CPU 壓力。使用Amazon的Route53服務作爲DNS服務,這是AWS控制檯上增加的一套很好的GUI工具。

3. 應用服務器

      在Amazon的High-CPU Extra-Large機器上運行了Django ,隨着用戶的增長,已經在上面跑了25個Django實例了(幸運地,因爲是無狀態的,所以非常便於橫向擴展)。但發現個別工作負載是屬於計算密集型而非IO密集型,因此High-CPU Extra-Large類型的實例剛好提供了合適的比重(CPU和內存)。

    爲此,使用 Gunicorn 作爲 WSGI 服務器。過去曾用過 Apache 下的 mod_wsgi 模塊,不過發現 Gunicorn 更容易配置並且節省 CPU 資源。使用 Fabric 加速部署。Fabric最近增加了並行模式,因此部署只需要花費幾秒鐘。

4. 數據存儲

    大部分數據(用戶信息,照片的元數據、標籤等)存儲在PostgreSQL中;並基於不同的Postgres 實例進行切分的。主要分片集羣包含12個四倍超大內存雲主機(且12個副本在不同的區域);

    而亞馬遜的網絡磁盤系統(EBS)每秒的尋道能力不夠,因此,將所有工作放到內存中就變得尤爲重要。爲了獲得合理的性能,創建了軟 RAID 以提升 IO 能力,使用的 Mdadm 工具進行 RAID 管理;

    這裏,vmtouch用來管理內存數據是個極好的工具,尤其是在故障轉移時,從一臺機器到另一臺機器,甚至沒有活動的內存概要文件的情況。這裏是腳本,用來解析運行於一臺機器上的vmtouch 輸出並打印出相應vmtouch命令,在另一臺機器上執行,用於匹配他當前的內存狀態;

    所有的PostgreSQL實例都是運行於主-備模式(Master-Replica),基於流複製,並且使用EBS快照經常備份我們的系統。爲了保證快照的一致性(原始靈感來源於ec2-consistent-snapshot)使用XFS作爲我們的文件系統,通過XFS,當進行快照時,可以凍結&解凍RAID陣列。爲了進行流複製,我們最愛的工具是repmgr 

    對於從應用服務器連接到數據,我們很早就使用了Pgbouncer做連接池,此舉對性能有巨大的影響。我們發現Christophe Pettus的博客 有大量的關於Django、PostgreSQL 和Pgbouncer 祕訣的資源。

    照片直接存儲在亞馬遜的S3,當前已經存儲了幾T的照片數據。使用亞馬遜的CloudFront作爲我們的CDN,這加快了全世界用戶的照片加載時間。

    爲了geo-search API,我們一直使用PostgreSQL了很多個月,不過後來遷移到了Apache Solr.他有一套簡單的JSON接口,這樣我們的應用程序相關的,只是另一套API而已。

    最後,和任何現代Web服務一樣,使用了Memcached 做緩存,並且當前已經使用了6個Memcached 實例,我們使用pylibmc & libmemcached進行連接。Amzon最近啓用了一個靈活的緩存服務(Elastic Cache service),但是它並不比運行我們自己的實例便宜,因此我們並沒有切換上去;

    5. 任務隊列&推送通知

    當一個用戶決定分享一張Instagram 的照片到Twitter 或Facebook,或者是當我們需要通知一個 實時訂閱者有一張新的照片貼出,我們將這個任務推到 Gearman,一個任務隊列系統能夠寫於Danga。這樣做的任務隊列異步通過意味着媒體上傳可以儘快完成,而“重擔”可以在後臺運行。我們大概有200個工作實例(都用Python寫的)在給定的時間內對隊列中的任務進行消費,並分發給不同的服務。我們的feed feed fan-out也使用了Gearman,這樣posting就會響應新用戶,因爲他有很多followers。

    對於消息推送,找到的最划算的方案是,一個開源的Twisted 服務,已經爲我們處理了超過10億條通知,並且絕對可靠。

    6. 監控

    基於 Python-Munin,寫了很多Munin 插件,使用Munin進行圖形化度量。用於圖形化度量非系統級的東西(例如,每秒的簽入人數,每條照片發佈數等),使用Pingdom作爲外部監控服務,PagerDuty 用於事件通知。

    Python錯誤報告,是使用的Sentry,一個Disqus的工程師寫的令人敬畏的開源的Django app。在任何時間,都可以實時的查看系統發生了什麼錯誤。

文章到這裏,並沒有結束。對今天已經跨越“億”線的Instagram而言,“爲了最小的運營負擔而優化程序,利用一切能用到的(開源)工具與雲平臺,極簡的技術主張”未變,不信,看看最近的新聞,《Instagram的負載均衡武器:Eureka填補了Amazon Web Services的大缺口》還在續寫屬於他自己的技術傳奇。(@紅心李的分析對本文也有貢獻,審校/仲浩)

鏈接:Mike Krieger《如何成爲十億美元公司》演講PPT

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章