《設計消息中間件時我關心什么?(解密電商數據一致性與完整性實現,含PPT)》要點:
本文介紹了設計消息中間件時我關心什么?(解密電商數據一致性與完整性實現,含PPT),希望對您有用。如果有疑問,可以聯系我們。
導讀:應對高可用及極端峰值,每個技術團隊都有自己的優秀經驗,但是這些方法遠沒有得到體系化的討論.高可用架構在 6 月 25 日舉辦了『高壓下的架構演進』專題活動,進行了閉門私董會研討及對外開放的四個專題的演講,期望能促進業界對應對峰值的方法及工具的討論,本文是去哪兒網余昭輝介紹設計電商消息中間件的設計經驗.
作者:余昭輝——去哪兒網 基礎架構部架構師,2011 年加入去哪兒網,經歷過去哪兒網從小到壯大,服務拆分的過程.現負責去哪兒網基礎架構部,參與設計和開發去哪兒大大小小各種基礎組件.本人對互聯網電商中間件,并發和異步編程尤為感興趣,是一個自我標榜 Clean Coder.熱衷于與同行技術交流.
我來自去哪兒的基礎架構部,我們部門負責公司的公共組件和基礎服務,包括敏感信息存儲、發號器、身份證認證、監控中心、任務調度、Redis 等.
今天主要給大家分享一下消息隊列基礎組件的設計.
我們是 2012 年初開始自研消息隊列和消息中間件的,當時也是契合公司背景,原來公司有一些龐大的單模塊系統,如機票交易系統和酒店交易系統等.為了對系統進行拆分,面臨著系統拆分之后事務處理的問題,于是自研了消息中間件.
(小編:上文滴滴passport設計之道:帳號體系高可用的7條經驗也多次提到了大系統做小的經驗與實踐,感興趣的讀者可以參閱)
現在去哪兒網基本所有交易環節都通過消息的方式流轉,使用了一種消息驅動的架構,像訂單的流轉、支付等,消息中間件已經成為核心基礎設施,對交易系統非常關鍵.最初設計消息中間件是為了滿足交易場景,后來大家覺得 API 使用非常方便,現在其他業務包括部分搜索等等功能也切到這個上面來.
截止目前,除了部分搜索場景是 AMQ,公司其他業務的都使用了自研的消息中間件,一些基本數據如下:
承載公司 1 萬多個 subject
平均接收消息量 QPS 12 萬+
峰值 QPS 50 萬
最多的一個消息 subject 有 180 個消費組來消費
消息中間件模型說簡單也很簡單,最小單元就是一條消息,所以伸縮、擴展非常容易,只要根據消息進行 hash.當中間件承受不住壓力的候,擴展是非常簡單的.另外一方面說它復雜也很復雜,消息中間件作為一個公司的基礎組件,如果它出問題就是一個很嚴重的事情.消息中間件出一次故障,就是 6 ~ 7 個部門報 P1 故障.
這便是它的復雜所在,如何保證它的正常運行,那么在介紹內容之前,先說明一下上下文:今天講的僅僅是適用交易環節的消息中間件,跟通常所說的社交領域的消息中間件有很大的不同.
在交易環節,需要考慮 3 個方面 :
不能丟消息.丟消息意味著掉單,意味著支付成功但是沒給人家出票,這是不能接受的.
穩定.消息中間件一旦出問題,交易不能進行,也是嚴重的故障.
性能.
在電商的場景前面兩條要高于性能要求,也是今天要重點討論的部分.
典型的消息中間件包含 3 部分 :producer(發布者)、broker(消息中間件)、consumer(消費者),是一個比較簡單的模型,下圖展示了把消息發送給 consumer 的全過程.?
Producer 消息發布端主要關注一致性、容災、性能.
分布式事務一致性的難題
上圖是一個訂票的訂單服務,生成的新訂單.如果訂單持久化成功(上面的紅框 ),消息發送失敗( 下面的紅框 ).那么用戶看到下單成功,假設代理商服務訂閱這個消息是否給用戶出票,那么現在的情況就是票沒出來,這會引起用戶投訴.但是如果先發消息,然后再持久化訂單,那可能就是訂單出票了,但其實這個訂單還沒下呢,這就會造成公司的損失.
這種一致性問題怎么解決?
大家可能會想到分布式事務,比如 2PC(Two phase commit).業內已經有很多文章介紹了分布式事務的利弊,它的成本還是比較高,在此不做討論,下面主要介紹電商系統中應用比較多的另外一種方法.
數據庫的一致性
先來看一下數據庫中的事務
在一個 DB 實例中,比如 3306 這個,使用同一個連接,對多個庫的操作是可以放在同一個事務里的.這樣是可以保證數據的一致性,這是由數據庫決定.比如圖中的業務 DB1,業務 DB2 和一個消息 DB 都在同一個實例里,是可以放在同一個事務里的.
業務數據的一致性保證
有了數據庫這層保證,就可以用這種方式來實現業務操作和消息發送了.
先將訂單持久化在同一個事務里,共享訂單操作的數據庫連接,在這個連接里將消息持久化到同一個實例里的消息庫里,然后在事務提交之后將消息發送到消息的 server.如果事務回滾了,消息就不會發送出去了.
詳細看一下流程圖 :
開啟事務.
業務操作,比如說訂單進行持久化等等動作.
生成消息,并存儲,這和業務操作是在同一個事務里.
事務提交.
消息真正發到出去.消息如果發送成功了,會將消息表里的消息刪除,而此時如果消息發送失敗了,后臺有一個任務會把消息表里面發送失敗的消息重新進行發送,這樣最終達到一致性,保證業務操作成功了,消息一定能發出去.
(小編:更多了解分布式事務的實現,可參看文末推薦閱讀的文章)
現在看看這種模型的優缺點.這種模型 API 非常簡單,業務開發只需要使用 sendMessage 這個簡單的 API,不需要關心事務等.同時運維也非常簡單,我們的做法是公司的 DBA 給所有 DB 實例上預初始化一個消息庫,業務根本不用關心,對業務完全是透明,API 把這些封裝在底下,使用起來還是非常簡單. 但是這樣有另外一個問題,就是存儲成本.本來只有業務操作訪問 DB,然后還要持久化消息.原來承受一個 QPS 現在可能只能承受一半了,所以對數據庫操作還是略重一些
另外,有的場景中,可能不僅做數據庫操作,還調用了 RPC.這樣的動作是不能放在一個數據庫事務里的,所以對于這種場景就不能滿足了.現在遇到這種情況只有把 RPC 這種操作拆出去了.所以這種模型的優點就是使用方便,但是有些限制.
還有另外一種實現一致性的方法.
發新的消息,直接發給 broker,這個消息發給 broker 并不立即將消息投遞出去.
做本地的操作
再調 broker 的接口,這一步真正把消息發送出去.如果這個時候,即使第二步操作成功,第三步發送失敗了,第一步發送給 broker 的消息就是一個未決狀態.
broker 反過來詢問 producer,那條消息是發還是不發出去呢?這種模型就不需要一個將一個消息庫放在業務庫同實例了,比較靈活,成本也更低些.但是業務使用的復雜度可能要高一些,需要提供一個接口供 broker 反查.
容錯
講完了一致性,再來看看容錯.broker 會有不同的集群,producer 發消息有一個優先級,默認消息優先發到本機房集群,本機房宕掉或者其他什么原因不可用再向別的機房進行發送.本機房出故障自動不向本機房發送,自動熔斷故障機房.后臺系統里面可以按照 subject 指定路由到特定的 broker 集群.
Broker 消息中間件設計
消息中間件要支持非常多的 subject,全公司都在使用消息中間件,各業務開發水平也參差不齊,如果有的系統弄了一個死循環,瘋狂的發消息就會給系統帶來不可控的壓力,所以中間層需要做好隔離.其次,業務使用消息中間件可能會遇到各種各樣的問題,需要輔助工具進行診斷.最后還需要全面的監控能力.?
隔離
隔離包括配額和調度.Producer 給 broker 發消息的時候,每一個 subject 需要給它多少配額,QPS 一旦高于這個配額,做什么處理?
這個地方我們也踩了一個小坑.假設給每個 subject 3000 QPS 限制,最初 producer 端的設計沒有考慮配額這種情況,配額生效之后,達到 3000 QPS broker 開始拒絕消息,也就是返回異常. Producer 一般的設計遇到這種異常時候就不斷地重試,這種一拒絕 producer 就不斷地重試,雪上加霜,帶寬都要打滿了.
公司業務部門比較多,因為不能要求所有 producer 都立即配合升級,于是我們做了改進,producer 達到 QPS 不是立即拒絕發送過來的消息,而是拖一會兒,這樣來避免將中間層拖垮.當然最好的方式是 producer 進行配合,當 broker 超配額,producer 降低發送速率.
還有一種情況,我們有很多個 subject,有 180 個 consumer group 進行訂閱,如果 QPS 達到 100,就是 1.8 萬,如果 QPS 達到 1000,就是 18 萬,180 倍的增長,所以怎么與其它 subject 進行隔離很重要.?
我們第一版做的很簡單,用線程池隔離,每個 subject 分配一個線程池,這種做法隔離效果是很好,但是 subject 不斷地增長,資源就不夠用了.這個問題抽象來看就像操作系統的 Scheduler 線程調度器,每一個隊列可以想象成 OS 里的線程,然后系統用一些來發送這些隊列,這些線程就對應 CPU 的 core.我們就模仿 Linux 的 Scheduler 實現了個調度,可能實現水平關系,但是最后測試發現效果很差,量一大起來,隊列就堵住了,完全發不出去消息.
最后我們看 actor 這種模型,系統里可以跑成千上萬的 actor,它肯定也有一個調度器,最后就模仿 akka 的調度器,它叫 dispatcher,實現了調度的策略.
可治理
像剛才配額都是可以動態調整,不能消息量突然上來了,重啟消息系統來調整.還有消息可靠級別,當消息上線時,我們要關心消息 QPS 能達到,能容忍多少丟失?如果這個時候消息中間件出問題了,我們就可以根據上線時可靠級別給有的消息降級.
降級也有很多種策略,比如僅僅給投遞一次,如果中間件出問題,這種消息就投遞一次算了,不管你是否消費成功,都不給你重發.還有重發次數,比如有的消費者那邊有問題,消費不成功,比如消費格式變了,重發一天也消費不了,就可以實時的去調整消息的重發次數.還有可以按多少比例給它發消息,比如說 50%,那么 50% 的消息就給拋棄.
還有日志,為了好查問題,每條消息都有軌跡日志,出問題的時候就可以有選擇的是否保存這些日志了.一個公司不可能所有消息都要求 100% 可靠.比如訂單支付的消息級別是最高的,但是搜索,比如說現在有報價,代理商幫旗下所有的酒店價格變了一下,關心它的系統就要受到價格的更新,這是通過消息廣播出去的.這個消息,它的變動是比較頻繁,QPS 也很高,丟了一兩條消息,可能又被后面的消息覆蓋了,它的可靠級別就比訂單的級別要低,這個時候遇到問題,為了保護訂單消息,肯定首先對它進行降級.
輔助工具
消息的發送投遞軌跡可視化,消息回溯、消息補發等等.發一條消息過來,這條消息什么時候接收到?什么時候進行投遞,投遞到哪些消費者是要有可視化,這樣便于用戶查找問題.
消息回溯.比如消費者把消息的內容理解錯了,幾號到幾號之間所有的消息都要進行重新發送,這樣就要回溯這段時間的消息.
消息補發.漏發的消息,把消息重新補發一下,用戶可以上傳一個文件,將這些文件里的內容解析成消息然后發送.
顯示消息的發送和消費的關系.系統發出了哪些 topic 的消息,系統訂閱了哪些 topic 的消息,有哪些消費有哪些訂閱了,這些都非常重要.
監控
監控分為兩塊.一個是指標監控,比如像 QPS 監控,耗時等.可以細化到 subject、consumer 等粒度.第二個是鏈路監控、全鏈路跟蹤,這是另外一個產品 QTracer 做的,可以根據消息 id 來查這個消息所關聯的鏈路,來看看這個消息的情況.
上下線控制
上下線的策略,如果消費端應用還沒有啟動成功的時候,消息就已經過來,這是不能接受的.我們使用了一個和 nginx 差不多的方法,利用一個 healthcheck.html,如果有這個文件,就把消費者上線.發布系統將應用發布之后,會檢查應用是否 ready,ready 之后就會 touch 一下這個文件,然后 consumer 就上線了.另外就是可以手動屏蔽消費端,比如一個消費組有多臺機器,可以屏蔽其中幾臺.
冪等
冪等分兩種實現.有的業務是可以處理冪等的,借助消息里的業務字段然后根據業務場景.但有的業務可能不太好實現冪等,我們的客戶單默認提供了冪等的措施,比如基于 Redis、MySQL 等.
關于順序
消息中間件是不嚴格保證順序的,只是盡量保證有序,一般情況下先發送的消息先到,但并不做出這種承諾.要保證順序對實現方式和成本都是不小的挑戰.
使用方一般怎么來保證順序呢?
一種是狀態機.涉及交易的系統一般都有狀態機.比如訂單流轉,假設現在訂單狀態是待支付,業務收到支付成功的消息,訂單就流轉成支付成功,這個時候收到了訂單完成或者出票成功這樣的消息,這個消息不是對應的當前狀態,都會進行拒絕,拒絕后消息的 server 稍后會重發.?
另外一種方法是 producer 在消息里面攜帶一個版本號.Consumer 收到以后會和自己當前的版本號進行比較,接到消息的版本號如果小于數據庫的版本號,這個消息就不消費了,直接吞掉,這里要注意,這樣的消息就不是拒絕了.
Q & A
Q:消息入庫本地庫,后臺應用掃描數據庫重發消息,會不會導致消息重發?
余昭輝:有幾層保證.首先,消息一旦發送成功,就把消息給刪除了.而后臺應用掃描也是掃描指定時間之前的消息,但這可能還是不能完全杜絕重發,比如在刪除之前,被掃描到了,就會導致這個問題.我們在 server 端會根據消息的 id 進行一個去重,不過去重也是有個限制的,也就是只保證多長時間內的消息不重復,而不是永久.如果有的業務覺得這還不夠,就要自己去實現冪等了.
Q:看你們實現了跨機房,如果中間件在兩個機房,其中一個機房出問題,會不會有影響,機房做容災?
余昭輝:這個是沒做的,如果一個機房出現不可恢復的故障,需要人工進行恢復.消息收到之后,首先落本地庫,還會保存一份到 HBase.你剛才說機房宕掉的,那些存儲把消息恢復回來,沒有做自動容災.
Q:冪等需要業務上做本地支持?
余昭輝:對.如果業務完全不能接受重復消息,就必須實現冪等.
Q:消息隊列是基于 Kafka 嗎?如果自研底層是怎么樣實現的
余昭輝:這塊是自己研發實現的,消息存儲直接用 MySQL,開發語言用 Java,去哪兒網主流的語言就是 Java,公司里其他語言很少.關于存儲的分區,因為模型非常簡單,分區就是用消息的 id,hash 進行分區.
Q:broker 做服務中心,如果 broker 重啟,消息持久化的情況怎么處理??
余昭輝:brocker 進行重啟,先參看最開始的那個模型.消息發到 broker,broker 如果回成功了,說明消息一定落地了,只有落地成功了,broker 才會回成功.返回成功,producer 就可以把本地消息刪除掉.如果你發一條消息正好碰到服務重啟,存儲沒落地,broker 肯定不會回消息,消息就在本地庫里面,稍后,后臺應用又會把消息掃出來重發.
Q:從第一版到現在有沒有對架構方面的考量或者重新調整設計??
余昭輝:架構上面變動不多,主要是里面細節在不斷調整.比如說最初的時候,網絡這一塊直接用的是一個 RPC 框架,沒有自己實現網絡傳輸.在 2013 年,碰到一些問題,因此又把網絡這塊完全重寫了.比如上文提到隊列隔離調度的地方,也是重新設計了.
本文相關 PPT 鏈接如下
http://pan.baidu.com/s/1eRMW4zK
文章出處:高可用架構
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/4485.html