建築釺


Braze如何在規模上利用Ruby

紮克麥考密克 通過紮克麥考密克2022年8月18日,

如果您是一名工程師,經常閱讀Hacker News、Developer Twitter或其他類似的信息來源,那麼您幾乎肯定會遇到上千篇標題為“鏽的速度vs C”、“是什麼讓Node.js比Java更快?”或“為什麼你應該使用Golang以及如何開始這些文章通常都認為,有一種特定的語言是可伸縮性或速度的明顯選擇,而您唯一要做的就是接受它。

當我還在上大學的時候,當我還是一名工程師的頭一兩年,我就會讀這些文章,然後馬上開始做一個喜歡的項目,去學習當前流行的新語言或框架。畢竟,它保證能“在全球範圍內”發揮作用,“比你見過的任何東西都要快”,誰能抗拒呢?最終,我發現在我的大多數項目中,我實際上並不需要這些非常具體的東西。隨著我事業的發展,我開始意識到沒有任何語言或框架的選擇可以做到這一點實際上這些東西免費給我。

相反,我發現當你想要擴展係統時,架構才是最大的杠杆,而不是語言或框架。

在Braze,我們在全球範圍內開展業務。是的,我們使用Ruby和Rails作為我們的兩個主要工具來完成它。然而,沒有“global_scale = true”配置值使這一切成為可能——這是一個深思熟慮的體係結構的結果,從應用程序深處一直延伸到部署拓撲。Braze的工程師們不斷地檢查擴展性瓶頸,並找出如何使我們的係統更快的方法,而答案通常不是“離開Ruby”:幾乎可以肯定的是要改變架構。

因此,讓我們看看Braze如何利用周到的體係結構來解決速度和大規模的全球規模問題——以及Ruby和Rails適合(不適合)它的地方!

一流架構的力量

一個簡單的Web請求

由於我們的運營規模,我們知道與我們客戶的用戶群相關的設備每天將發出數十億個web請求,這些請求必須由Braze的web服務器提供服務。manbetx万博全站客户端即使是最簡單的網站,你也會有一個相對複雜的流程,與客戶端到服務器端的請求相關聯:

  1. 首先,客戶端的DNS解析器(通常是他們的ISP)根據網站URL中的域名,找出要訪問的IP地址。

  2. 一旦客戶端有了一個IP地址,他們將把請求發送到他們的網關路由器,後者將把它發送到“下一跳”路由器(這可能會發生幾次),直到請求到達目的IP地址。

  3. 然後,接收請求的服務器上的操作係統將處理網絡細節,並通知web服務器的等待進程,在它正在監聽的套接字/端口上收到了傳入請求。

  4. web服務器將把響應(請求的資源,可能是index.html)寫入套接字,然後通過路由器返回到客戶端。

對於一個簡單的網站來說,這些東西太複雜了,不是嗎?幸運的是,這些事情中有許多已經為我們處理好了(稍後會詳細介紹)。但是我們的係統仍然有數據存儲、後台作業、並發性問題以及其他需要處理的問題!讓我們來看看這是什麼樣子的。

第一個支持規模的係統

在大多數情況下,DNS和名稱服務器通常不需要太多的關注。你的頂級域名服務器可能會有一些條目將“yourwebsite”映射到你的域名服務器,如果你使用的是像Amazon Route 53或Azure DNS這樣的服務,他們會處理你的域名服務器(例如管理a, CNAME或其他類型的記錄)。您通常不必考慮縮放這個部分,因為這將由您所使用的係統自動處理。

然而,流的路由部分可能很有趣。有一些不同的路由算法,如開放最短路徑優先或路由信息協議,它們都旨在尋找從客戶端到服務器的最快/最短路由。因為互聯網實際上是一個巨大的連通圖(或者,或者,一個流網絡),可能有多個路徑可以利用,每一個都有相應的更高或更低的成本。尋找絕對最快的路線是不可能的,所以大多數算法使用合理的啟發式來獲得可接受的路線。電腦和網絡並不總是可靠的,所以我們依賴急劇增強客戶端更快地路由到我們服務器的能力。

fast的工作原理是在世界各地為存在點(pop)提供非常快速、可靠的連接。把它們想象成互聯網的州際高速公路。我們的域的A和CNAME記錄指向fast,這導致我們的客戶機的請求直接發送到高速公路。從那裏,快速可以把他們送到正確的地方。

通往Braze的前門

好了,我們客戶的請求已經通過了快速高速公路,就在Braze平台的前門——接下來會發生什麼呢?

在簡單的情況下,該前門將是一個接受請求的單一服務器。正如您可以想象的那樣,這將不能很好地擴展,因此我們實際上將Fastly指向一組負載平衡器。負載平衡器可以使用各種各樣的策略,但是想象一下,在這個場景中,快速地將請求均勻地循環到負載平衡器池中。這些負載均衡器會將請求排隊,然後將這些請求分發到web服務器,我們也可以想象它們正在以循環的方式處理客戶機請求。(在實踐中,某些類型的親和力可能是有好處的,但這是另一個話題了。)

這允許我們根據我們獲得的請求吞吐量和我們可以處理的請求吞吐量來擴展負載均衡器和web服務器的數量。到目前為止,我們已經構建了一個可以毫不費力地處理大量請求的體係結構!它甚至可以通過負載均衡器請求隊列的彈性來處理突發流量模式——這太棒了!

Web服務器

最後,我們來到令人興奮的(Ruby)部分:web服務器。我們使用Ruby on Rails,但那隻是一個網絡框架——真正的網絡服務器是Unicorn。Unicorn的工作方式是在一台機器上啟動多個工作進程,每個工作進程在OS套接字上監聽工作。它為我們處理進程管理,並將請求的負載平衡傳遞給操作係統本身。我們隻需要我們的Ruby代碼盡可能快地處理請求;其他一切都在Ruby之外為我們進行了有效優化。

因為大多數請求要麼由SDK裏麵我們的客戶通過我們的應用程序或REST API是異步的(即我們不需要等待操作完成特定響應返回給客戶端),大多數manbetx万博全站客户端的API服務器的結構非常簡單驗證請求,任何API鍵約束,然後把複述,隊列的請求並返回一個200響應給客戶端如果一切檢查。

Ruby代碼處理這個請求/響應周期大約需要10毫秒,其中一部分時間用於等待Memcached和Redis。即使我們用另一種語言重寫所有這些,實際上也不可能從中擠出更多的性能。最後,正是您目前所閱讀的所有內容的體係結構,使我們能夠擴展數據攝取過程,以滿足客戶不斷增長的需求。manbetx万博全站客户端

工作隊列

這是一個我們過去已經探討過的主題,所以我不會深入討論這個方麵—要了解更多關於我們的作業排隊係統的信息,請查看我的帖子使用隊列實現彈性.在高層次上,我們所做的是利用大量的Redis實例作為作業隊列,進一步緩衝需要完成的工作。與我們的web服務器類似,這些實例被分割到不同的可用性區域——在特定可用性區域出現問題時提供更高的可用性——並且它們以主/備對的形式使用Redis Sentinel實現冗餘。我們還可以在水平和垂直方向上擴展它們,以優化容量和吞吐量。

工人們

這當然是最有趣的部分——我們如何讓工人規模化?

首先也是最重要的是,我們的工作人員和隊列由許多維度劃分:客戶、工作類型、所需的數據存儲等等。manbetx万博全站客户端這讓我們擁有高可用性;例如,如果一個特定的數據存儲有困難,其他函數將繼續正常工作。它還允許我們根據這些維度中的任何一個,獨立地自動縮放工作者類型。我們最終能夠以水平擴展的方式管理工人的能力,也就是說,如果我們有更多的某種類型的工作,我們可以擴大更多的工人。

從這裏開始,您可能會發現語言或框架的選擇很重要。最終,一個更有效率的工人將能夠做更多的工作,更快。像C或Rust這樣的編譯語言在處理計算任務時往往比Ruby這樣的解釋語言要快得多,這可能會使某些工作負載的工作人員更加高效。然而,我花了大量的時間觀察跟蹤,原始的CPU處理在Braze的大局中隻占很小的一部分。我們的大部分處理時間都花在等待來自數據存儲或外部請求的響應上,而不是處理數據;我們不需要大量優化的C代碼。

的數據存儲

到目前為止,我們介紹的所有內容都是可擴展的。所以,讓我們花一分鍾來談談我們的員工把大部分時間花在哪裏——數據存儲。

任何曾經使用SQL數據庫進行web服務器或異步工作的人都可能遇到一個特定的規模問題:事務。您可能有一個端點負責完成一個Order,它創建兩個FulfillmentRequests和一個PaymentReceipt。如果這一切沒有在事務中發生,您可能會得到不一致的數據。在一個數據庫上同時執行多個事務可能導致在鎖上花費大量時間,甚至死鎖。在Braze,我們通過對象獨立性和最終的一致性,直麵數據模型本身的伸縮問題。有了這些原則,我們可以從數據存儲中擠出很多性能。

獨立的數據對象

我們在Braze大量利用MongoDB,原因非常充分:也就是說,它使我們能夠大幅水平擴展MongoDB碎片,並獲得近乎線性的存儲和性能增長。這對於我們的用戶配置文件非常有效,因為它們彼此獨立——在用戶配置文件之間不需要維護JOIN語句或約束關係。隨著每個客戶的增長,或者隨著我manbetx万博全站客户端們增加了新客戶(或兩者都有),我們可以簡單地向現有數據庫添加新數據庫和新碎片,以增加我們的容量。我們明確地避免了像多文檔事務這樣的特性,以保持這種級別的可伸縮性。

除了MongoDB,我們經常使用Redis作為臨時數據存儲,例如緩衝分析信息。因為這些分析的真相來源在一段時間內作為獨立的文檔存在於MongoDB中,我們維護了一個水平可擴展的Redis實例池作為緩衝區;在這種方法下,在基於密鑰的分片方案中使用散列文檔ID,由於獨立性,可以均勻地分配負載。周期性作業將這些緩衝區從一個水平伸縮的數據存儲刷新到另一個水平伸縮的數據存儲。規模達到了!

此外,我們對這些實例使用Redis Sentinel,就像我們對上述作業隊列所做的一樣。我們還為不同的目的部署了許多“類型”的Redis集群,為我們提供了一個可控的故障流程(即,如果一個特定類型的Redis集群有問題,我們不會看到不相關的功能同時出現故障)。

最終一致性

Braze還將最終一致性作為大多數讀操作的原則。這允許我們在大多數情況下利用MongoDB副本集的主成員和從成員的讀取,使我們的體係結構更有效。我們的數據模型中的這一原則允許我們在整個堆棧中大量利用緩存。

我們使用一種使用Memcached的多層方法——基本上,當從數據庫請求文檔時,我們首先檢查一個生存時間(TTL)非常低的機器本地Memcached進程,然後檢查一個遠程Memcached實例(具有較高的TTL),然後再直接請求數據庫。這幫助我們大幅減少了對常見文檔(如客戶設置或活動細節)的數據庫讀取。“最終”可能聽起來很可怕,但實際上,隻有幾秒鍾,采用這種方法會減少大量來自真相來源的流量。如果您曾經上過計算機架構課,您可能會認識到這種方法與cpu L1、L2和L3緩存係統的工作方式是多麼相似!

通過這些技巧,我們可以從架構中最慢的部分擠出很多性能,然後在吞吐量或容量需求增加時適當地水平擴展它。

Ruby和Rails適合做什麼

事情是這樣的:事實證明,當您花費大量精力構建一個每個層水平伸縮良好的整體架構時,語言或運行時的速度就沒有您想象的那麼重要了。這意味著對語言、框架和運行時的選擇是基於完全不同的需求和約束集做出的。

2011年Braze成立時,Ruby和Rails在幫助團隊快速迭代方麵已經有了良好的記錄,現在GitHub、Shopify和其他領先品牌仍在使用它們,因為它們繼續使這成為可能。它們繼續分別由Ruby和Rails社區積極開發,而且它們都有大量的開源庫可以滿足各種需求。對於快速迭代來說,這對組合是一個很好的選擇,因為它們具有巨大的靈活性,並且為常用用例保持了大量的簡單性。我們發現這是絕對正確的每一天我們使用它。

現在,這並不是說Ruby on Rails是一個適合所有人的完美解決方案。但在Braze,我們發現它可以很好地為我們的大部分數據攝取管道、消息發送管道和麵向客戶的儀表板提供動力,所有這些都需要快速迭代,並且是Braze平台整體成功的關鍵。

當我們不使用Ruby時

但是等等!並非我們在Braze所做的一切都是在Ruby中進行的。在過去的幾年中,由於各種各樣的原因,我們在一些地方呼籲將事情轉向其他語言和技術。讓我們看一下其中的三個,以便提供一些關於什麼時候依賴Ruby和什麼時候不依賴Ruby的額外見解。

1.發送方服務

事實證明,Ruby並不擅長在單個進程中處理高度並發的網絡請求。這是一個問題,因為當Braze代表我們的客戶發送消息時,一些終端服務提供商可能要求每個用戶有一個請求。manbetx万博全站客户端當我們有一堆準備發送的100條消息時,我們不希望等待每一條消息都完成後再繼續發送下一條消息。我們更願意同時做所有的工作。

進入我們的“發送者服務”——即用Golang編寫的無狀態微服務。上麵示例中的Ruby代碼可以將所有100條消息發送到其中一個服務,該服務將並行執行所有請求,等待它們完成,然後向Ruby返回一個批量響應。在並發聯網方麵,這些服務比我們用Ruby所做的工作效率要高得多。

2.電流連接器

我們的釺焊電流大容量數據導出功能允許Braze客戶不斷流數manbetx万博全站客户端據到我們的眾多數據合作夥伴中的一個或多個。該平台由Apache Kafka提供支持,流通過Kafka Connectors完成。從技術上講,您可以用Ruby編寫這些代碼,但官方支持的方式是使用Java。而且由於Java的高度支持,用Java編寫這些連接器要比用Ruby容易得多。

3.機器學習

如果你曾經做過機器學習方麵的工作,你就會知道選擇的語言是Python。Python中大量用於機器學習工作負載的包和工具使Ruby的支持相形見絀——像TensorFlow和Jupyter筆記本這樣的東西對我們的團隊很有幫助,而這些類型的工具在Ruby世界中根本不存在或沒有很好地建立起來。因此,在構建利用機器學習的產品元素時,我們傾向於使用Python。

當語言問題

顯然,在上麵的一些很好的例子中,Ruby並不是理想的選擇。為什麼你可能會選擇一種不同的語言,原因有很多,這裏有一些我們認為特別有用的考慮。

在不改變成本的情況下創造新事物

如果您打算構建一個全新的係統,使用新的領域模型,並且沒有與現有功能緊密耦合的集成,那麼如果您願意,您可能有機會使用不同的語言。特別是在您的組織正在評估不同機會的情況下,一個較小的、孤立的新項目可能是嚐試新語言或框架的一個很好的現實實驗。

任務特定語言生態係統與人體工程學

有些任務用特定的語言或框架會容易得多——我們特別喜歡Rails和Grape來開發儀表板功能,但是用Ruby編寫機器學習代碼絕對是一場噩夢,因為開源工具根本不存在。您可能希望使用特定的框架或庫來實現某種功能或集成,有時您的語言選擇將受到這些因素的影響,因為它幾乎肯定會帶來更容易或更快的開發體驗。

執行速度

有時,您需要優化原始執行速度,而所使用的語言將嚴重影響這一點。很多高頻交易平台和自動駕駛係統都是用c++編寫的,這是有原因的;本地編譯的代碼可以非常快!我們的Sender Services利用了Golang的並行/並發原語,正是因為這個原因,這些原語在Ruby中是不可用的。

開發人員熟悉

另一方麵,您可能正在構建一些孤立的東西,或者在腦海中有一個想要使用的庫,但您的語言選擇對團隊的其他成員來說完全陌生。在Scala中引入一個非常傾向於函數式編程的新項目,可能會給團隊中的其他開發人員帶來熟悉障礙,這最終將導致知識隔離或淨速度下降。我們發現這在Braze尤其重要,因為我們非常強調快速迭代,所以我們傾向於鼓勵使用在組織中已經廣泛使用的工具、庫、框架和語言。

最終的想法

如果我能回到過去,告訴自己關於大型係統中的軟件工程的一件事,那就是:對於大多數工作負載,您的整體架構選擇將比語言選擇更能定義您的擴展限製和速度。這種洞察力每天都在Braze得到證實。

Ruby和Rails是不可思議的工具,當體係結構正確時,它們是係統的一部分,可擴展性非常好。Rails也是一個非常成熟的框架,它支持我們在Braze快速迭代和產生真正客戶價值的文化。這些使得Ruby和Rails成為我們理想的工具,我們計劃在未來幾年繼續使用這些工具。

有興趣在Braze工作嗎?我們的工程、產品管理和用戶體驗團隊正在招聘不同的職位。查看我們的職業頁麵了解我們的開放角色和我們的文化。


紮克麥考密克

紮克麥考密克

紮克是紐約市的一名軟件工程師。他的愛好包括解決大型分布式係統的bug,然後在博客上發表相關文章,彈電吉他,以及慢慢學習匈牙利語。

相關內容

黑客日的故事:Braze產品工程經理德裏克·舒爾茨如何解決廣告抄襲挑戰

閱讀更多

Braze是如何走向國際化的

閱讀更多

開發人員

黑客日的故事:Braze高級軟件工程師Hal Anil計算行使期權的稅收影響

閱讀更多

開發人員

黑客日的故事:Braze高級軟件工程師John Parsons如何在他的第一個黑客日幫助建造火炬感恩機器人

閱讀更多