SoundCloud研發團隊Sean Treadway談SoundCloud架構演變

SoundCloud是一個新興的社會化音樂創建和分享平臺,前不久,他們研發團隊的Sean Treadway在SoundCloud的博客上談到了SoundCloud的架構演變

Sean開門見山指出:

擴展是一個奢侈的問題,它與組織架構的關係遠超與具體技術實現的關係。在每個變化階段,我們都會預測用戶的下一個數量級,從數千開始,我們的設計現在支持數億用戶。我們識別出瓶頸,解決它們的方法也很簡單:在基礎設施中加入明確的集成點,並以分而治之的方式處理各個問題。

識別擴展點,將其轉爲一系列更小的問題;有良好定義的集成點;這些方法讓我們能夠以有機而系統的方式增長。

產品初期

SoundCloud最開始的架構簡單而直接:

互聯網->Web層(Apache)->應用層(Rails)->數據層(MySQL)

Apache支持圖片、風格和行爲資源,由MySQL支持的Rails提供一個環境,幾乎所有的產品都在其中有model,可以快速完成路由和呈現。大多數團隊成員都可以理解這種模型,交付的產品與我們現在的產品很類似。

我們有意沒有在這時處理高可用問題,也知道到時可能面臨哪些問題。 此時我們版本脫離private beta,面向公衆發佈了SoundCloud。

我們的主要成本是機會成本,只要是阻礙了我們開發SoundCloud產品理念的東西,都被規避了。

在早期,我們有意確保構建的不僅是一個產品,而是一個平臺。從一開始,我們的公開API與網站同步開發。現在,我們與第三方集成者使用的API完全相同,並以之推動網站開發

後來,SoundCloud用Nginx替換了Apache,主要原因有兩個:

Rails應用服務器運行在多個主機之上,而Apache處理多個虛擬主機的配置和路由很繁瑣,特別是要保持開發和生產環境之間的同步時;

爲了更好地提供連接池和基於內容的路由配置,以便管理、分配接收到的web請求、緩存向外的響應,並可以空出一個應用服務器,儘快處理後續請求。

負載分佈與排隊理論

接下來,SoundCloud發現有些負載耗去的時間比其他要多幾百個毫秒,一些較快的請求必須要等較慢的請求處理完成。2008年,他們研發架構時,Rails和ActiveRecord中的並行請求處理還不夠成熟。他們也開發了一些並行請求處理的代碼,但是爲了不佔用更多時間去檢查依賴,他們使用的方法是:

在每個應用服務器進程上運行一個並行進程,並在每個主機上運行多個進程。

Sean接下來用到了排隊理論中的肯德爾記錄法(Kendall's notation)。

我們從web服務器嚮應用服務器發送一個請求,這個請求過程可以建模爲一個M/M/1隊列。該隊列的響應時間由之前所有的請求決定,因此,如果大幅提升一個請求的平均處理時間,那麼平均響應時間也會大幅提升。

由於當時仍然處於機會成本最高的階段,所以Sean和他的團隊決定:在繼續開發產品的同時,用更好的請求分發方法來解決這個問題。

我們研究了Phusion旅客方法,也就是在每個主機上使用多個子進程,可是認爲這樣可能很快會在每個子進程上填滿長時間運行的請求。這就像有多個隊列,每個隊列上有幾個工作者,模擬在單個監聽端口上的併發請求處理。

這就把M/M/1隊列模型變成了M/M/c隊列模型,c是子進程數目。

該模型類似於銀行中使用的排號系統。

該模型降低的響應時間由c決定,如果有5個子進程,對緩慢請求的處理速度能快5倍。但是,我們當時已經預計未來幾個月用戶會有10倍增長,而且每個主機上的處理能力有限,因此,只加入5到10個工作者並不足以解決排隊阻塞問題。

我們希望系統中不要有等待隊列,如果有,隊列中的等待時間也要降到最低。如果將M/M/c用到極致,我們自問:“如何才能讓c儘可能大?”

爲了達到該目的,我們需要確保單個Rails應用服務器每次絕不接收超過1個請求,TCP方式的負載均衡就此出局,因爲TCP無法區分HTTP請求和響應。我們也要保證:如果所有的應用服務器都是忙碌狀態,請求將會被排隊到下一個可用的應用服務器中。這就意味着我們必須保證所有的服務器做到完全無狀態。當時,我們做到了後者,但未實現前者。

我們在基礎設施中加入了HAProxy,將每個後端配置的最大連接數爲1,並在所有主機中加入了後端進程,保證M/M/c在等待時間上的減少,生成HTTP請求隊列,當任何主機上的後端進程可以處理時再發送過去。

將HAProxy作爲隊列負載均衡器,Sean他們就可以把其他組件中複雜的隊列設計推到請求管道中處理。此時架構如下圖:

 

Sean全力推薦Neil J. Gunther的書籍《使用Perl::PDQ分析計算機系統性能》,幫助大家複習排隊理論,更多瞭解如何針對HTTP請求隊列系統進行建模和度量,並可以深入到磁盤控制器的層面。

走向異步

爲了解決用戶通知和存儲增長方面的問題,SoundCloud決定加入中間層,以有效地解決工作隊列的失敗處理問題。最後他們選擇了AMQP,因爲它提供可編程的拓撲,由RabbitMQ實現。

爲了不修改網站中的業務邏輯,我們調整了Rails環境,併爲每個隊列構建了一個輕量級的分發器。隊列的命名空間描述了預估的工作次數,這在異步工作者中創建起一個優先級系統,同時不需要向broker中加入信息優先級的處理複雜性,因爲每一類工作的分發器進程只處理該類工作中的多個隊列。應用服務器中的絕大多數異步工作隊列,其命名空間中要麼有“interactive”(工作時間小於250ms),要麼是“batch”(任何工作時間)。其他命名空間被用於特定應用。

此時他們的架構圖如下:

緩存

當用戶達到十萬數量級時,Sean發現應用層佔用了過多CPU,主要用在呈現引擎和Ruby運行時上。

不過,他們沒有用Memcached,而是緩存了大量DOM片段和完整頁面。由此引發的失效問題,他們通過維護緩存主鍵的反向索引解決,使用Memcached,當應用中的model發生改變從而導致失效時,同樣需要該方法解決。

我們最高的海量請求,來自於某個特定的服務,它向頁面微件(widge)交付數據。我們在Nginx中爲該服務創建了特定路由,併爲其加入代理緩存。不過我們希望緩存功能能夠通用化,做到任何服務都能創建正確的HTTP/1.1緩存控制頭,並可以由我們控制的某個中介正確處理。現在,我們的微件內容完全由公開API提供。

此後,爲了處理後端部分呈現模板緩存和大多數只讀API響應,他們加入了Memcached,並在很晚之後加入了Varnish。此時,他們的架構是:

通用化

SoundCloud後來的處理模型變成:針對某個領域model,爲其狀態設定連續處理方式,以便處理後續狀態。

爲了通用化這個模式,他們利用了ActiveRecord的after-save鉤子,加入了ModelBroadcast方式。原則是:當業務領域變化時,事件會丟入AMQP總線中,任何對此變化感興趣的異步客戶端會得到該變化。

把寫路徑從閱讀者中解耦出來,這種技術容納了我們從未想到過的集成點,爲未來的增長和演化提供了更大空間。

以下是示例代碼:

after_create do |r|
  broker.publish("models", "create.#{r.class.name}",  r.attributes.to_json)
end

after_save do |r|
  broker.publish("models", "save.#{r.class.name}", r.changes.to_json)
end

after_destroy do |r|
  broker.publish("models", "destroy.#{r.class.name}", r.attributes.to_json)
end

Dashboard功能

數據的快速增長引出了Dashboard功能。在SoundCloud中,用戶可以在Dashboard中看到個人和的社會化活動索引,並可以個人化來自自己關注的人制作的音樂片段。SoundCloud一直受困於Dashboard組件帶來的存儲和訪問問題。

讀寫的路徑各自不同,讀操作路徑需要針對每個用戶提供一定時間範圍內的順序讀並做優化,寫操作路徑需要對任意訪問做優化,而且一個事件就有可能影響幾百萬用戶的索引。

解決方法是重新排序任意讀操作,將其變爲順序方式,並以順序格式存儲供未來的讀取使用,這可能會擴展到多個主機上。排序字符串表非常適合用持久化格式,考慮到需要自由分區和擴展,我們選擇了Cassandra存儲Dashboard的索引。

我們從model廣播開始,然後用RabbitMQ做隊列完成步驟處理,主要包括三步:扇出(fan-out)、個性化、指向領域模型的外鍵引用串行化。

  • 扇出會找出一個活動應該傳播到的社會化圖譜中的區域。
  • 個性化查看發起者和目的用戶之間的關係,以及其他註解或過濾索引項目的信號。
  • 串行化把Cassandra中的索引條目持久化,供以後查找使用,並與領域模型做聯接,以供顯示或API展示。

此時他們的架構如下:

搜索

SoundCloud的搜索通過HTTP接口暴露數據集操作子集,供查詢使用。索引的更新與Dashboard類似,通過ModelBroadcase完成,並使用了由Elastic Search管理的索引存儲,在複製數據庫方面有提升。

通知和狀態

爲確保用戶得到Dashboard的通知,SoundCloud在Dashboard的工作流中加入了一個階段,用來接收Dashboard索引更新的消息。Agent可以通過消息總線得到路由到它們自己的AMQP隊列中的事件完成通知。他們的狀態和統計通過broker中介集成,但是沒有ModelBroadcast,他們會發出在日誌的隊列中的特定領域事件,然後保存在隔離的數據庫集羣中,以滿足不同時間段快速訪問需要。

未來預期

Sean這樣描述他們對未來的規劃:

我們已經建立起了明確的集成點,包括供異步寫路徑的broker中介中,包括向後端服務完成同步讀和寫的路徑的應用中。

隨着時間演變,應用服務器的數據庫已經承擔了集成和功能兩方面的職責。產品開發基本塵埃落定,我們現在有信心將功能與集成分開,轉移到後端服務中,供應用層還有其他後端服務使用,各自都可以在持久化層中有自己的命名空間。

我們在SoundCloud的研發方式,是識別擴展點,然後分別隔離、優化讀路徑和寫路徑,並預估下一個成長階段的數量級。

最後,Sean指出了SoundCloud以前和現在在架構上的約束:

在產品開發開始階段,我們讀寫擴展的限制來自消費者的關注和開發人員的時間。現在,我們的工程方向是I/O、網絡和CPU的限制。

發佈了24 篇原創文章 · 獲贊 14 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章