Twitter 高並發高可用架構

解決 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查詢,理解數據中心的拓撲應該是怎麼樣結構,最後建立這種類型集群。


分享到:


相關文章: