Twitter 高併發高可用架構

發表於2013-07-24

解決 Twitter的“問題”就像玩玩具一樣,這是一個很有趣的擴充套件性比喻。每個人都覺得 Twitter很簡單,一個菜鳥架構師隨便擺弄一下個可伸縮的 Twitter就有了,就這麼簡單。然而事實不是這樣, Twitter的工程副總裁 Raffi Krikorian細緻深入的描述了在 Twitter在可伸縮性上的演化過程,如果你想知道 Twitter的如何工作—從這裡開始吧。

Twitter發展太快,一切轉瞬即過,但 Twitter已經長大了。它從一開始一個在Ruby on Rails上苦苦掙扎的小網站變成一個以服務為 核心驅動的漂亮站點,服務停掉都難得一見,很大的一個轉變。

Twitter現在有1.5億全球活躍使用者,300K QPS,22 MB/秒的流量,系統每天處理4億條推特資料,用5分鐘時間將Lady Gaga手尖流淌出的資訊傳遞到她3100萬個關注者。

一些需要列出來的要點:

  • Twitter不再希望成為一個Web應用程式,Twitter想要成為一套驅動全世界手機客戶端的API,作為地球上最大的實時互動工具。
  • Twitter主要在分發訊息,而不是生產訊息,300K QPS是在讀而每秒僅有6000請求在寫。
  • 不對稱,有數量龐大的粉絲,現在正成為一種常見情況。單個使用者傳送的訊息有很多追隨者要看到,這是一個大的扇型輸出,分發可能很緩慢,Twitter試圖保證在5秒以內,但並不能總是達到這個目標,尤其是當名人或名人之間發推特時,這種情況越來越常見,一個可能後果是在還未看到原始訊息之前接受到了回覆。Twitter做工作是在迎接高關注度使用者所寫推特讀取的挑戰。
  • 你主頁的資料儲存在由800多個Redis節點組成的叢集上。
  • 從你關注的人及你點選的連結Twitter更瞭解你。可以通過隱私設定的雙向以下時不存在。
  • 使用者關心推特內容本身,但對Twitter而言推特的內容與其基礎設施建設幾乎無關。
  • 需要一個非常複雜的監控和除錯系統來跟蹤複雜協議棧內的效能問題。傳統的遺留問題一直困擾著系統。

Twitter是如何工作的?通過Raffi精彩的演講來發現吧…

面臨的挑戰

  • 可靠的實現150萬線上使用者及300K QPS(主頁和搜尋訪問),響應慢怎麼辦?
  • 可靠的實現是一個對所有推特的select語句,響應忙死卡死。
  • 資料扇形輸出的解決方案。當接收到新推特時需要弄清楚應該把它發到哪,這樣可以更快速簡單的讀取,不要在讀操作上做任何邏輯計算,效能上寫要比讀慢得多,能做到4000 QPS。

內部組成

  • 平臺服務部門負責Twitter的核心基礎設施的可擴充套件性。
    • 他們執行的東西為時間軸、微博、使用者及社交網路提供服務,包括所有支撐Twitter平臺的系統裝置。
    • 統一內外部客戶使用相同的API。
    • 為數百萬的第三方應用註冊支援
    • 支援產品團隊,讓他們專注產品無系統支撐方面顧慮。
    • 致力於容量規劃、構建可擴充套件的後端系統等工作,通過不斷更換設施使網站達到意想不到的效果。
  • Twitter有一個架構師部門,負責Twitter整體架構,研究技術改進路線(他們想一直走在前面)。

Push、Pull模式

  • 每時每刻都有使用者在Twitter上發表內容,Twitter工作是規劃如何組織內容並把它傳送使用者的粉絲。
  • 實時是真正的挑戰,5秒內將訊息呈現給粉絲是現階段的目標。
    • 投遞意味著內容、投入網際網路,然後儘可能快的傳送接收。
    • 投遞將歷時資料放入儲存棧,推送通知,觸發電子郵件,iOS、黑莓及Android手機都能被通知到,還有簡訊。
    • Twitter是世界上活躍中最大的資訊傳送機。
    • 推薦是內容產生並快速傳播的巨大動力。
  • 兩種主要的時間軸:使用者的及主頁的。
    • 使用者的時間軸特定使用者傳送的內容。
    • 主頁時間表是一段時間內所有你關注使用者釋出的內容。
    • 線上規則是這樣的:@別人是若被@的人你未關注的話將被隔離出來,回覆一個轉發可以被過濾掉。
    • 這樣在Twitter對系統是個挑戰。
  • Pull模式
    • 有針對性的時間軸。像twitter.com主頁和home_timeline的API。你請求它才會得到資料。拉請求的不少:通過REST API請求從Twitter獲取資料。
    • 查詢時間軸,搜尋的API。查詢並儘可能快的返回所有匹配的推特。
  • Push模式
    • Twitter執行著一個最大的實時事件系統,出口頻寬22MB/秒。
      • 和Twitter建立一個連線,它將把150毫秒內的所有訊息推送給你。
      • 幾乎任何時候,Push服務簇上大約有一百萬個連線。
      • 像搜尋一樣往出口傳送,所有公共訊息都通過這種方式傳送。
      • 不,你搞不定。(實際上處理不了那麼多)
    • 使用者流連線。 TweetDeck 和Twitter的Mac版都經過這裡。登入的時,Twitter會檢視你的社交圖,只會推送那些你關注的人的訊息,重建主頁時間軸,而不是在持久的連線過程中使用同一個時間軸 。
    • 查詢API,Twitter收到持續查詢時,如果有新的推特釋出並且符合查詢條件,系統才會將這條推特發給相應的連線。

高觀點下的基於Pull(拉取方式)的時間軸:

  • 短訊息(Tweet)通過一個寫API傳遞進來。通過負載平衡以及一個TFE(短訊息前段),以及一些其它的沒有被提到的設施。
  • 這是一條非常直接的路徑。完全預先計算主頁的時間軸。所有的業務邏輯在短訊息進入的時候就已經被執行了。
  • 緊接著扇出(向外傳送短訊息)過程開始處理。進來的短訊息被放置到大量的Redis叢集上面。每個短息下在三個不同的機器上被複制3份。在Twitter 每天有大量的機器故障發生。
  • 扇出查詢基於Flock的社交圖服務。Flock 維護著關注和被關注列表。
  • Flock 返回一個社交圖給接受者,接著開始遍歷所有儲存在Redis 叢集中的時間軸。
  • Redis 叢集擁有若干T的記憶體。
  • 同時連線4K的目的地。
  • 在Redis 中使用原生的連結串列結構。
  • 假設你發出一條短訊息,並且你有20K個粉絲。扇出後臺程式要做的就是在Redis 叢集中找出這20K使用者的位置。接著它開始將短訊息的ID 注入到所有這些列表中。因此對於每次寫一個短訊息,都有跨整個Redis叢集的20K次的寫入操作。
  • 儲存的是短訊息的ID, 最初短訊息的使用者ID, 以及4個位元組,標識這條短訊息是重發還是回覆還是其它什麼東東。
  • 你的主頁的時間軸駐紮在Redis叢集中,有800條記錄長。如果你向後翻很多頁,你將會達到上限。記憶體是限制資源決定你當前的短訊息集合可以多長。
  • 每個活躍使用者都儲存在記憶體中,用於降低延遲。
  • 活躍使用者是在最近30天內登陸的twitter使用者,這個標準會根據twitter的快取的使用情況而改變。
  • 只有你主頁的時間軸會儲存到磁碟上。
  • 如果你在Redis 叢集上失敗了,你將會進入一個叫做重新構建的流程。
  •      查新社交圖服務。找出你關注的人。對每一個人查詢磁碟,將它們放入Redis中。
  •      MySQL通過Gizzard 處理磁碟儲存,Gizzard 將SQL事務抽象出來,提供了全域性複製。
  • 通過複製3次,當一臺機器遇到問題,不需要在每個資料中心重新構建那臺機器上的時間軸。
  • 如果一條短訊息是另外一條的轉發,那麼一個指向原始短訊息的指標將會儲存下來。
  • 當你查詢你主頁的時間軸時候,時間軸服務將會被查詢。時間軸服務只會找到一臺你的時間軸所在的機器。
  •      高效的執行3個不同的雜湊環,因為你的時間軸儲存在3個地方。
  •      它們找到最快的第一個,並且以最快速度返回。
  •      需要做的妥協就是,扇出將會花費更多的時間,但是讀取流程很快。大概從冷快取到瀏覽器有2秒種時間。對於一個API呼叫,大概400ms。
  • 因為時間軸只包含短訊息ID, 它們必須”合成”這些短訊息,找到這些短訊息的文字。因為一組ID可以做一個多重獲取,可以並行地從T-bird 中獲取短訊息。
  • Gizmoduck 是使用者服務,Tweetypie 是短訊息物件服務。每個服務都有自己的快取。使用者快取是一個memcache叢集 擁有所有使用者的基礎資訊。Tweetypie將大概最近一個半月的短訊息儲存在memcache叢集中。這些暴露給內部的使用者。
  • 在邊界將會有一些讀時過濾。例如,在法國過濾掉納粹內容,因此在傳送之前,有讀時內容剝離工作。

高階搜尋

  • 與Pull相反,所有計算都在讀時執行,這樣可以使寫更簡單。
  • 產生一條推特時,Ingester會對其做語法分析找出新建索引的一切東西,然後將其傳入一臺Early Bird機器。Early Bird是Lucene的修改版本,索引儲存在記憶體中。
  • 在推特的分發過程中可能被儲存在多個由粉絲數量決定的主頁時間軸中,一條推特只會存入一個Early Bird機器中(不包括備份)。
  • Blender進行時間軸的跨資料中心集散查詢。為發現與查詢條件匹配的內容它查詢每個Early Bird。如果你搜尋“紐約時報”,所有分片將被查詢,結果返回後還會做分類、合併及重排序等。排序是基於社會化度量的,這些度量基於轉發、收藏及評論的數量等。
  • 互動資訊是在寫上完成的,這裡會建立一個互動時間軸。當收藏或回覆一條推特時會觸發對互動時間軸的修改,類似於主頁時間軸,它是一系列的活躍的ID,有最受喜歡的ID,新回覆ID等。
  • 所有這些都被送到Blender,在讀路徑上進行重計算、合併及分類,返回的結果就是搜尋時間軸上看到的東西。
  • Discovery是基於你相關資訊的定製搜尋,Twitter通過你關注的人、開啟的連結瞭解你的資訊,這些資訊被應用在Discovery搜尋上,重新排序也基於這些資訊。

Search和Pull是相反的

  • Search和Pull明顯的看起來很相似,但是他們在某些屬性上卻是相反的。
  • 在home timeline時:
    • 寫操作。tweet寫操作引發O(n)個程式寫入Redis叢集,n代表你的粉絲,如果是這樣,處理Lady Gaga或是Obama百萬粉絲的資料就得用10s的時間,那是很難接受的。所有的Redis叢集都支援硬碟處理資料,但是一般都是在RAM裡操作的。
    • 讀操作。通過API或是網路可用O(1)的時間來找到Redis機器。Twitter在尋找主頁路徑方面做了大量的優化。讀操作可在10毫秒完成。所有說Twitter主導消費機制而不是生產機制。每秒可處理30萬個請求和6000 RPS寫操作。
  • 在搜尋timeline時:
    • 寫操作。一個tweet請求由Ingester收到並有一個Early Bird機器來處理。寫操作時O(1).一個tweet需要5秒的處理時間,其包括排隊等待和尋找路徑。
    • 讀操作。讀操作引發O(n)個叢集讀操作。大多數人不用search,這樣他們可以在儲存tweets上面更加有效,但是他們得花時間。讀需要100毫秒,search不涉及硬碟操作。全部Lucene 索引表都放入RAM,這樣比放在硬碟更加有效。
  • tweet的內容和基礎設施幾乎沒什麼關係。T-bird stores負責tweet所有的東西。大多數tweet內容是放在RAM處理的。如有沒再記憶體中,就用select query將內容抓到記憶體中。只有在search,Trends,或是What’s Happening pipelines中涉及到內容,hone timeline對此毫不關心。

展望:

  • 如何使通道更快更有效?
  • Fanout可以慢下來。可以調整到5秒以下,但是有時候不能工作。非常難,特別是當有名人tweet時候,這種情況越來越多。
  • Twitter 關注也是非常不對稱的。Tweet只提供給定時間內被關注的資訊。Twitter更注重你的資訊,因為你關注了蘭斯.阿姆斯特朗,但是他並沒有關注你。由於不存在互相的關注關係,所以社會聯絡更多是暗示性隱含性。
  • 問題是巨大的基數。@ladygaga 有3100萬關注者。@katyperry有2800萬。@barackobama有2300萬關注著。
  • 當這些人中有人發微博的時候,資料中心需要寫大量微薄到各個關注者。當他們開始聊天時候,這是非常大的挑戰,而這時刻都在發生著。
  • 這些高關注度的Fanout使用者是Twitter最大的挑戰。在名人原始微薄發出到關注使用者之前,回覆一直可以看到。這導致整個網站紊亂。Lady Gaga的一條微薄到關注使用者需要幾分鐘的時間,所以關注者看到這條微薄時間是在不同時間點上。有些人可能需要大概5分鐘的時間才能看到這條微薄,這遠遠落後於其他人。可能早期收到微薄的使用者已經收到了回覆的列表,而這時候回覆還正在處理之中,而fanout還一直進行著所以回覆被加了進來。這都發生在延遲關注者收到原始微薄之前。這會導致大量使用者混亂。微薄發出之前是通過ID排序的,這導致它們主要是單調增長地,但是那種規模下這不能解決問題。對高值的fanouts佇列一直在備份。
  • 試圖找到解決合併讀和寫路徑的方法。不在分發高關注度使用者微薄。對諸如Taylor Swift的人不會在額外的處理,只需要在讀時候,他的時間點併入即可。平衡讀和寫的路徑。可以節約百分之10s的計算資源。

解耦

  • Tweet通過很多的方式解除關聯,主要地通過彼此解耦的方式。搜尋、推送、郵件興趣組以及主頁時間軸可以彼此獨立的工作。
  • F由於效能的原因,系統曾經被解耦過。Twitter曾經是完全同步地方式的,由於效能的原因2年前停止了。提取一個tweet到tweet介面需要花費145微秒,接著所有客戶端被斷開連線。這是歷史遺留的問題。寫路徑是通過MRI用一個Ruby驅動的程式,一個單執行緒的服務。每次一個獨立的woker被分配的時候,處理器都會被耗盡。他們希望有能力盡可能快的去釋放客戶端連線。一個tweet進來了。Ruby處理了它。把它插入佇列,並且斷開連線。他們僅僅執行大概45-48程式/盒。所以他們只能並行處理同樣多tweets/盒,所以他們希望儘可能快的斷開連線。
  • Tweets 被切換到非同步路徑方式,我們討論所有東西都被剔除了。

監控

  • 辦公室四處的儀表盤顯示了系統在任何給定時間系統的執行狀態。
  • 如果你有100萬的關注者,將會要花費好幾分鐘到分發到所有tweets。
  • Tweets 入口統計:每天400兆的tweets。日平均每秒種5000;日高峰時每秒7000;有事件時候是每秒超過12000。
  • 時間軸分發統計:30b分發/日(約21M/分鐘);3.5秒@p50(50%分發率)分發到1M;300k 分發/秒;@p99 大概需要5分鐘。
  • 一個名為VIZ的監控系統監控各個叢集。時間軸服務檢索資料叢集資料的平均請求時間是5毫秒。@p99需要100毫秒。@p99.9需要請求磁碟,所以需要大概好幾百毫秒。
  • Zipkin 是基於谷歌Dapper的系統。利用它可以探測一個請求,檢視他點選的每一個服務,以及請求時間,所以可以獲得每個請求的每一部分效能細節資訊。你還還可以向下鑽取,得到不同時間週期下的每單個的請求的詳細資訊。大部分的時間是在除錯系統,檢視請求時間都消耗的什麼地方。它也可以展示不同緯度的聚合統計,例如,用來檢視fanout和分發了多久。大概用了2年長的一個工程,來把活躍使用者時間線降到2毫秒。大部分時間用來克服GC停頓,memchache查詢,理解資料中心的拓撲應該是怎麼樣結構,最後建立這種型別叢集。
  • 參與翻譯(4人):Garfielt, 桔子, 李勇2, yale8848

相關文章