1. 程式人生 > >支撐5億使用者、1.5億活躍使用者的Twitter最新架構詳解及相關實現

支撐5億使用者、1.5億活躍使用者的Twitter最新架構詳解及相關實現

Twitter如今在世界範圍內已擁有1.5億的活躍使用者,為了給使用者生成timeline(時間軸)需支撐30萬QPS,其firehose每秒同樣生成22MB資料。整個系統每天傳輸tweet 4億條,並且只需要5分鐘就可以讓一條tweet從Lady Gaga手中呈現到她3100萬粉絲的螢幕上。當下Twitter系統的規模及強大的吞吐量確實惹人豔羨,然而在出道之初Twitter也只是個奮鬥在 RoR上的小站點而已,下面就一覽Twitter如何完成從RoR到以服務為核心的系統架構蛻變。

 

Twitter系統的一些特性:

1. 當下的Twitter已不滿足於Web Ap的現狀。Twitter期望成為一組API,驅動世界範圍內的移動客戶端,成為世界級最大的實時事件鏈之一。

2. Twitter主導的是消費機制,而不是生產機制。每秒讀取timeline的操作就會產生30萬次的查詢,而每秒的寫入請求只有6000左右。

3. 離群值,擁有巨量粉絲的個體開始變得普遍,大量粉絲擁有者傳送tweet時會因為大量的擴散而變得緩慢。Twitter試圖將這個延時控制在5秒內,但是也並非一直生效,特別是名人們傳送tweet以及相互轉發變得越來越頻繁後。這樣就導致轉發的內容可能比原始內容先一步到達共同粉絲的介面上,這樣一來,就高價值使用者來說,Twitter的主要精力必須從寫操作轉移到讀操作上。

4. 使用Redis叢集處理Home Timeline(首頁時間軸,包含了眾多關注者的tweet),最大條數為800。

5. 從你關注的人和你點選的連結,Twitter可以獲知一系列關於你的資訊。

6. 使用者最關心的是tweet內容,然而大部分的基礎設施卻和這些內容不相關。

7. 對如此複雜堆疊進行效能追蹤所需求的監視和除錯系統往往非常複雜,同樣舊決策的影響會不時的出現。

Twitter面臨的挑戰

1. 1.5億的使用者以及支撐timeline(home及Search)的30萬QPS會讓最初的具體實現(Naive materialization)變得緩慢。

2. 最初的具體實現由大量選擇語句組成,遍及整個Twitter系統,曾今使用後被取締。

3. 使用一個基於寫的擴散方案。在接收到tweet時,系統將做大量的計算以發現tweet需要呈現的使用者。這將造就更快、方便的讀取,不要對讀做任何的計算。由於所有的計算都被安排到寫去執行,每秒大約可處理4000個寫操作,比讀操作要慢一些。

Twitter的團隊合作

1. Platform Service團隊承擔起了Twitter核心基礎設施的一切事務:

  • 他們負責Timeline Service、Tweet Service、User Service、Social Graph Service這些驅動Twitter平臺的所有元件。
  • 內外客戶端使用了大致相同的API
  • 產品團隊不需要擔心任何規模相關
  • 針對第三方API的註冊應用過百萬
  • 做容量規劃,打造可擴充套件後端系統架構,在網站超出預期增長時要不斷的更換基礎設施。

2. Twitter還擁有一個架構團隊。負責Twitter的整體架構,維護技術負債列表。

Pull和Push模式

1. 任何時刻都有使用者在Twitter上釋出內容,Twitter的任務就是考慮如何將訊息同步發出並呈現到粉絲。

2. 真正的挑戰就是實時性約束,目標則是在5秒內將訊息傳送到粉絲:

  • 交付意味著儘可能快的收集內容、投入網際網路,並且在儘可能短的時間內返回。
  • 交付要做的是釋出到記憶體timeline叢集、推送通知以及觸發電子郵件,其中包括所有的iOS、黑莓、安卓通知以及SMS。
  • Twitter是最大的SMS製造者
  • Elections可以成為產生內容並且以最快速度擴散內容的最大動力

3. 兩種型別的timeline:user timeline(使用者時間軸,即指定使用者tweet頁)及home timeline

  • user timeline就是一個指定的使用者釋出的所有tweet
  • Home timeline是你所有關注使用者user timeline的一個臨時合併
  • 業務規則。非你關注人@你時,將會被自動過濾,轉發的tweet也可以被過濾。
  • 在Twitter的規模做這些事情是非常有挑戰性的

Pull模式

1. 指向timeline,比如Twitter.com及hone_line API。之所以將tweet傳送給你,是因為你對其進行了請求。基於Pull的交付:你通過REST API的呼叫向Twitter請求這些資料。

2. 查詢timeline,搜尋API。對資料庫進行查詢,儘可能快的返回所有匹配指定查詢的tweet。

Push模式

1. Twitter運行了一個巨型的實時事件系統,通過Firehose以每秒22M的速度推送tweet。

  • 給Twitter開啟一個socket,他們將會在150毫秒內完成所有公共tweet的推送。
  • 任何時候給推送叢集開啟的socket都超過1百萬個
  • 使用類似搜尋引擎的firehose客戶端,所有公共的tweet都通過這些socket傳輸

2. 使用者流連線。TweetDeck及Mac版的Twitter同樣通過這種方式驅動。在登入的時候,Twitter會檢視你的社交圖,同樣也只會推送關注人的訊息,重建home timeline,而不是在持久的連線過程中獲得同一個timeline。

3. 查詢API,釋出一個對tweet的持續查詢時,每當有新的tweet釋出,並且被認定匹配這個查詢,系統會將這條tweet傳送給相應的socket。

高等級基於Pull的timeline

  • Tweet由一個寫入API生成,它將會通過負載均衡器及TFE(Twitter Front End)
  • 這種做法很直接,所有的業務邏輯在tweet生成時就已經被執行。
  • 隨著tweet的擴散過程開始,新生成的tweet會被投入一個大規模的Redis叢集中。每個tweet都會在3個不同的機器上做3個拷貝。因為在Twitter的規模,每天會有大把的機器出故障。
  • 粉絲的查詢基於Flock的社交圖服務,Flock會維護粉絲及粉絲列表:
  • Flock會返回一個接收者的社交圖,並且開始迴圈訪問所有儲存在Redis叢集上的timeline
  • Redis叢集擁有TB級以上的記憶體
  • 每次投遞4K左右的tweet
  • Redis使用原生的表結構
  • 如果你有2萬個粉絲,負責粉絲查詢的守護程序將會確認2萬個使用者在Redis叢集中的具體位置,然後它會橫跨整個Redis叢集將Tweet ID插入相應的列表中。所以當你有2萬個粉絲時,每條tweet的寫入都會造成2萬個插入操作。
  • 儲存的資訊包括新生成tweet的ID、tweet編寫者ID以及一個4位元組大小的狀態資訊(轉發、評論或者是其它相關)。
  • Home timeline位於Redis叢集中,每個有800條tweet。如果你向後翻太多頁就沒了,RAM是限制列表tweet數量的最大瓶頸。
  • 為了控制延時,所有活躍使用者都儲存在記憶體中。
  • 活躍使用者的定義是在30天內有登陸過Twitter,當然這個規則可以根據快取容量、實際使用等進行修改。
  • 如果你不是活躍使用者,tweet就不會被放入快取。
  • 只對home timeline進行存檔(持久化。PS:個人覺得這裡應該是user timeline,如果是home timeline下文的重建方法顯然不科學,歡迎大家討論
  • 如果home timeline不在Redis叢集中,則需要經歷一個重建的過程:
  1. 對社交圖服務進行查詢,找出你關注的人。分別的訪問磁盤獲取每個人的資料,然後將他們送回Redis。
  2. 通過Gizzard使用MySQL處理磁碟儲存,這將抽象出所有SQL事務並且提供了全域性備份。
  • 鑑於每條tweet都會做3個備份,如果其中某臺機器發生故障,他們無需對這臺機器上的所有timeline進行重建。
  • 當tweet被轉發時,將會儲存一個指向原tweet的指標。
  • 當做home timeline查詢時,Timeline Service將被呼叫。Timeline Service確認home timeline究竟存在哪臺機器上:
  • 鑑於timeline備份在3個不同的機器上,所以需要執行3個不同的雜湊環。
  • 一旦找著其中一個,就會盡可能快的返回結果。
  • 雖然這個過程會花費稍長的一點時間,但是讀的處理仍然很快。從冷快取到瀏覽器上呈現大約需要2秒,其中一個API的呼叫時間大約400毫秒。
  • 鑑於timeline只包含了tweet的ID,所以還必須要做tweet內容的查詢。確定了ID以後,Twitter將通過T-bird並行獲取tweet的內容。
  • Gizmoduck是個使用者服務,而Tweetypie則是個tweet物件服務,每個服務都擁有自己的獨立快取。使用者快取使用的是memcache叢集,快取了所有使用者。Tweetypie處理的是上個月的內容,它將一半的tweet儲存在它獨立的memcache叢集中,當然這個部分服務的是內部使用者。
  • 內容的過濾同樣會省卻一些讀取時間,比如過濾掉法國的納粹相關,這些內容的讀取時間在呈現之前就被過濾了。

高等級的搜尋

1. 所有的計算都通過讀來解決,這讓寫更加簡潔

2. 當有tweet生成時,Ingester會做相應的語法分析和索引,隨後會將其傳入Early Bird機器中。Early Bird屬於Lucene的修改版本,同時索引都儲存在記憶體中。

3. 在tweet擴散過程中,它可能會被儲存在多個home timeline中,其個數由粉絲的數量決定。然而在Early Bird中,一個tweet只會被存入一個Early Bird機器中(不包括備份)。

4. Blender負責timeline的查詢,橫跨整個資料中心做集散操作。它對每個Early Bird做查詢,以發現與查詢條件匹配的內容。如果你搜索“New York Times”,Blender會查詢資料中心的所有分片並返回結果,同時還會做分類、合併及重新排序等。排序的規則基於互動的資料,也就是轉發、收藏及評論的數量等。

5. 互動的資訊使用寫的模式完成,這裡會建立一個互動timeline。如果你收藏或者回復一個tweet,將會觸發對互動timeline的修改;類似於home timeline,它同樣由一系列的互動ID組成,比如收藏ID、評論ID等等。

6. 所有這些資訊都被送到Blender。以讀的方式進行重算、合併以及分類,返回的結果就是search timeline為你呈現的介面。

7. Discovery是個基於你相關資訊的定製搜尋,這些資訊主要來自你關注的人、開啟的連結,而重新排序的規則同樣基於這些資訊。

Search和Pull是相反的

1. 搜尋和pull看起來非常相似,其實他們有著本質上的區別。

2. 在home timeline情況下:

  • 寫。一個寫tweet的動作會觸發一個O(n)規模的Redis叢集寫入操作,n的值取決於粉絲的數量,由此可見處理Lady Gaga及Barack Obama這樣擁有數千萬粉絲的名人將會很麻煩。Redis叢集上的資訊都會寫入磁碟,Flock叢集會將user timeline儲存到磁碟上,但是通常情況下timeline在Redis叢集的記憶體中都可以發現。
  • 讀。通過API或網路查詢Redis是一個常數規模的操作。Twitter對home tiimeline的讀操作做了高可用性優化,讀操作只花費數十毫秒。這裡也可以看出Twitter主導的是一個消費機制,而不是生產機制。每秒可處理30萬個讀操作,而寫操作每秒處理6000個。

3. 搜尋timeline情況:

  • 寫。Tweet生成,並且傳輸到Ingester,只會寫入一個Early Bird機器。一個tweet處理的時間大約為5秒,其中包括了排隊及尋找待寫入的Early Bird 機器。
  • 讀。每個讀請求都會觸發一個O(n)規模的叢集讀操作。讀大約需要100毫秒,搜尋不涉及到存檔。所有的Lucene索引都儲存在RAM中,所以聚散是非常有效率的,因為不涉及到磁碟。

4. Tweet的內容基本上與大多數的基礎設施都是無關的。T-bird儲存了所有tweet內容,大部分的tweet內容都是在記憶體中。如果沒有的話,可以通過select查詢將其拉回記憶體。與tweet內容相關的功能非常少,搜尋就是其中一個,而Home timeline則完全不關心。

未來的工作

1. 如何將這條資料的管道打造的更快更有效

2. 在5秒內做到tweet的擴散,但是並不是時刻的奏效,特別是越來越多的高粉單位。

3. Twitter是非對稱的關注,只有你關注人的tweet才會呈現給你。Twitter可以從這些單向關注中獲取你更多的資訊,有些單向關注同樣還影射出一些社會契約。

4. 問題一般發生在大基數的圖上:@ladygaga擁有3100萬粉絲,@katyperry擁有2800萬粉絲,@justinbieber擁有2800萬粉絲,@barackobama擁有2300萬粉絲。

5. 大批量粉絲的擁有者每傳送一條tweet將造成資料中心大量的寫入操作,而隨著越來越多名人之間的互動,挑戰變得更加的艱鉅。

6. 這些需要擴散給大批量使用者的tweet是Twitter最大的挑戰,在關注這些名人的共同粉絲中,經常會出現回覆tweet比原文更早一步送達的情況。他們在站點中引入競態條件,比如最近關注Lady Gaga的粉絲可能會比老早之前關注的粉絲早5分鐘看到tweet內容。比如說一個使用者先收到了tweet,並進行回覆,然而這時候Lady Gaga的原微博並沒有擴散完畢,這樣就會存在有些使用者先看到回覆的情況,為使用者造成很大的困擾。Tweet通過ID進行排序,因為他們大多數是單調遞增的,然而在如此粉絲規模下,這種做法並不奏效。

7. 尋找讀和寫途徑的合併,不再做如此大規模的擴散;比如傳播Taylor Swift新生成的tweet,取代在生成時進行擴散tweet ID,而是在讀取時候就進行合併。通過平衡讀寫途徑,節省百分之幾十的資源。

解耦相關

1. 基於Twitter通過各種途徑傳播tweet,解耦可以讓不同技術團隊獨立完成自己的工作。

2. 基於效能問題,系統也需要解耦。Twitter過去使用的一直是完全同步模式,而在兩年前因為效能問題他們停用了這個模式。裝置接收一個tweet需要145毫秒,接收完畢後就斷開所有客戶端連線,這麼做同樣也因為技術負債。寫的路徑由Ruby驅動,通過MRI實現,一個單執行緒伺服器,每次Unicorn worker分配都會耗盡所有處理效能。每當有tweet流入,Ruby就會接收它,將它放到一個佇列中然後斷開連結。他們在每臺伺服器上只執行45-48個程序,這樣的話每個機箱能同時處理的tweet數量也是這麼多,所以他們必須儘可能快的斷開連線。

3. 當下的tweet已經通過非同步模式來處理,而這些討論也都是建立在非同步之上。

監視相關

1. 系統性能實時儀表盤

2. 使用VIZ系統監視每個叢集,請求Timeline Service並從Scala叢集獲取資料的平均時間為5毫秒。

3. 基於Google Dapper系統的Zipkin,工程師可以通過Zipkin對請求的細節進行監視,比如獲取請求所訪問的服務及請求時間,這樣就可以獲知每個請求的效能細節。這樣就可以通過每個階段耗費的時間對系統進行除錯,同樣也可以從總體上看從請求到交付耗費的時間。花費了兩年的時間,Twitter將活躍使用者的timeline降到2毫秒。

部分統計資料:

  • 如果你有100萬個粉絲,每個tweet將耗費數秒的時間來傳播
  • Tweet輸入統計:每天4億條;日平均統計5000每秒;日統計峰值7000每秒;大事件期間高於1.2萬每秒。
  • Timeline交付統計:每天300億次(更多資料見原文)