search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

微信開源PhxQueue:高可用、高可靠、高性能的分散式隊列

作者| 梁俊傑 編輯| 小智 PhxQueue 是微信開源的一款基於 Paxos 協議實現的高可用、高吞吐和高可靠的分散式隊列,保證 At-Least-Once Delivery,在微信內部廣泛支持微信支付、公眾平台等多個重要業務。

開源地址消息隊列概述

消息隊列作為成熟的非同步通信模式,對比常用的同步通信模式,有如下優勢:

  • 解耦:防止引入過多的 API 給系統的穩定性帶來風險;調用方使用不當會給被調用方系統造成壓力,被調用方處理不當會降低調用方系統的響應能力。

  • 削峰和流控:消息生產者不會堵塞,突發消息緩存在隊列中,消費者按照實際能力讀取消息。

  • 復用:一次發布多方訂閱。

PhxQueue 誕生背景舊隊列

微信初期使用的分散式隊列(稱為舊隊列)是微信後台自研的重要組件,廣泛應用在各種業務場景中,為業務提供解耦、緩存、非同步化等能力。

舊隊列以 Quorum NRW 作為同步機制,其中 N=3、W=R=2,刷盤方式採用非同步刷盤,兼顧了性能和可用性。

新需求

隨著業務發展,接入業務種類日益增多,舊隊列逐漸顯得力不從心,主要不足如下:

非同步刷盤,數據可靠性堪憂

對於支付相關業務,保證數據可靠是首要需求。 目前大多數分散式隊列方案是以同步複製 + 非同步刷盤來保證數據可靠性的,但我們認為需要同步刷盤來進一步提高數據可靠性。

亂序問題

部分業務提出了絕對有序的需求,但 NRW 並不保證順序性,無法滿足需求。

另外舊隊列還存在出隊去重、負載均衡等其他方面的問題亟需改善。上述種種促使了我們考慮新的方案。

業界方案的不足

Kafka 是大數據領域常用的消息隊列,最初由 LinkedIn 採用 Scala 語言開發,用作 LinkedIn 的活動流追蹤和運營系統數據處理管道的基礎。

其高吞吐、自動容災、出入隊有序等特性,吸引了眾多公司使用,在數據採集、傳輸場景中發揮著重要作用,詳見 Powerd By Kafka。

但我們充分調研了 Kafka,認為其在注重數據可靠性的場景下,有如下不足:

Kafka 性能與同步刷盤的矛盾

Kafka 在開啟配置 log.flush.interval.messages=1,打開同步刷盤特性后,吞吐會急劇下降。該現象由如下因素導致:

SSD 寫放大

業務消息平均大小在數 1k 左右。 而 SSD 一次刷盤的最小單位為一個 page size,大小為 4k。 當 Kafka 對大小不足 4k 的消息進行刷盤時,實際寫入的物理數據量是消息大小的數倍。導致硬碟寫帶寬資源被浪費。

業務場景下 Producer batch 效果不好

Kafka Producer batch,簡單來說,就是把多個消息打包在一起發送到 Broker,廣泛用於大數據場景。按道理,batch 效果足夠,是能抵消寫放大的影響的。 但業務場景下的消息生產不同於大數據場景下的日誌生產,每個需要入隊的業務請求在業務系統中有獨立的上下文,batch 難度大。即使在業務和 Broker 之間加入代理層,將 Producer 轉移到代理層內進行 batch,也因代理層的節點數眾多,batch 效果難以提高,導致寫放大無法抵消。

Kafka replica 同步設計上的不足

Kafka replica 同步設計概要:

Kafka Broker leader 會跟蹤與其保持同步的 follower 列表,該列表稱為 ISR(即 in-sync Replica)。如果一個 follower 宕機,或者落後太多,leader 將把它從 ISR 中移除。

該同步方式偏重於同步效率,但是在可用性方面表現略顯不足:

Broker fail over 過程成功率下降嚴重

在 3 replicas 的場景下,leader 均勻分佈在各 Broker 上,一個 Broker 出現故障,就意味著 1/3 的 leader、follower 離線,這時讀寫成功率下降:

  • 對於 leader 離線的 partition,暫時無法讀寫,需要等待 Controller 選舉出新的 leader 后才能恢復;

  • 對於 follower 離線的 partition,也暫時無法讀寫,需要等待一定時長(取決於 replica.lag.time.max.ms,默認 10s)后,leader 將故障 follower 從 ISR 中剔除才能恢復。

也就是說,任意一個 Broker 故障時,讀寫成功率會在一段時間內降為 0。

同步延遲取決於最慢節點

在同步複製場景下,需要等待所有節點返回 ack。

通過對比 Kafka replica 與 Paxos 的表現,我們認為在同步方式上 Paxos 是更好的選擇:

所以,我們基於舊隊列,用 Paxos 協議改造了同步邏輯,並且進行了包括同步刷盤之內的多項優化,完成了 PhxQueue。

PhxQueue 介紹

PhxQueue 目前在微信內部廣泛支持微信支付、公眾平台等多個重要業務,日均入隊達千億,分鐘入隊峰值達一億。

其設計出發點是高數據可靠性,且不失高可用和高吞吐,同時支持多種常見隊列特性。

PhxQueue 支持的特性如下:

  • 同步刷盤,入隊數據絕對不丟,自帶內部實時對賬

  • 出入隊嚴格有序

  • 多訂閱

  • 出隊限速

  • 出隊重放

  • 所有模塊均可平行擴展

  • 存儲層批量刷盤、同步,保證高吞吐

  • 存儲層支持同城多中心部署

  • 存儲層自動容災 / 接入均衡

  • 消費者自動容災 / 負載均衡

PhxQueue 設計整體架構

PhxQueue 由下列 5 個模塊組成。

Store - 隊列存儲

Store 作為隊列存儲,引入了 PhxPaxos 庫,以 Paxos 協議作副本同步。只要多數派節點正常工作及互聯,即可提供線性一致性讀寫服務。

為了提高數據可靠性,同步刷盤作為默認開啟特性,且性能不亞於非同步刷盤。

在可用性方面,Store 內有多個獨立的 paxos group,每個 paxos group 僅 master 提供讀寫服務,平時 master 動態均勻分佈在 Store 內各節點,均衡接入壓力,節點出災時自動切換 master 到其它可用節點。

Producer - 生產者

Producer 作為消息生產者,根據 key 決定消息存儲路由。相同 key 的消息默認路由到同一個隊列中,保證出隊順序與入隊順序一致。

Consumer - 消費者

Consumer 作為消費者,以批量拉取的方式從 Store 拉消息,支持多協程方式批量處理消息。

Consumer 以服務框架的形式提供服務,使用者以實現回調的方式,根據不同主題(Topic),不同處理類型(Handler)定義具體的消息處理邏輯。

Scheduler - 消費者管理器(可選擇部署)

Scheduler 的作用是,收集 Consumer 全局負載信息, 對 Consumer 做容災和負載均衡。當使用者沒有這方面的需求時,可以省略部署 Scheduler,此時各 Consumer 根據配置權重決定與隊列的處理關係。

部署 Scheduler 后,Scheduler leader 與所有 Conusmer 維持心跳,在收集 Consumer 的負載信息的同時,反向調整 Consumer 與隊列的處理關係。

當 Scheduler leader 宕機了后,Scheduler 依賴下述分散式鎖服務選舉出新 leader,不可用期間僅影響 Consumer 的容災和負載均衡,不影響 Consumer 的正常消費。

Lock - 分散式鎖(可選擇部署)

Lock 是一個分散式鎖,其介面設計非常通用化,使用者可以選擇將 Lock 獨立部署,提供通用分散式鎖服務。

Lock 在 PhxQueue 中的作用有如下兩點:

  • 為 Scheduler 選舉 leader;

  • 防止多個 Consumer 同時處理一條隊列。

Lock 同樣也是可選擇部署的模塊:

  • 若部署了 Scheduler,就必須部署 Lock 為 Scheduler 選舉出 leader;

  • 否則,若業務對重複消費不敏感,可選擇不部署 Lock。

這裡所指的重複消費場景是:若省略部署 Scheduler 的話,Consumer 需要通過讀取配置得知可處理的隊列集合;當隊列有變更(如隊列縮擴容)時,各 Consumer 機器上的配置改變有先有后,這時各 Consumer 在同一時間看到的配置狀態可能不一樣,導致一段時間內兩個 Consumer 都認為自己該消費同一個隊列,造成重複消費。Lock 的部署可以避免該場景下的重複消費。(注意,即使省略部署 Lock,該場景僅造成重複消費,而不會造成亂序消費)

Store 複製流程

PhxQueue Store 通過 PhxPaxos 協議進行副本複製。

PhxPaxos 的工程實現方式分為三層:app 層負責處理業務請求,paxos 層執行 paxos 同步過程,狀態機層更新業務狀態。

其中,app 層發起 paxos 提議,paxos 層各節點通過 paxos 協議共同完成一個 paxos log 的確認,之後狀態機以 paxos log 作為的輸入作狀態轉移,更新業務的狀態,最後返回狀態轉移結果給 app 層。一致的狀態機層,加上來自 paxos 層的一致輸入,就產生一致的狀態轉移,從而保證多個節點強一致。

這裡我們要基於 PhxPaxos 在狀態機層實現一個隊列,就需要作如下概念映射:

  • 隊列這種模型不涉及數據修改,是有序的數據集合,和 paxos log 的定義很像,所以可以讓入隊的數據直接作為 paxos log,而狀態機只需要保存 paxos log 序列。

  • instance id 的嚴格遞增特性,使得它可以方便地作為隊列偏移。

  • 隊列中讀偏移之前的數據,認為是可以刪除的數據,這點和 check point 的定義一致。

整體上隊列狀態機和 paxos 能很好地切合。

Store Group Commit - 高效刷盤及副本同步

未經優化的 Paxos 協議並未解決同步刷盤的寫放大問題。而且,其副本同步效率不如 Kafka。

原因是,Kafka 的副本同步是流式批量的,而 Paxos 協議是以 paxos log 為單位串列同步,每個 paxos log 的同步開銷是 1 個 RTT + 1 次刷盤。

在多 DC 部署的場景下,ping 時延可達 4ms,這樣會導致單個 paxos group 的理論最高 TPS 僅 250。

我們採用多 paxos group 部署 以及 Group Commit 的方式來同時解決同步刷盤的寫放大問題以及 Paxos 吞吐問題。

如上圖, 我們部署多個 paxos group,以 paxos group 作為 Group Commit 的單位,一個 paxos group 內對應多個 queue,將多個 queue 在一段時間內入隊的數據合併在一起,當等待耗時或積累數據數目達到閥值,才會觸發一次 Paxos 同步和同步刷盤,等待期間前端阻塞。

與 Kafka 的 Producer 批量邏輯相比,在存儲層以 Group Commit 進行批量合併的好處如下:

  • 業務層無需關注如何組織請求進行批量;

  • 在存儲層以 paxos group 為單位的聚合效果比上層聚合效果更好。

PhxQueue 與 Kafka 對比

下面分別從設計、性能、存儲層 failover 過程三方面對比 PhxQueue 與 Kafka。

設計對比

PhxQueue 架構雖然與 Kafka 等常見分散式隊列類似,但設計上仍有不少獨特之處。為了能讓對 Kafka 有一定了解的讀者更方便地了解 PhxQueue,下面列出了兩者的對比。

註:以下對比基於相同的數據可靠性場景:少數派節點失效,不會造成數據丟失,且整體依舊可用。

性能對比

測試環境

測試基準及配置

測試結果

開啟 Producer Batch:

關閉 Producer Batch:

以上場景,PhxQueue 瓶頸在 cpu,使用率達 70% ~ 80%。

小結

  • PhxQueue 性能與 Kafka 持平;

  • 相同 QPS 下,由於不用等最慢節點返回,PhxQueue 平均耗時比 Kafka 稍優;

  • 關閉 Producer Batch 后,在同步刷盤場景下,PhxQueue 性能可達 Kafka 的 2 倍,原因是,PhxQueue 存儲層在寫盤前做了 batch,而 Kafka 沒有,所以後者會有寫放大。

存儲層 failover 過程對比

主要對比殺死存儲層的一個節點后,對整體吞吐的影響。

Kafka

表現:

  • Failover 期間,在不同階段程度不同,入隊成功率在 0% ~ 33%;

  • Failover 持續時間由租約決定,租約時長默認 10s。

測試過程:

將 replica.lag.time.max.ms 從 10s 調整為 60s(延長時間方便觀察),然後 kill Broker 0,挑選 3 個 partition,觀察 ISR 變化如下:

其中,第二 / 三階段入隊成功率受損:

  • 第二階段期間,Partition 96/97/98 均無法寫入,入隊成功率成功率下降至 0%。

  • 第三階段期間,Partition 96 可繼續寫入,但 Partition 97/98 無法寫入,因為寫入要等 Broker 0 回 ack,但 Broker 0 已 kill,入隊成功率下降至 33%。

而實際觀察,第二 / 三階段期間完全沒吞吐,原因是壓測工具不斷報連接失敗,停止了寫入。

壓測工具輸出:

壓測工具連接 Broker 失敗日誌:

原因分析:

Kafka Broker leader 是通過 Controller 選舉出來的,ISR 列表是 leader 維護的。

前者的的租約是 Controller 定義的,後者的租約是 Broker 配置 replica.lag.time.max.ms 指定的。

所以,第二階段持續時間較短,是 Controller 的租約時間決定的,第三階段持續時間較長,是 replica.lag.time.max.ms 決定的。

當 Broker 0 被 kill 時,前者影響本來 Broker 0 是 leader 的 1/3 partitions 的入隊成功率,後者影響 Broker 0 作為 follower 的 2/3 partitions 的入隊成功率。

PhxQueue

表現:

  • Failover 期間,入隊成功率僅下降至 66%;

  • Failover 持續時間由租約決定,租約時長默認 5s。

  • 開啟 換隊列重試特性(適合沒有絕對順序性要求的業務提高可用性)后,Failover 期間仍有 90+% 入隊成功率。

測試過程:

將 Store master 租約時長從 10s 調整為 60s(延長時間方便觀察),然後 kill store 0,觀察某 Producer 入隊成功率:

關閉換隊列重試特性:

開啟換隊列重試特性:

小結

  • 在存儲層 failover 過程中,PhxQueue 和 Kafka 的入隊成功率均有一定時長的下降,PhxQueue 的入隊成功率在 66% ~ 100%,Kafka 的入隊成功率在 0% ~ 33%;

  • PhxQueue 開啟換隊列重試特性后,failover 過程中入隊成功率保持在 90+%;

  • PhxQueue 和 Kafka 均能自動切換 master,最終入隊成功率完全恢復。

寫在最後

PhxQueue 在存儲層做了很多的努力:實現了 master 自動切換,且仍然保證線性一致,切換期間仍然高可用;保證了同步刷盤的吞吐,其性能不亞於非同步刷盤。

另外實現了大部分隊列實用特性,例如出入隊順序一致、多訂閱、限速、消息重放等,適用於各種業務場景。

目前 PhxQueue 已在微信內部大規模使用,也正式開源。

我們將保持 PhxQueue 開源版本與內部版本的一致,歡迎讀者試用並反饋意見。

開源地址:

作者介紹

梁俊傑,微信高級工程師,目前負責微信消息系統、消息中間件等開發及優化。2011 年華南師範大學大學部畢業,曾參與和主導微博私信、反垃圾系統,以及微信多個系統架構優化項目。在過去一年多,作為 PhxQueue 主創成員之一,對微信分散式隊列進行重大架構改造,致力於提供高可用、高吞吐和高可靠的消息中間件服務。

分散式系統事務一致性解決方案大對比



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦