Uber 如何大規模完成工作統計
介紹
優步的運營規模龐大,每個季度爲超過 22 億次出行提供便利。即使是簡單的見解,也需要一個規模化的解決方案。在我們的案例中,我們需要計算某人在 Uber 平臺上參與的工作數量,在任意時間窗口內。本文重點介紹在將 Apache Pinot™ 集成到我們的解決方案中時面臨的挑戰和吸取的經驗教訓。
背景
具體來說,我們的解決方案需要解決以下問題:
-
作業計數的幾種排列,按角色、市場和完整性軸細分
-
給定行程或給定時間戳的時間點任期(即,工作 X 按時間順序在人員 A 的工作歷史記錄中位於什麼位置?
我們之前的解決方案很簡單:檢索頁面大小限制爲 50 的給定主題的作業,然後對結果進行分頁,直到沒有其他作業。在 Uber 的早期,沒有一個賬戶可以累積相對較長的任期,這運作良好。然而,Uber 涉足新的垂直領域,一些賬戶開始出現數以萬計的任期,很明顯我們需要一個更強大的解決方案。
特定於旅行的任期是一個狹窄的用例
一個主要的產品要求是,該解決方案必須能夠計算任期回溯。這本身是站得住腳的,但伴隨着我們的數據保留政策,我們的下游團隊認爲適應是不合理的。
撤銷超過 2 年的數據
爲了節省成本,同一團隊確定將超過 2 年的數據隔離到延遲更高的存儲層中。然而,在項目中期計劃的變化導致他們完全放棄了對這些數據的在線訪問。
降低複雜性
我們當時的解決方案需要爲 Uber 的每個市場拼接三個數據源:Rides 和 Eats。
更廣泛的團隊中的類似用例
在向更廣泛的團隊展示我們的設計後,我們發現 Uber 的團隊都有類似的相鄰項目,每個項目都有類似的要求,每個項目都重新實施了自己的解決方案,以獨立計算工作任期。考慮到這些限制,我們考慮了幾種架構。我們認真考慮過的一個方案得到了 Apache Hive™ 和 Docstore(Uber 的內部分佈式數據庫)的支持,最終選擇了利用 Apache Pinot™ 的解決方案。這裏一個有趣的功能是混合表,它提供了一個無縫的界面,可以將實時和離線數據拼接在一起。
架構
發展挑戰
Apache Pinot™ 是一個非常強大的產品,爲我們的架構提供了無與倫比的靈活性。然而,我們在此過程中遇到了幾個障礙,下面,我們將詳細介紹挑戰和我們解決這些問題的方法,這可能會對讀者有所啓發。
挑戰 #1a – 容量規劃
我們面臨的第一個挑戰是制定容量要求,以配置我們的專用租戶所需的硬件。Apache Pinot™ 使用壓縮技術,因此很難先驗地測量磁盤上的空間。在這裏,我們選擇採用 10% 的樣本數據集,在我們的例子中,根據樣本佔用的空間預測磁盤使用情況就足夠了。
挑戰 #1b – 查詢性能
這裏一個有趣的是,雖然我們能夠通過採樣來估計磁盤上的數據集大小,但我們無法準確預測查詢性能。在這種情況下,我們必須等到擴展到整個數據集之後,才能獲得關鍵指標,例如 p99 讀取時間、磁盤讀取吞吐量等。一旦我們能夠做到這一點,生產規模的流量就會使我們的專用集羣陷入癱瘓,讀取時間超過 10 秒的代理限制,SSD 的讀取吞吐量和 CPU 使用率達到最大。我們立即着手研究優化,因爲這並不意外,因爲每個查詢都相當於加載到全表掃描。作爲參考,我們的查詢的 SQL 爲:
SELECT
*
FROM
pinot_hybrid_table
WHERE provider_id = ‘…’
AND requester_id = ‘…’
AND timestamp >= … AND timestamp <= …
這讓團隊感到擔憂,我們採取了幾個步驟來提高績效,包括:
-
按列排序 provider_id 這將同一提供商在同一天進行的所有行程進行分類,從而最大程度地減少每次查詢訪問的區段。如果沒有這一點,提供商在給定日期完成的所有工作都將平均分配給給定日期的所有部門。因此,如果一個提供者在同一天執行了多項工作,爲了讓代理完成我們的讀取查詢,它將迅速收斂到檢索提供者完成工作的每一天的所有段。
-
在 provider_id 和 requester_id 上添加倒掛索引 ** 我們還在 provider_id 列和 requester_id 列上啓用了倒掛索引。與排序列結合使用時,這將在 provider_id 列上提供排序的倒排索引。這允許 log(n) 的查找時間複雜度,因爲它執行二進制搜索以查找對應於給定 provider_id 值的行。一個令人驚訝的事實讓我們措手不及,那就是這些倒掛指數是_爲每個細分市場_創建的。這與傳統的 RDBMS 索引之間存在非常顯着的差異。此索引不是在所有段之間共享的全局結構,直接指向正在查詢的數據。在我們的例子中,代理仍必須對錶中每個段的一部分進行內存映射,以便使用索引。如果不實施額外的節段修剪技術,這是非常低效的,我們很快就實施了這些技術。
-
在 provider_id 和 requester_id 上添加布隆濾鏡 布隆過濾器是一種概率數據結構,用於測試元素是否爲集合的成員,並給出兩個答案:可能在集合中,或不在集合中。當爲列啓用時,Apache Pinot™ 會爲每個段創建一個 Bloom 過濾器,並允許代理在執行查詢時完全跳過段。如果段上的列存在 Bloom 過濾器,並且查詢中存在該列的相等謂詞,則代理能夠快速確定該記錄是否存在於段中。由於我們的數據集不能完全適應內存,我們選擇了 MMAP(內存映射)堆外配置,即段被延遲加載到內存中,如果操作系統的物理內存不足(如我們的情況),先前加載的段將被取消映射。但是,段的關聯 Bloom 過濾器可以存儲在堆上(內存中),並且將保留在那裏,即使其底層段不再位於物理內存中也是如此。觀察到的加速應與跳過的段數相關聯,因此它有利於讀取模式不需要獲取總段的大部分比例(例如全表掃描)的數據集。請參閱圖 3 和圖 4,在啓用 Bloom 過濾器後,可以觀察到 (numSegmentsQueried – numSegmentsProcessed) 的顯著下降。
-
每天增加的細分,爲了創建我們的 Apache Pinot™ 段,我們安排每天運行一個 Apache Spark™ 作業,創建並上傳組成離線表的新段。每個計劃間隔創建的區段數是可調整的,我們最初從每天四個區段開始。然而,隨着分段開始變得非常大(每個分段超過 4GB),我們逐漸增加到 8 個,然後是 16 個,最後每天增加到 32 個分段。這裏所做的權衡是,雖然增加段數會導致 Zookeeper 元數據存儲的負載增加,並增加 Apache Pinot™ 服務器的內存堆使用量,但較小的段大小會導致段更快地從磁盤讀取,並且成比例地將段上的更多數據包含在結果中。從經驗上看,我們觀察到 p99 讀取延遲顯著減少,並且代理 CPU 使用率沒有明顯增加。
-
向上遊使用者添加了跨數據中心緩存,雖然不是特定於 Apache Pinot™,但我們的主要上游消費者在暫存環境和生產環境之間執行相同的查詢。雖然我們預計查詢之間的時間延遲會最小,但我們認爲 30 分鐘的過時時間是可以接受的。
圖 3:通過 Pinot 查詢控制檯實施布隆過濾器之前和之後的查詢統計信息。
挑戰 #2 – 業務邊緣案例
隨着 Uber 繼續向平臺添加功能,它們對源數據的下游影響也在增加。目前,我們可以通過三種模式接收行程級別的信息:Apache Hive™、Apache Kafka™ Topic 和 API 響應,每種模式都有不同的架構。以合理的方式將新功能改造和表示到現有架構中可能很困難,尤其是 Apache Hive™ 架構。歷史數據具有很大的慣性,可能會使遷移架構變得不合理。例如,考慮 “票價分割” 功能,其中乘車費用可以在多個乘客之間分攤。在此功能之前,一項工作始終只有一個騎手,該騎手始終是付款人,並且每個 Hive 記錄都暗示司機在訂單上執行了一項工作。這些不變性不再成立。此處選擇複製記錄並將狀態設置爲 FARE_SPLIT,同時_將 driver_uuid_列設置爲 NULL。正是這樣的複雜性使得執行簡單的 _COUNT DISTINCT _聚合變得不可能。
挑戰 #3 – 慢數據
我們在這裏必須解決的另一個挑戰是上游數據的速度緩慢。雖然大多數數據在幾秒鐘內到達,但行程可能在長達一週的時間內不會顯示在上游數據源中。爲了解決這個問題,我們創建了一個管道,該管道動態生成回填管道,但只計劃在 T - 7d 和 T 之間運行。除此之外,我們還執行離線數據質量檢查,這是對 T - 1d 日期的簡單 count(*) 查詢,以確保我們的源 Apache Hive™ 表與 Apache Pinot™ 混合表的結果同步。
挑戰 #4 – 突發性上游負載
具有尖峯流量模式的上游流量也是一個問題。在這種情況下,特定的速率限制實現導致每 10 秒的流量激增,每次流量都違反了 10 秒的 Apache Pinot™ 代理超時,導致請求失敗。
我們通過在請求時向上遊客戶端添加抖動來解決這個問題,以便隨着時間的推移更均勻地分佈我們的查詢。在克服了這些挑戰之後,我們已經爲實時生產流量提供服務了近一年,執行負載測試顯示,在包含故障轉移流量緩衝區後,至少有 200% 的餘量。我們的 p99 讀取延遲爲 ~1 秒:令人印象深刻,因爲我們的一些上游查詢可能會達到 2,000 多個段,每個段消耗大約 90 MB 的磁盤空間。在迭代我們的解決方案後,我們開始發佈將我們之前的下游與我們的解決方案進行比較的指標。我們基於 Apache Pinot™ 的解決方案提供了幾乎 1:1 的準確度,讓我們有信心依賴它。我們首先使用 config 標誌對更改進行門控,一段時間後,完全切換並棄用了之前的實現。
結論
通過一些簡單的補充,我們有信心能夠回答更有力的問題。例如:
-
有人在過去 50 次旅行中多次在哪個城市旅行?
-
在 Uber 的人口中,誰是高任期?
在工作粒度上找到時間點任期的成本很高。在這方面,提高性能和降低存儲成本的途徑仍在探索之中。雖然仍處於設計過程中,但我們預計通過降低作業粒度要求,我們應該能夠顯著提高讀取吞吐量。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/p8ZF_hJ7xdX0pvzbfASV9g