美國大選期間,Urban Airship如何將系統(tǒng)擴(kuò)展至發(fā)送25億個通知的規(guī)模?
譯文【51CTO.com快譯】近來,Urban Airship受到期望與移動技術(shù)一同增長的成千上萬家公司的信賴。Urban Airship是一家成立七年的SaaS公司,它采用了一種免費(fèi)增值的商業(yè)模式。
Urban Airship現(xiàn)在每天發(fā)送的推送通知平均超過10億個。本文介紹了2016年美國大選期間Urban Airship的通知使用情況,探討其Core Delivery Pipeline系統(tǒng)架構(gòu),該系統(tǒng)為新聞媒體發(fā)送了數(shù)十億的實(shí)時通知。
2016年美國大選
在選舉日前后的24小時內(nèi),Urban Airship發(fā)送了25億個通知――這是其迄今為止***日發(fā)送量。這相當(dāng)于美國每人收到8個通知或世界上每部活躍智能手機(jī)收到1個通知。雖然Urban Airship支持45000多個應(yīng)用程序,覆蓋每個行業(yè)垂直領(lǐng)域,但是分析選舉使用數(shù)據(jù)后顯示,400多個媒體應(yīng)用程序在這創(chuàng)記錄的發(fā)送量中占到了60%,大家在跟蹤報告選舉結(jié)果時,它在單單一天內(nèi)就發(fā)送了15億個通知。
總統(tǒng)選舉結(jié)束時,通知量趨于穩(wěn)定,并達(dá)到峰值。
Urban Airship API的HTTPS入站流量在選舉期間達(dá)到每秒近75K的峰值。大部分流量來自與Urban Airship API通信的Urban Airship軟件開發(fā)工具包(SDK)。
推送通知量一直在快速增長。最近的主要驅(qū)動因素是英國退歐、奧運(yùn)會和美國大選。2016年10月的月通知量同比猛增了150%。
Core Delivery Pipeline 架構(gòu)解密
Core Delivery Pipeline(CDP)是Urban Airship的核心系統(tǒng),負(fù)責(zé)利用audience selector工具來確定設(shè)備地址,并發(fā)送通知。其發(fā)送的所有通知都要做到低延遲,無論它們同時推送到數(shù)千萬用戶,發(fā)送給多個復(fù)雜的子群,含有個性化內(nèi)容,還是其他什么。下面概述了這套系統(tǒng)的架構(gòu)以及該公司汲取的若干經(jīng)驗(yàn)。
他們是如何開始入手的
2009 年,CDP最初只是一個Web應(yīng)用程序,一些worker模塊轉(zhuǎn)變成了面向服務(wù)的架構(gòu)(SOA)。由于舊系統(tǒng)的一些部分開始遇到規(guī)模問題后,我們將它們提取為一個或多個新服務(wù),這些服務(wù)旨在提供同樣的功能集,但是支持更大的規(guī)模,并擁有更好的性能。我們的許多原始API和worker是用Python編寫的,將它們提取為高并發(fā)的Java服務(wù)。最初,是將設(shè)備數(shù)據(jù)存儲在一組Postgres數(shù)據(jù)庫分片中,但公司規(guī)模的增長速度超過了添加新數(shù)據(jù)庫分片的能力,于是系統(tǒng)遷移到了使用HBase和Cassandra的多數(shù)據(jù)庫架構(gòu)。
CDP 是一系列負(fù)責(zé)處理分段和推送通知的服務(wù)。這些服務(wù)提供了響應(yīng)請求的同一種類型的數(shù)據(jù),但出于性能原因,每個服務(wù)都以全然不同的方式索引該數(shù)據(jù)。比如,我們有一個系統(tǒng)負(fù)責(zé)處理廣播消息,將同樣的通知內(nèi)容推送到已向相關(guān)應(yīng)用程序注冊的每個設(shè)備。該服務(wù)及底層的數(shù)據(jù)存儲區(qū)其設(shè)計(jì)方式與我們擁有的,負(fù)責(zé)基于位置或用戶概況屬性來發(fā)送通知的服務(wù)全然不同。
我們把任何長時間運(yùn)行的進(jìn)程都看作服務(wù)。這些長時間運(yùn)行的進(jìn)程密切關(guān)注一個通用模板,涉及度量指標(biāo)、配置和日志,以便易于部署和操作。通常,我們的服務(wù)屬于RPC服務(wù)或消費(fèi)者服務(wù)這兩個組中的一個。RPC服務(wù)提供這些使用內(nèi)部庫與服務(wù)同步交互的命令,非常類似GRPC,而消費(fèi)者服務(wù)處理來自Kafka數(shù)據(jù)流的消息,并對那些消息執(zhí)行針對特定服務(wù)的操作。
數(shù)據(jù)庫
為了滿足我們的性能和規(guī)模要求,我們高度依賴HBase和Cassandra以滿足數(shù)據(jù)存儲要求。雖然HBase和Cassandra都是列式NoSQL存儲庫系統(tǒng),但是它們有著全然不同的取舍,影響著我們使用哪種存儲系統(tǒng)、派什么用場。
HBase非常擅長高吞吐量掃描,響應(yīng)的預(yù)期基數(shù)(cardinality)非常高,而Cassandra擅長較低基數(shù)的查詢,預(yù)計(jì)響應(yīng)只含有少數(shù)結(jié)果。兩者都允許大量的寫入吞吐量,這是我們的一個要求,因?yàn)閬碜杂脩羰謾C(jī)的所有元數(shù)據(jù)更新都是實(shí)時的。
它們的故障特點(diǎn)也不一樣。一旦遇到失敗,HBase傾向于一致性和分區(qū)容錯性,而Cassandra傾向于可用性和分區(qū)容錯性。每個CDP服務(wù)都有非常具體的使用場合,因此有一種非常專用的數(shù)據(jù)庫模式(schema),旨在便于所需的訪問模式以及限制存儲占用空間。一般說來,每個數(shù)據(jù)庫僅由單個服務(wù)來訪問,該服務(wù)負(fù)責(zé)通過一個不大專用的接口,滿足數(shù)據(jù)庫訪問其他服務(wù)的要求。
服務(wù)及其后端數(shù)據(jù)庫之間實(shí)現(xiàn)這種1:1的關(guān)系有許多優(yōu)點(diǎn):
·通過將服務(wù)的后端數(shù)據(jù)存儲區(qū)當(dāng)作一個實(shí)現(xiàn)細(xì)節(jié),而不是共享資源,我們獲得了靈活性。
·我們只要改變服務(wù)的代碼,就可以調(diào)整該服務(wù)的數(shù)據(jù)模型。
·使用情況跟蹤起來更簡單直觀,因而容量規(guī)劃來得更容易。
·故障排除更容易。有時問題出在服務(wù)代碼上,有時問題出在后端數(shù)據(jù)庫上。讓服務(wù)和數(shù)據(jù)庫成為一個邏輯單元極大地簡化了故障排除過程。我們沒必要搞清楚“還有誰可以訪問這個數(shù)據(jù)庫,讓它以這種方式來運(yùn)行?”相反,我們可以依賴來自服務(wù)本身的應(yīng)用程序?qū)佣攘恐笜?biāo),只要為一組訪問模式操心。
·由于只有一個服務(wù)與數(shù)據(jù)庫交互,我們可以執(zhí)行幾乎所有的維護(hù)活動,而不會停機(jī)。繁重的維護(hù)任務(wù)成為服務(wù)級別問題:數(shù)據(jù)修復(fù)、數(shù)據(jù)庫模式遷移甚至改用完全不同的數(shù)據(jù)庫,這些都可以在不中斷服務(wù)的前提下執(zhí)行。
誠然,我們將應(yīng)用程序分解為較小的服務(wù)時,性能方面可能會有一些下降。然而我們發(fā)現(xiàn),我們可靈活地滿足高擴(kuò)展性和高可用性方面的要求,性能下降一點(diǎn)也是完全值得的。
數(shù)據(jù)建模
我們的服務(wù)大多數(shù)處理同樣的數(shù)據(jù),只是它們使用不同的格式。一切都要保持一致性。為了保持所有這些服務(wù)的數(shù)據(jù)更新,我們高度依賴Kafka。Kafka速度極快,也很牢靠。速度快帶來了某些缺點(diǎn)。只可保證Kafka消息至少發(fā)送一次,但不可保證它們按順序到達(dá)。
我們?nèi)绾翁幚磉@個問題呢?我們將所有可變路徑建模為可交換的:操作可以按任何順序來進(jìn)行,***獲得同樣的結(jié)果。它們也是冪等的。這有一個很好的附帶影響:我們可以重放Kafka數(shù)據(jù)流,進(jìn)行一次性的數(shù)據(jù)修復(fù)工作、回填甚至遷移。
為此,我們充分利用了“單元版本”概念,HBase和Cassandra中都有這個概念。它通常是一個時間戳,但也可以是你喜歡的任何數(shù)字(有一些例外;比如MAX_LONG會導(dǎo)致一些奇怪的行為,這取決于HBase或Cassandra的版本以及你的數(shù)據(jù)庫模式如何處理刪除)。
對我們來說,這些單元的一般規(guī)則是,它們可以有多個版本,我們按照提供的時間戳對版本進(jìn)行排序??紤]到這種行為,我們可以將入站消息分解為一組特定的列,并將該布局與自定義的應(yīng)用程序邏輯合并起來,用于邏輯刪除,并考慮到了時間戳。這樣就可以對底層數(shù)據(jù)存儲區(qū)進(jìn)行盲寫,同時保持?jǐn)?shù)據(jù)的完整性。
將變化的部分盲寫到Cassandra和HBase并非沒有問題。一個典型例子就是在重放的情況下,重復(fù)寫入同樣數(shù)據(jù)。雖然由于我們努力確保記錄具有冪等性,數(shù)據(jù)狀態(tài)因而不會改變,但是重復(fù)數(shù)據(jù)必須壓縮去掉。在最極端的情況下,這些額外記錄可能導(dǎo)致顯著的壓縮延遲和備份。由于這個細(xì)節(jié),我們密切關(guān)注壓縮時間和隊(duì)列深度,因?yàn)樵? Cassandra和HBase中,壓縮操作都會導(dǎo)致嚴(yán)重問題。
通過確保來自數(shù)據(jù)流的消息遵循一系列嚴(yán)格的規(guī)則,并且設(shè)計(jì)的消費(fèi)服務(wù)能夠處理無序和重復(fù)的消息,我們就可以確保大量的異步服務(wù)同步,更新時只有一兩秒的滯后。
服務(wù)設(shè)計(jì)
我們的大多數(shù)服務(wù)是用Java編寫的,但采用了一種非常獨(dú)特而現(xiàn)代的風(fēng)格。我們在設(shè)計(jì)Java服務(wù)時考慮到了一系列基本的指導(dǎo)原則:
·只做一件事,并且把它做好――設(shè)計(jì)服務(wù)時,它應(yīng)該只有一項(xiàng)責(zé)任。實(shí)施者決定這是什么樣的責(zé)任,但是她或他需要準(zhǔn)備好在代碼審查時予以說明。
·沒有共享操作狀態(tài)――設(shè)計(jì)服務(wù)時,假設(shè)總是至少有三個實(shí)例在運(yùn)行。服務(wù)需要能夠處理其他任何實(shí)例能夠處理的同一個請求,沒有任何外部協(xié)調(diào)。熟悉Kafka的那些人會特別指出,Kafka消費(fèi)者從外部協(xié)調(diào)topic:group對的分區(qū)所有權(quán)。這個指導(dǎo)原則面向針對特定服務(wù)的外部協(xié)調(diào),而不是利用可能從外部協(xié)調(diào)的庫或客戶端。
·限制隊(duì)列――我們在所有服務(wù)中使用隊(duì)列,這是分批處理請求的好方法。所有隊(duì)列都應(yīng)該有限制。不過,限制隊(duì)列確實(shí)帶來了許多問題:
△隊(duì)列滿時生產(chǎn)者會發(fā)生什么?它們會阻塞嗎?例外處理?還是丟棄?
△我的隊(duì)列應(yīng)該多大?要回答這個問題,假設(shè)隊(duì)列總是滿的有所幫助。
△如何干凈地關(guān)閉隊(duì)列?
△針對這些問題,每個服務(wù)會給出不同的答案,這取決于具體的使用場合。
·命名自定義線程池,并注冊UncaughtExceptionHandler――如果我們***創(chuàng)建自己的線程池,我們使用來自Executors的構(gòu)造函數(shù)或幫助方法,讓我們得以提供ThreadFactory。有了這個ThreadFactory,我們就可以正確命名線程,設(shè)置守護(hù)進(jìn)程狀態(tài),并注冊UncaughtExceptionHandler來處理異常讓它進(jìn)入到堆棧頂部的情況。這些措施將大大簡化服務(wù)的調(diào)試,并且讓我們不必在深夜備感沮喪。
·青睞不可變數(shù)據(jù)對象,而不是可變狀態(tài)――在高度并發(fā)環(huán)境中,可變狀態(tài)可能很危險。一般來說,我們使用可以在內(nèi)部子系統(tǒng)和隊(duì)列之間傳遞的不可變數(shù)據(jù)對象。讓不可變對象成為子系統(tǒng)之間的主要通信形式,這樣一來,為并發(fā)使用進(jìn)行設(shè)計(jì)要直觀簡單得多,還讓故障排除更容易。
下一步何去何從?
由于Urban Airship能夠通過移動錢包passes發(fā)送通知,新增對Web通知和蘋果新聞通知的支持,及其開放頻道能夠?qū)⑼ㄖl(fā)送到任何平臺、設(shè)備或營銷渠道,我們預(yù)計(jì)通知推送量會呈指數(shù)增長。為了滿足這種需求,我們繼續(xù)大力投資于Core Delivery Pipeline架構(gòu)、服務(wù)、數(shù)據(jù)庫和基礎(chǔ)設(shè)施。
原文標(biāo)題:How Urban Airship Scaled To 2.5 Billion Notifications During The U.S. Election,作者:Todd Hoff
【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請注明原文譯者和出處為51CTO.com】