選擇正確的工具來滿足異步處理需求的技術(shù)指南
作為后端開發(fā)人員,有一天你需要回答這個(gè)問題:
我需要構(gòu)建一個(gè)使用分布式隊(duì)列的異步應(yīng)用程序,我應(yīng)該使用哪個(gè)代理? 作為工程師,我們的本能是列出我們了解或希望熟悉的工具(如果它是一種新的和已知的技術(shù)),然后開始使用它。不幸的是,在那個(gè)時(shí)刻,我們錯(cuò)過了第一個(gè)最重要的問題,這個(gè)問題需要在所有其他問題之前得到答案:我們的現(xiàn)有和有時(shí)未來的用例/需求是什么,什么工具能最好地解決它們? 這是我們?cè)谠O(shè)計(jì)一個(gè)重要功能時(shí)的起點(diǎn),當(dāng)時(shí)工程師的本能占了上風(fēng)。我們的第一個(gè)問題不是最重要的問題,從那時(shí)開始,我們選擇正確工具的過程變得不太有效。我們的團(tuán)隊(duì)開了幾次會(huì),討論了我們對(duì)分布式隊(duì)列的需求,不同技術(shù)的不同限制和特性(來自不同的范例)讓關(guān)注點(diǎn)遠(yuǎn)離了我們最重要的需求,也遠(yuǎn)離了決策和共識(shí)的實(shí)現(xiàn)。 在那個(gè)時(shí)候,我們決定回到基本問題,問:我們?cè)噲D解決的用例是什么,沒有讓步的余地在哪些領(lǐng)域?一如既往,讓我們從需求開始。
步驟1:明確您要解決的問題以及技術(shù)/工具體系結(jié)構(gòu)如何與您的目標(biāo)和考慮保持一致
在選擇消息代理或事件代理時(shí),有很多事情要考慮:高可用性、容錯(cuò)性、多租戶、多云區(qū)域支持、能夠支持高吞吐量和低延遲等等,列舉不勝枚舉。
大多數(shù)情況下,當(dāng)閱讀有關(guān)事件代理或消息代理的主要特性時(shí),我們都是以大多數(shù)公司或產(chǎn)品從未完全使用或需要的最復(fù)雜用例為例的。
作為工程師,生活中有一句常用的話語(yǔ):
上帝在細(xì)節(jié)中,但魔鬼隱藏在細(xì)節(jié)之間。
在選擇事件代理與消息代理兩個(gè)范式之間,"魔鬼"隱藏在更多的低層技術(shù)考慮因素中,比如:消息的消耗或生成確認(rèn)方法、去重、消息的優(yōu)先級(jí)、消費(fèi)者線程模型、消息的消耗方法、消息的分發(fā)/擴(kuò)散支持、毒藥藥處理等等。
概念之間的不同:橙子與蘋果
步驟2:了解兩種范式之間的差異
(1) 事件代理
存儲(chǔ)一系列事件。通常,事件會(huì)按到達(dá)事件代理的順序附加到日志(隊(duì)列或主題)上。主題或隊(duì)列中的事件是不可變的,其順序不能更改。
當(dāng)事件發(fā)布到隊(duì)列或主題時(shí),代理識(shí)別主題或隊(duì)列的訂閱者,并使事件可供多種類型的訂閱者使用。
生產(chǎn)者和消費(fèi)者不需要彼此熟悉。
事件潛在地可以存儲(chǔ)數(shù)天或數(shù)周,因?yàn)樗鼈円坏┍怀晒ο模筒粫?huì)從隊(duì)列/主題中刪除。
(2) 消息代理
用于服務(wù)或組件之間的通信。它通過異步方式在應(yīng)用程序之間傳輸由生產(chǎn)者接收的消息。
它通常支持隊(duì)列的概念,其中消息通常存儲(chǔ)一段時(shí)間。隊(duì)列中消息的目的是在消費(fèi)者可用于處理消息并在成功消耗后刪除消息。
不能保證隊(duì)列中消息處理的順序,并且可以更改。
消息代理與事件代理
通常,在處理短命令或面向任務(wù)的處理時(shí),我們會(huì)傾向于使用消息代理。
例如,假設(shè)你在一家電子商務(wù)公司工作,想要將新產(chǎn)品添加到公司的網(wǎng)站。這可能意味著多個(gè)服務(wù)需要知道并以異步方式處理此請(qǐng)求。
上圖顯示了RabbitMQ扇出消息分發(fā)的使用,其中每個(gè)服務(wù)都有自己的隊(duì)列連接到扇出交換機(jī)。
產(chǎn)品服務(wù)發(fā)送包含新產(chǎn)品信息的消息到交換機(jī),交換機(jī)將消息發(fā)送到所有連接的隊(duì)列。
在從隊(duì)列成功消耗消息后,它將被刪除,因?yàn)樯婕暗姆?wù)不需要保留或重新處理消息。
在處理當(dāng)前或歷史事件時(shí),通常涉及大量數(shù)據(jù),需要以單個(gè)或批量方式處理這些數(shù)據(jù),我們會(huì)傾向于使用事件代理。
例如,假設(shè)你在一個(gè)娛樂評(píng)級(jí)網(wǎng)站工作,你想為用戶添加一個(gè)新功能,用來顯示電影的編劇和導(dǎo)演。這些信息雖然歷史存儲(chǔ),但不對(duì)負(fù)責(zé)提供這些數(shù)據(jù)的服務(wù)可用。
上圖顯示了使用Kafka作為事件代理,它能夠從數(shù)據(jù)倉(cāng)庫(kù)中提取數(shù)億部電影,以為每個(gè)服務(wù)存儲(chǔ)的電影信息附加所需的信息。
Kafka可以在相對(duì)短的時(shí)間內(nèi)接受大量的數(shù)據(jù),而消費(fèi)者可以有一個(gè)獨(dú)立的消費(fèi)者組來單獨(dú)處理電影主題流。
需要注意的重要方面
正如我之前提到的,選擇合適的范式時(shí)有很多事情要考慮。
我想討論一些關(guān)鍵的差異,這些差異通??赡艹删突蚱茐哪鷮?duì)技術(shù)的決策。
在這一部分,我將比較迄今為止最流行的兩種技術(shù):Kafka(事件代理)和RabbitMQ(消息代理),它們分別代表了這兩個(gè)范式,我對(duì)它們都有實(shí)際經(jīng)驗(yàn)。
我強(qiáng)烈鼓勵(lì)您在技術(shù)選擇過程中考慮以下幾點(diǎn)。
1.輪詢與推送
Kafka消費(fèi)者的工作方式是通過輪詢一個(gè)主題中按順序分區(qū)劃分的消息的塊,每個(gè)消費(fèi)者負(fù)責(zé)從一個(gè)或多個(gè)分區(qū)中消費(fèi)消息,其中分區(qū)用作消費(fèi)者的并行機(jī)制(隱式線程模型)。
這意味著通常負(fù)責(zé)管理主題的生產(chǎn)者會(huì)隱式知道可以訂閱主題的消費(fèi)者實(shí)例的最大數(shù)量。
消費(fèi)者負(fù)責(zé)處理消息處理的成功和失敗情況。由于消息是從分區(qū)中批量輪詢的,所以消息處理順序在分區(qū)級(jí)別是有保證的。
RabbitMQ消費(fèi)者從隊(duì)列中接收消息的方式是通過代理將消息推送給它們。
每條消息都以一種獨(dú)立的方式進(jìn)行處理,消費(fèi)者可以采用顯式線程模型,而不需要生產(chǎn)者知道消費(fèi)者實(shí)例的數(shù)量。
成功的消息處理是消費(fèi)者的責(zé)任,而處理失敗主要由消息代理完成。
消息分發(fā)由代理進(jìn)行管理。
如延遲消息和消息優(yōu)先級(jí)等功能是開箱即用的,因?yàn)橄⑻幚眄樞蛟陉?duì)列中通常是不保證的。
2.錯(cuò)誤處理
Kafka處理消息處理錯(cuò)誤的方式是將處理錯(cuò)誤的責(zé)任委托給消費(fèi)者。
如果某條消息被處理了幾次但失?。ǘ舅幩帲?,消費(fèi)者應(yīng)用程序需要跟蹤處理嘗試的數(shù)量,然后生成一條消息到一個(gè)單獨(dú)的DLQ(死信隊(duì)列)主題,以便以后檢查/重新運(yùn)行。
就錯(cuò)誤處理而言,消費(fèi)者是承擔(dān)所有責(zé)任的一方。
這意味著如果您希望具有重試/DLQ功能,您需要提供重試機(jī)制,并在發(fā)送消息到DLQ主題時(shí)充當(dāng)生產(chǎn)者,這在某些極端情況下可能導(dǎo)致消息丟失。
RabbitMQ處理消息處理錯(cuò)誤的方式是跟蹤處理消息失敗。一旦一條消息被視為毒藥藥,它將被路由到一個(gè)DLQ交換機(jī)。
這允許重新排隊(duì)消息或?qū)⑵渎酚傻綄S肈LQ以進(jìn)行檢查。
通過這種方式,RabbitMQ提供了保證未成功處理的消息不會(huì)丟失。
3.消費(fèi)者確認(rèn)和傳遞保證
Kafka處理消費(fèi)者確認(rèn)的方式是由消費(fèi)者提交從主題分區(qū)中輪詢的消息的偏移量。
開箱即用,Kafka客戶端會(huì)自動(dòng)提交偏移量,無論消息是否成功處理,這可能導(dǎo)致消息丟失,如下圖所示。
通過消費(fèi)者代碼負(fù)責(zé)手動(dòng)提交獲取的消息的偏移量,包括處理消息消費(fèi)失敗的情況,可以更改此行為。
RabbitMQ處理消費(fèi)者確認(rèn)的方式是消費(fèi)者以每條消息的方式進(jìn)行“確認(rèn)”或“否認(rèn)”,允許由消息代理處理重試策略/DLQ,如果需要,可以由消息代理進(jìn)行管理。
開箱即用,RabbitMQ客戶端自動(dòng)進(jìn)行確認(rèn),無論消息是否成功處理,可以通過消費(fèi)者端的配置手動(dòng)控制確認(rèn),允許消息在失敗/超時(shí)時(shí)重新推送給消費(fèi)者。
RabbitMQ和Kafka在大多數(shù)情況下都提供至少一次消息/事件處理的保證,這意味著消費(fèi)者應(yīng)該是冪等的,以處理同一消息/事件的多次處理。
我們的流程
步驟 3:根據(jù)您的用例選擇技術(shù),而不是反過來
對(duì)我們來說,最重要的部分是編制我們解決方案的技術(shù)標(biāo)準(zhǔn)清單,并為我們作為團(tuán)隊(duì)和產(chǎn)品不能缺少的要求分配“不可接受”。
回到基礎(chǔ)精神,我使用了一個(gè)普通的表格來編制和比較不同的標(biāo)準(zhǔn),并提到了一些需要注意的地方。記住,“細(xì)節(jié)藏在細(xì)節(jié)之中”。
這真的幫助我們組織并集中精力關(guān)注對(duì)我們至關(guān)重要的內(nèi)容以及我們無法缺少的內(nèi)容。
例如,我們的“不可接受”要求之一是,如果在處理過程中發(fā)生錯(cuò)誤,我們不能承受丟失消息。
正如您可能從上面的部分中記得的,當(dāng)使用需要DLQ的Kafka時(shí),消費(fèi)者也是DLQ的生產(chǎn)者。這意味著在消費(fèi)者發(fā)生故障的某些情況下,消息將不會(huì)被發(fā)送到DLQ主題,可能導(dǎo)致消息丟失。
在這一點(diǎn)上,正如您可能已經(jīng)猜到的那樣,我們決定選擇消息代理。
我們的功能包括面向命令/任務(wù)的處理用例,消息代理滿足了我們的產(chǎn)品/數(shù)據(jù)容量要求,也滿足了我們團(tuán)隊(duì)的需求。
最后的思考
消息和事件流生態(tài)系統(tǒng)包括許多解決方案,每個(gè)解決方案都有許多不同的方面需要考慮和熟悉。
重要的是我們要睜大眼睛進(jìn)入每個(gè)生態(tài)系統(tǒng),并對(duì)這些不同的范式有清晰的理解。它們將對(duì)我們工程師的日常生活(有時(shí)是夜間生活)產(chǎn)生重大影響。