歷經8年雙11流量洗禮,淘寶開放平臺如何攻克技術難關?

淘寶開放平臺(open.taobao.com)是阿里系統與外部系統通訊的最重要平臺,每天承載百億級的API調用,百億級的消息推送,十億級的數據同步,經歷了8年雙11成倍流量增長的洗禮。本文將爲您揭開淘寶開放平臺的高性能API網關、高可靠消息服務、零漏單數據同步的技術內幕。

高性能API網關

阿里巴巴內部的數據分佈在各個獨立的業務系統中,如:商品中心、交易平臺、用戶中心,各個獨立系統間通過HSF(High-speed Service Framework)進行數據交換。如何將這些數據安全可控的開放給外部商家和ISV,共建繁榮電商數據生態,在這個背景下API網關誕生。

總體架構

API網關採用管道設計模式,處理業務、安全、服務路由和調用等邏輯。爲了滿足雙11高併發請求(近百萬的峯值QPS)下的應用場景,網關在架構上做了一些針對性的優化:

元數據讀取採用富客戶端多級緩存架構,並異步刷新緩存過期數據,該架構能支持千萬級QPS請求,並能良好的控制機房網絡擁塞。

同步調用受限於線程數量,而線程資源寶貴,在API網關這類高併發應用場景下,一定比例的API超時就會讓所有調用的RT升高,異步化的引入徹底的隔離API之間的影響。網關在Servlet線程在進行完API調用前置校驗後,使用HSF或HTTP NIO client發起遠程服務調用,並結束和回收到該線程。待HSF或者HTTP請求得到響應後,以事件驅動的方式將遠程調用響應結果和API請求上下文信息,提交到TOP工作線程池,由TOP工作線程完成後續的數據處理。最後使用Jetty Continuation特性喚起請求將響應結果輸出給ISV,實現請求的全異步化處理。線程模型如圖所示。

多級緩存富客戶端

在API調用鏈路中會依賴對元數據的獲取,比如需要獲取API的流控信息、字段等級、類目信息、APP的密鑰、IP白名單、權限包信息,用戶授權信息等等。在雙11場景下,元數據獲取QPS高達上千萬,如何優化元數據獲取的性能是API網關的關鍵點。

千萬級QPS全部打到DB是不可取的,儘管DB有做分庫分表處理,所以我們在DB前面加了一層分佈式緩存;然而千萬級QPS需要近百臺緩存服務器,爲了節約緩存服務器開銷以及減少過多的網絡請求,我們在分佈式緩存前面加了一層LRU規則的本地緩存;爲了防止緩存被擊穿,我們在本地緩存前面加了一層BloomFilter。一套基於漏斗模型的元數據讀取架構產生。緩存控制中心可以動態推送緩存規則,如數據是否進行緩存、緩存時長、本地緩存大小。爲了解決緩存數據過期時在極端情況下可能出現的併發請求問題,網關會容忍拿到過期的元數據(多數情況對數據時效性要求不高),並提交異步任務更新數據信息。

高性能批量API調用

在雙11高併發的場景下,對商家和ISV的系統同樣是一個考驗,如何提高ISV請求API的性能,降低請求RT和網絡消耗同樣是一個重要的事情。在ISV開發的系統中通常存在這樣的邏輯單元,需要調用多個API才能完成某項業務,在這種串行調用模式下RT較長同時多次調用發送較多重複的報文導致網絡消耗過多,在弱網環境下表現更加明顯。

API網關提供批量API調用模式緩解ISV在調用RT過高和網絡消耗上的痛點。ISV發起的批量請求會在TOP SDK進行合併,併發送到指定的網關;網關接收到請求後在單線程模式下進行公共邏輯計算,計算通過後將調用安裝API維度拆分,並分別發起異步化遠程調用,至此該線程結束並被回收;每個子API的遠程請求結果返回時會拿到一個線程進行私有邏輯處理,處理結束時會將處理結果緩存並將完成計數器加一;最後完成處理的線程,會將結果進行排序合併和輸出。

多維度流量控制

TOP API網關暴露在互聯網環境,日調用量達幾百億。特別是在雙11場景中,API調用基數大、調用者衆多以及各個API的服務能力不一致,爲了保證各個API能夠穩定提供服務,不會被暴漲的請求流量擊垮,那麼多維度流量控制是API網關的一個重要環節。API網關提供一系列通用的流量控制規則,如API每秒流控、API單日調用量控制、APPKEY單日調用量控制等。

在雙11場景中,也會有一些特殊的流量控制場景,比如單個API提供的能力有限,例如只能提供20萬QPS的能力而實際的調用需求可能會有40萬QPS。在這種場景下怎麼去做好流量分配,保證核心業務調用不被限流。

TOP API網關提供了流量分組的策略,比如我們可以把20萬QPS的能力分爲3個組別,並可以動態去配置和調整每個組別的比例,如:分組1佔比50%、如分組2佔比40%、分組3佔比10%。我們將核心重要的調用放到分組1,將實時性要求高的調用放到分組2,將一些實時性要求不高的調用放到分組3。通過該模式我們能夠讓一些核心或者實時性要求高的調用能夠較高概率通過流量限制獲取到相應的數據。同時TOP API網關是一個插件化的網關,我們可以編寫流控插件並動態部署到網關,在流控插件中我們可以獲取到調用上下文信息,通過Groovy腳本或簡單表達式編寫自定義流控規則,以滿足雙11場景中豐富的流控場景。

使用集羣流控還是單機流控?單機流控的優勢是系統開銷較小,但是存在如下短板:

集羣單機流量分配不均。

單日流控計數器在某臺服務器掛掉或者重啓時比較難處理。

API QPS限制小於網關集羣機器數量時,單機流控無法配置。

基於這些問題,API網關最開始統一使用集羣流控方案,但在雙11前壓測中發現如下一些問題:

單KEY熱點問題,當單KEY QPS超過幾十萬時,單臺緩存服務器RT明顯增加。

緩存集羣QPS達到數百萬時,服務器投入較高。

針對第一個問題的解法是,將緩存KEY進行分片可將請求離散多臺緩存服務器。針對第二個問題,API網關採取了單機+集羣流控相結合的解決方案,對於高QPS API流控採取單機流控方案,服務端使用Google ConcurrentLinkedHashMap緩存計數器,在併發安全的前提下保持了較高的性能,同時能做到LRU策略淘汰過期數據。

高可靠消息服務

有了API網關,服務商可以很方便獲取淘係數據,但是如何實時獲取數據呢?輪詢 !數據的實時性依賴於應用輪詢間隔時間,這種模式,API調用效率低且浪費機器資源。基於這樣的場景,開放平臺推出了消息服務技術,提供一個實時的、可靠的、異步雙向數據交換通道,大大提高API調用效率。目前,整個系統日均處理百億級消息,可支撐百萬級瞬時流量,如絲般順滑。

總體架構

消息系統從部署上分爲三個子系統,路由系統、存儲系統以及推送系統。消息數據先存儲再推送,保證每條消息至少推送一次。寫入與推送分離,發送方不同步等待接收方應答,客戶端的任何異常不會影響發送方系統的穩定性。系統模塊交互如圖所示。

路由系統,各個處理模塊管道化,擴展性強。系統監聽主站的交易、商品、物流等變更事件,針對不同業務進行消息過濾、鑑權、轉換、存儲、日誌打點等。系統運行過程記錄各個消息的處理狀況,通過日誌採集器輸出給JStorm分析集羣處理並記錄消息軌跡,做到每條消息有跡可循。

存儲系統,主要用於削峯填谷,基於BitCask存儲結構和內存映射文件,磁盤完全順序寫入,速度極佳。數據讀取基於FileRegion零拷貝技術,減少內存拷貝消耗,數據讀取速度極快。存儲系統部署在多個機房,有一定容災能力。

推送系統,基於Disputor構建事件驅動模型,使用Netty作爲網絡層框架,構建海量連接模型,根據連接吞吐量智能控制流量,降低慢連接對系統的壓力;使用WebSocket構建 長連接通道,延時更低;使用對象池技術,有效降低系統GC頻率;從消息的觸發,到拉取,到發送,到確認,整個過程完全異步,性能極佳。

選擇推送還是拉取

在消息系統中,一般有兩種消費模式:服務端推送和客戶端拉取。本系統主要面向公網的服務器,採用推送模式,有如下優點 :

實時性高。從消息的產生到推送,總體平均延時100毫秒,最大不超過200毫秒。

服務器壓力小。相比於拉取模式,每次推送都有數據,避免空輪詢消耗資源。

使用簡便。使用拉取模式,客戶端需要維護消費隊列的位置,以及處理多客戶端同時消費的併發問題。而在推送模式中,這些事情全部由服務器完成,客戶端僅需要啓動SDK監聽消息即可,幾乎沒有使用門檻。

當然,系統也支持客戶端拉取,推送系統會將客戶端的拉取請求轉換爲推送請求,直接返回。推送服務器會據此請求推送相應數據到客戶端。即拉取異步化,如果客戶端沒有新產生的數據,不會返回任何數據,減少客戶端的網絡消耗。

如何保證低延時推送

在採用推送模式的分佈式消息系統中,最核心的指標之一就是推送延時。各個長連接位於不同的推送機器上,那麼當消息產生時,該連接所在的機器如何快速感知這個事件?

在本系統中,所有推送機器彼此連接(如圖所示),構成一個通知網,其中任意一臺機器感知到消息產生事件後,會迅速通知此消息歸屬的長連接的推送機器,進而將數據快速推送給客戶端。而路由系統每收到一條消息,都會通知下游推送系統。上下游系統協調一致,確保消息一觸即達。

如何快速確認消息

評估消息系統另外一個核心指標是消息丟失問題。由於面向廣大開發者,因此係統必須兼顧各種各樣的網絡環境問題,開發者能力問題等。爲了保證不丟任何一條消息,針對每條推送的消息,都會開啓一個事務,從推送開始,到確認結束,如果超時未確認就會重發這條消息,這就是消息確認。

由於公網環境複雜,消息超時時間註定不能太短,如果是內網環境,5秒足矣,消息事務在內存就能完成。然後在公網環境中,5秒遠遠不夠,因此需要持久化消息事務。在推送量不大的時候,可以使用數據庫記錄每條消息的發送記錄,使用起來也簡單方便。但是當每秒推送量在百萬級的時候,使用數據庫記錄的方式就顯得捉襟見肘,即便是分庫分表也難以承受如此大的流量。

對於消息推送事務數據,有一個明顯特徵,99%的數據會在幾秒內讀寫各一次,兩次操作完成這條數據就失去了意義。在這種場景,使用數據庫本身就不合理,就像是在數據庫中插入一條幾乎不會去讀的數據。這樣沒意義的數據放在數據庫中,不僅資源浪費,也造成數據庫成爲系統瓶頸。

如上圖所示,針對這種場景,本系統在存儲子系統使用HeapMemory、DirectMemory、FileSystem三級存儲結構。爲了保護存儲系統內存使用情況,HeapMemory存儲最近10秒發送記錄,其餘的數據會異步寫入內存映射文件中,並寫入磁盤。HeapMemory基於時間維度劃分成三個HashMap,隨着時鐘滴答可無鎖切換,DirectMemory基於消息隊列和時間維度劃分成多個鏈表,形成鏈表環,最新數據寫入指針頭鏈表,末端指針指向的是已經超時的事務所在鏈表。這裏,基於消息隊列維護,可以有效隔離各個隊列之間的影響;基於時間分片不僅能控制鏈表長度,也便於掃描超時的事務。

在這種模式下,95%的消息事務會在HeapMemory內完成,5%的消息會在DirectMemory完成,極少的消息會涉及磁盤讀寫,絕大部分消息事務均在內存完成,節省大量服務器資源。

零漏單數據同步

我們已經有了API網關以及可靠的消息服務,但是對外提供服務時,用戶在訂單數據獲取中常常因爲經驗不足和代碼缺陷導致延遲和漏單的現象,於是我們對外提供數據同步的服務。

傳統的數據同步技術一般是基於數據庫的主備複製完成的。在簡單的業務場景下這種方法是可行的,並且已經很多數據庫都自帶了同步工具。 但是在業務複雜度較高或者數據是對外同步的場景下,傳統的數據同步工具就很難滿足靈活性、安全性的要求了,基於數據的同步技術無法契合複雜的業務場景。

雙11場景下,數據同步的流量是平常的數十倍,在峯值期間是百倍,而數據同步機器資源不可能逐年成倍增加。保證數據同步寫入的平穩的關鍵在於流量調控及變更合併。

分佈式數據一致性保證

在數據同步服務中,我們使用了消息 + 對賬任務雙重保障機制,消息保障數據同步的實時性,對賬任務保障數據同步一致性。以訂單數據同步爲例,訂單在創建及變更過程中都會產生該訂單的消息,消息中夾帶着訂單號。接受到該消息後,對短時間內同一訂單的消息做合併,數據同步客戶端會拿消息中的訂單號請求訂單詳情,然後寫入DB。消息處理過程保證了訂單在創建或者發生了任意變更之後都能在極短的延遲下更新到用戶的DB中。

對賬任務調度體系會同步運行。初始化時每個用戶都會生成一個或同步任務,每個任務具有自己的唯一ID。數據同步客戶端存活時每30秒發出一次心跳數據,針對同一分組任務的機器的心跳信息將會進行彙總排序,排序結果一般使用IP順序。每臺客戶端在獲取需執行的同步任務列表時,將會根據自身機器在存活機器總和x中的順序y,取得任務ID % x = y - 1的任務列表作爲當前客戶端的執行任務。執行同步任務時,會從訂單中心取出在過去一段時間內發生過變更的訂單列表及變更時間,並與用戶DB中的訂單進行一一對比,如果發現訂單不存在或者與存儲的訂單變更時間不一致,則對DB中的數據進行更新。

資源動態調配與隔離

在雙11場景下如何保證數據同步的高可用,資源調配是重點。最先面臨的問題是,如果每臺機器都是冪等的對應全體用戶,那麼光是這些用戶身後的DB連接數消耗就是很大問題;其次,在淘寶的生態下,賣家用戶存在熱點,一個熱點賣家的訂單量可能會是一個普通賣家的數萬倍,如果用戶之間直接共享機器資源,那麼大流量用戶將會佔用幾乎全部的機器資源,小流量用戶的數據同步實效會受到很大的影響。

爲了解決以上問題,我們引入了分組隔離。數據同步機器自身是一個超大集羣,在此之上,我們將機器和用戶進行了邏輯集羣的劃分,同一邏輯集羣的機器只服務同一個邏輯集羣的用戶。在劃分邏輯集羣時,我們將熱點用戶從用戶池中取出,劃分到一批熱點用戶專屬集羣中。分組隔離解決了DB連接數的問題,在此場景下固定的用戶只會有固定的一批機器爲他服務,只需要對這批機器分配連接數即可,而另一個好處是,我們可以進行指定邏輯集羣的資源傾斜保障大促場景下重點用戶的數據同步體驗。

數據同步服務大集羣的機器來源於三個機房, 在劃分邏輯集羣時,每個邏輯分組集羣都是至少由兩個以上機房的機器組成,在單個機房宕機的場景下,邏輯集羣還會有存活機器,此時消息和任務都會向存活的機器列表進行重新分配,保證該邏輯集羣所服務的用戶不受影響。 在機器發生宕機或者單個邏輯集羣的壓力增大時,調度程序將會檢測到這一情況並且對冗餘及空閒機器再次進行邏輯集羣劃分,以保證數據同步的正常運行。在集羣壓力降低或宕機機器恢復一段時間後,調度程序會自動將二次劃分的機器回收,或用於其他壓力較大的集羣。

通用數據存儲模型

訂單上存儲的數據結構隨着業務的發展也在頻繁的發生的變化,進行訂單數據的同步,需要在上游結構發生變化時,避免對數據同步服務產生影響,同時兼顧用戶的讀取需求。對此我們設計了應對結構易變數據的大字段存儲模型。在訂單數據的存儲模型中,我們將訂單號、賣家暱稱、更新時間等需要被當做查詢/索引條件的字段抽出獨立字段存儲,將整個的訂單數據結構當成json串存入一個大字段中。

這樣的好處是通過大字段存儲做到對上游業務的變化無感知,同時,爲了在進行增量數據同步時避免對大字段中的訂單詳情進行對比,在進行數據同步寫入的同時將當前數據的hashcode記錄存儲,這樣就將訂單數據對比轉換成了hashcode與modified時間對比,提高了更新效率。

如何降低數據寫入開銷

在雙11場景下,數據同步的瓶頸一般不在淘寶內部服務,而在外部用戶的DB性能上。數據同步是以消息的方式保證實時性。在處理非創建消息的時候,我們會使用直接update + modified時間判斷的更新方式,替換傳統的先select進行判斷之後再進行update的做法。這一優化降低了90%的DB訪問量。

傳統寫法:

SELECT * FROM jdp_tb_trade WHERE tid = #tid#;

UPDATE jdp_tb_trade SET jdp_response = #jdpResponse#, jdp_modified = now() WHERE tid = #tid#

優化寫法:

UPDATE jdp_tb_trade SET jdp_response = #jdpResponse#, jdp_modified = now() WHERE tid = #tid# AND modified #modified#

訂單數據存在明顯的時間段分佈不均的現象,在白天訂單成交量較高,對DB的訪問量增大,此時不適合做頻繁的刪除。採用邏輯刪除的方式批量更新失效數據,在晚上零點後交易低峯的時候再批量對數據錯峯刪除,可以有效提升數據同步體驗。

原文地址:http://chuansong.me/n/1588392851627rn . 作者|風勝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章