<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官問: kafka 重試機(jī)制原理

          共 8658字,需瀏覽 18分鐘

           ·

          2021-10-19 06:20

          關(guān)注公眾號,回復(fù)“資料”,領(lǐng)取1000G資料。

          Apache Kafka 已成為跨微服務(wù)異步通信的主流平臺。它有很多強(qiáng)大的特性,讓我們能夠構(gòu)建健壯、有彈性的異步架構(gòu)。

          同時,我們在使用它的過程中也需要小心很多潛在的陷阱。如果未能提前發(fā)現(xiàn)可能發(fā)生(換句話說就是遲早會發(fā)生)的問題,我們就要面對一個容易出錯和損壞數(shù)據(jù)的系統(tǒng)了。

          在本文中,我們將重點介紹其中的一個陷阱:嘗試處理消息時遭遇失敗。首先,我們需要意識到消息消費可能會,而且遲早會遭遇失敗。其次,我們需要確保在處理此類故障時不會引入更多問題。

          Kafka 簡介

          閱讀本文的讀者應(yīng)該都對 Kafka 有所了解。網(wǎng)上也有一些介紹 Kafka 及其使用方法的深度文章。話雖如此,我們這里還是先簡要回顧一下對我們的討論很重要的一些概念。

          事件日志、發(fā)布者和消費者

          Kafka 是用來處理數(shù)據(jù)流的系統(tǒng)。從概念上講,我們可以認(rèn)為 Kafka 包含三個基本組件:

          • 一個事件日志(Event Log),消息會發(fā)布到它這里
          • 發(fā)布者(Publisher),將消息發(fā)布到事件日志
          • 消費者(Consumer),消費(也就是使用)事件日志中的消息
          187890dcf3dc0ca31afe7ca3d783d2fb.webp

          與 RabbitMQ 之類的傳統(tǒng)消息隊列不同,Kafka 由消費者來決定何時讀取消息(也就是說,Kafka 采用了拉取而非推送模式)。每條消息都有一個偏移量(offset),每個消費者都跟蹤(或提交)其最近消費消息的偏移量。這樣,消費者就可以通過這條消息的偏移量請求下一條消息。

          主題

          事件日志分為幾個主題(topic),每個主題都定義了要發(fā)布給它的消息類型。定義主題是我們這些工程師的責(zé)任,所以我們應(yīng)該記住一些經(jīng)驗法則:

          • 每個主題都應(yīng)描述一個其他服務(wù)可能需要了解的事件。
          • 每個主題都應(yīng)定義每條消息都將遵循的一個唯一模式(schema)。

          分區(qū)和分區(qū)鍵

          主題被進(jìn)一步細(xì)分為多個分區(qū)(partition)。分區(qū)使消息可以被并行消費。Kafka 允許通過一個**分區(qū)鍵(partition key)**來確定性地將消息分配給各個分區(qū)。分區(qū)鍵是一段數(shù)據(jù)(通常是消息本身的某些屬性,例如 ID),其上會應(yīng)用一個算法以確定分區(qū)。

          7667365e79a868dcfa977058432a596d.webp

          這里,我們將消息的 UUID 字段分配為分區(qū)鍵。生產(chǎn)者應(yīng)用一種算法(例如按照分區(qū)數(shù)修改每個 UUID 值)來將每條消息分配給一個分區(qū)。

          以這種方式使用分區(qū)鍵,使我們能夠確保與給定 ID 關(guān)聯(lián)的每條消息都會發(fā)布到單個分區(qū)上。

          還需要注意的是,可以將一個消費者的多個實例部署為一個消費者組。Kafka 將確保給定分區(qū)中的任何消息將始終由組中的同一消費者實例讀取。

          在微服務(wù)中使用 Kafka

          Kafka 非常強(qiáng)大。所以它可用于多種環(huán)境中,涵蓋眾多用例。在這里,我們將重點介紹微服務(wù)架構(gòu)中最常見的用法。

          跨有界上下文傳遞消息

          當(dāng)我們剛開始構(gòu)建微服務(wù)時,我們許多人一開始采用的是某種中心化模式。每條數(shù)據(jù)都有一個駐留的單一微服務(wù)(即單一真實來源)。如果其他任何微服務(wù)需要訪問這份數(shù)據(jù),它將發(fā)起一個同步調(diào)用以檢索它。

          這種方法導(dǎo)致了許多問題,包括同步調(diào)用鏈較長、單點故障、團(tuán)隊自主權(quán)下降等。

          最后我們找到了更好的辦法。在今天的成熟架構(gòu)中,我們將通信分為命令處理和事件處理。

          命令處理通常在單個有界上下文中執(zhí)行,并且往往還是會包含同步通信。

          另一方面,事件通常由一個有界上下文中的服務(wù)發(fā)出,并異步發(fā)布到 Kafka,以供其他有界上下文中的服務(wù)消費。

          7f263fc311497e388eccfd6d4a895b54.webp

          左側(cè)是我們以前設(shè)計微服務(wù)通信的方式:一個有界上下文(由虛線框表示)中的服務(wù)從其他有界上下文中的服務(wù)接收同步調(diào)用。右邊是我們?nèi)缃竦淖龇ǎ阂粋€有界上下文中的服務(wù)發(fā)布事件,其他有界上下文中的服務(wù)在自己空閑時消費它們。

          例如,以一個 User 有界上下文為例。我們的 User 團(tuán)隊會構(gòu)建負(fù)責(zé)啟用新用戶、更新現(xiàn)有用戶帳戶等任務(wù)的應(yīng)用程序和服務(wù)。

          創(chuàng)建或修改用戶帳戶后,UserAccount 服務(wù)會將一個相應(yīng)的事件發(fā)布到 Kafka。其他感興趣的有界上下文可以消費該事件,將其存儲在本地,使用其他數(shù)據(jù)增強(qiáng)它,等等。例如,我們的 Login 有界上下文可能想知道用戶的當(dāng)前名稱,以便在登錄時向他們致意。

          4c785f2ee63cc428ad3e5254537ea1f5.webp

          我們將這種用例稱為跨邊界事件發(fā)布。

          在執(zhí)行跨邊界事件發(fā)布時,我們應(yīng)該發(fā)布聚合(Aggregate)。聚合是自包含的實體組,每個實體都被視為一個單獨的原子實體。每個聚合都有一個“根”實體,以及一些提供附加數(shù)據(jù)的從屬實體。

          當(dāng)管理聚合的服務(wù)發(fā)布一條消息時,該消息的負(fù)載將是一個聚合的某種表示形式(例如 JSON 或 Avro)。重要的是,該服務(wù)將指定聚合的唯一標(biāo)識符作為分區(qū)鍵。這將確保對任何給定聚合實體的更改都將發(fā)布到同一分區(qū)。

          出問題的時候怎么辦?

          盡管 Kafka 的跨邊界事件發(fā)布機(jī)制顯得相當(dāng)優(yōu)雅,但畢竟這是一個分布式系統(tǒng),因此系統(tǒng)可能會有很多錯誤。我們將關(guān)注也許是最常見的惱人問題:消費者可能無法成功處理其消費的消息。

          316d8abb6e2fce739575cfd0bd993039.webp圖片

          我們現(xiàn)在該怎么辦?

          確定這是一個問題

          團(tuán)隊做錯的第一件事就是根本沒有意識到這是一個潛在的問題。消息失敗時有發(fā)生,我們需要制定一種策略來處理它……要未雨綢繆,而非亡羊補(bǔ)牢。

          因此,了解這是一種遲早會發(fā)生的問題并設(shè)計針對性的解決方案是我們要做的第一步。如果我們做到了這一點,就應(yīng)該向自己表示一點祝賀。現(xiàn)在最大的問題仍然存在:我們該如何處理這種情況?

          我們不能一直重試那條消息嗎?

          默認(rèn)情況下,如果消費者沒有成功消費一條消息(也就是說消費者無法提交當(dāng)前偏移量),它將重試同一條消息。那么,難道我們不能簡單地讓這種默認(rèn)行為接管一切,然后重試消息直到成功嗎?

          問題是這條消息可能永遠(yuǎn)不會成功。至少,沒有某種形式的手動干預(yù)它是不會成功的。于是乎,消費者就永遠(yuǎn)不會繼續(xù)處理后續(xù)的任何消息,并且我們的消息處理將陷入困境。

          好吧,我們不能簡單地跳過那條消息嗎?

          我們通常允許同步請求失敗。例如,對我們的 UserAccount 服務(wù)所做的一個“create-user”POST 可能包含錯誤或丟失的數(shù)據(jù)。在這種情況下,我們可以簡單地返回一個錯誤代碼(例如 HTTP 400),然后要求調(diào)用方重試。

          雖然這種辦法并不不理想,但這不會對我們的數(shù)據(jù)完整性造成任何長期問題。那個 POST 代表一條命令,是還沒有發(fā)生的事情。即使我們讓它失敗,我們的數(shù)據(jù)也將保持一致狀態(tài)。

          當(dāng)我們丟棄消息時情況并非如此。消息表示已經(jīng)發(fā)生的事件。任何忽略這些事件的消費者都將與生成事件的上游服務(wù)不再同步。

          所有這些都表明,我們不想丟棄消息。

          那么我們?nèi)绾谓鉀Q這個問題呢?

          對我們來說這不是什么容易解決的問題。因此,一旦我們認(rèn)識到它需要解決,就可以向互聯(lián)網(wǎng)咨詢解決方案。但這引出了我們的第二個問題:網(wǎng)上有一些我們可能不應(yīng)該遵循的建議。

          重試主題:流行的解決方案

          你會發(fā)現(xiàn)最受歡迎的一種解決方案就是重試主題(retry topics)的概念。具體細(xì)節(jié)因?qū)崿F(xiàn)而異,但總體概念是這樣的:

          • 消費者嘗試消費主要主題中的一條消息。
          • 如果未能正確消費該消息,則消費者將消息發(fā)布到第一個重試主題,然后提交消息的偏移量,以便繼續(xù)處理下一條消息。
          • 訂閱重試主題的是重試消費者,它包含與主消費者相同的邏輯。該消費者在消息消費嘗試之間引入了短暫的延遲。如果這個消費者也無法消費該消息,則會將該消息發(fā)布到另一個重試主題,并提交該消息的偏移量。
          • 這一過程繼續(xù),并增加了一些重試主題和重試消費者,每個重試的延遲越來越多(用作退避策略)。最后,在最終重試消費者無法處理某條消息后,該消息將發(fā)布到一個死信隊列(Dead Letter Queue,DLQ)中,工程團(tuán)隊將在該隊列中對其進(jìn)行手動分類。
          42715d87fab6675a59cdbb26f24c9302.webp

          概念上講,重試主題模式定義了失敗的消息將被分流到的多個主題。如果主要主題的消費者消費了它無法處理的消息,它會將該消息發(fā)布到重試主題 1 并提交當(dāng)前偏移量,從而將自身釋放給下一條消息。重試主題的消費者將是主消費者的副本,但如果它無法處理該消息,它將發(fā)布到一個新的重試主題。最終,如果最后一個重試消費者也無法處理該消息,它將把該消息發(fā)布到一個死信隊列(DLQ)。

          問題出在哪里?

          看起來這種方法似乎很合理。實際上,它在許多用例中都能正常工作。問題在于它不能充當(dāng)一種通用解決方案。現(xiàn)實中存在一些特殊用例(例如我們的跨邊界事件發(fā)布),對于這些用例來說,這種方法實際上是危險的。

          它忽略了不同類型的錯誤

          第一個問題是,它沒有考慮到導(dǎo)致事件消費失敗的兩大原因:可恢復(fù)錯誤和不可恢復(fù)錯誤。

          可恢復(fù)錯誤指的是,如果我們多次重試,這些錯誤最終將得以解決。一個簡單的示例是將數(shù)據(jù)保存到數(shù)據(jù)庫的消費者。如果數(shù)據(jù)庫暫時不可用,那么當(dāng)下一條消息通過時,消費者將失敗。一旦數(shù)據(jù)庫再次變得可用,消費者就能夠再次處理該消息。

          從另一個角度來看:可恢復(fù)錯誤指的是那些根源在消息和消費者外部的錯誤。解決這種錯誤后,我們的消費者將繼續(xù)前進(jìn),好像無事發(fā)生一樣。(很多人在這里被弄糊涂了。“可恢復(fù)”一詞并不意味著應(yīng)用程序本身——在我們的示例中為消費者——可以恢復(fù)。相反,它指的是某些外部資源——在此示例中為數(shù)據(jù)庫——會失敗并最終恢復(fù)。)

          關(guān)于可恢復(fù)錯誤需要注意的是,它們將困擾主題中的幾乎每一條消息。回想一下,主題中的所有消息都應(yīng)遵循相同的架構(gòu),并代表相同類型的數(shù)據(jù)。同樣,我們的消費者將針對該主題的每個事件執(zhí)行相同的操作。因此,如果消息 A 由于數(shù)據(jù)庫中斷而失敗,那么消息 B、消息 C 等也將失敗。

          不可恢復(fù)錯誤指的是無論我們重試多少次都將失敗的錯誤。例如,消息中缺少字段可能會導(dǎo)致一個 NullPointerException,或者包含特殊字符的字段可能會使消息無法解析。

          與可恢復(fù)錯誤不同,不可恢復(fù)錯誤通常會影響單個孤立消息。例如,如果只有消息 A 包含不可解析的特殊字符,則消息 B 將成功,消息 C 等也將成功。

          與可恢復(fù)錯誤不同,解決不可恢復(fù)錯誤意味著我們必須修復(fù)消費者本身(永遠(yuǎn)不要“修復(fù)”消息本身——它們是不可變的記錄!)例如,我們可能會修復(fù)消費者以便正確處理空值,然后重新部署它。

          那么,這與重試主題解決方案有什么關(guān)系?

          對于初學(xué)者來說,它對可恢復(fù)錯誤不是特別有用。請記住,在解決外部問題之前,可恢復(fù)錯誤將影響每一條消息,而不僅僅是當(dāng)前的一條消息。因此可以肯定的是,將失敗的消息分流到重試主題將為下一條消息清理出通道。但接下來的消息也將失敗,下一條以及再下一條也將失敗。我們最好還是讓消費者自己重試,直到問題解決為止。

          不可恢復(fù)的錯誤呢?重試隊列可以在這些情況下提供幫助。如果一條麻煩的消息阻止了所有后續(xù)消息的消費,那么毫無疑問,分流該消息肯定會為我們的用戶消費清除障礙(當(dāng)然,多個重試主題是沒必要的)。

          但是,雖然重試隊列可以幫助受不可恢復(fù)錯誤困擾的消息消費者繼續(xù)前進(jìn),但它也可能帶來更多隱患。下面我們就進(jìn)一步分析背后的原因。

          它會忽略排序

          我們簡要回顧一下跨邊界事件發(fā)布的一些重要環(huán)節(jié)。在有界上下文中處理一條命令后,我們會將一個對應(yīng)的事件發(fā)布到一個 Kafka 主題。重要的是,我們會將聚合的 ID 指定為分區(qū)鍵。

          為什么這很重要?它確保的是對任何給定聚合的更改都會發(fā)布到同一分區(qū)。

          好吧,那這一點為什么會那么重要呢?當(dāng)事件發(fā)布到同一分區(qū)時,可以保證各個事件按照它們發(fā)生的順序進(jìn)行處理。如果對同一聚合進(jìn)行連續(xù)更改,并且所產(chǎn)生的事件發(fā)布到不同的分區(qū),就可能發(fā)生爭用狀況,也就是消費者在消費第一個更改之前就消費了第二個更改。這會導(dǎo)致數(shù)據(jù)不一致。

          我們舉個簡單的例子。我們的 User 有界上下文提供了一個允許用戶更改其名稱的應(yīng)用程序。一位用戶將他的名字從 Zoey 更改為 Zo?,然后立即又更改為 Zoiee。如果我們不管排序,則某個下游消費者(例如 Login 有界上下文)可能會先處理對 Zoiee 的更改,然后不久用 Zo?覆蓋它。

          現(xiàn)在,登錄數(shù)據(jù)與我們的用戶數(shù)據(jù)已經(jīng)不同步了。更麻煩的是,每當(dāng) Zoiee 登錄我們的網(wǎng)站時都會看到“歡迎光臨,Zo?!”的登錄提示。

          這才是重試主題真正出問題的地方。它們讓我們的消費者容易打亂處理事件的順序。如果一個消費者在處理 Zo?更改時受到某個臨時的數(shù)據(jù)庫中斷的影響,它會把這個消息分流到一個重試主題,稍后再嘗試。如果在 Zoiee 更改到達(dá)時數(shù)據(jù)庫中斷已得到糾正,則這條消息將先被成功處理,然后再由 Zo?更改覆蓋。

          為了說明問題,這里用了 Zoiee/Zo?這樣一個簡單的示例。實際上,亂序處理事件可能導(dǎo)致會各種各樣的數(shù)據(jù)損壞問題。更糟糕的是,這些問題很少會在一開始就被注意到。相反,它們所導(dǎo)致的數(shù)據(jù)損壞往往在一段時間內(nèi)都不會引起注意,但損壞程度會隨著時間的推移而增長。一般來說,當(dāng)我們意識到發(fā)生了什么事情時,已經(jīng)有大量數(shù)據(jù)受到影響了。

          重試主題什么時候可行?

          需要明確的是,重試主題并非一直都是錯誤的模式。當(dāng)然,它也存在一些合適的用例。具體來說,當(dāng)消費者的工作是收集不可修改的記錄時,這種模式就很不錯。這樣的例子可能包括:

          • 處理網(wǎng)站活動流以生成報告的消費者
          • 將交易添加到分類賬的消費者(只要這些交易用不著按特定順序跟蹤)
          • 正在從另一個數(shù)據(jù)源 ETL 數(shù)據(jù)的消費者

          這類消費者可能會從重試主題模式中受益,同時沒有數(shù)據(jù)損壞的風(fēng)險。

          不過,請注意

          即使存在這種用例,我們?nèi)詰?yīng)謹(jǐn)慎行事。構(gòu)建這樣的解決方案既復(fù)雜又耗時。因此,作為一個組織,我們不想為每個新的消費者編寫一個新的解決方案。相反,我們要創(chuàng)建一個統(tǒng)一的解決方案,比如一個庫或一個容器等,可以在各種服務(wù)之間重復(fù)使用。

          還存在另一個問題。我們可能會為相關(guān)消費者構(gòu)建一個重試主題的解決方案。不幸的是,不久之后,這個解決方案就會進(jìn)入跨邊界事件發(fā)布消費者的領(lǐng)域了。擁有這些消費者的團(tuán)隊可能沒有意識到風(fēng)險的存在。正如我們前面所討論的那樣,在發(fā)生重大數(shù)據(jù)損壞之前,他們可能不會意識到任何問題。

          因此,在實現(xiàn)重試主題解決方案之前,我們應(yīng) 100%確定:

          • 我們的業(yè)務(wù)中永遠(yuǎn)不會有消費者來更新現(xiàn)有數(shù)據(jù),或者
          • 我們擁有嚴(yán)格的控制措施,以確保我們的重試主題解決方案不會在此類消費者中實現(xiàn)

          我們?nèi)绾胃纳七@種模式?

          鑒于重試主題模式可能不是跨邊界事件發(fā)布消費者的可接受解決方案,我們是否可以對其做一些調(diào)整來改善它呢?

          一開始,本文想要提供一種完整的解決方案。但之后我意識到,并不存在什么萬能的路徑。因此,我們將只討論一些在制定合適解決方案時需要考慮的事項。

          消除錯誤類型

          如果我們能夠在可恢復(fù)錯誤和不可恢復(fù)錯誤之間消除歧義,生活就會變得輕松許多。例如,如果我們的消費者開始遇到可恢復(fù)錯誤,那么重試主題就變得多余了。

          因此,我們可以嘗試確定所遇到的錯誤類型:

          void?processMessage(KafkaMessage?km)?{
          ??try?{
          ????Message?m?=?km.getMessage();
          ????transformAndSave(m);
          ??}?catch?(Throwable?t)?{
          ????if?(isRecoverable(t))?{
          ??????//?...
          ????}?else?{
          ??????//?...
          ????}
          ??}
          }

          在上面的 Java 偽代碼示例中,isRecoverable()將采用一種白名單方法來確定 t 是否表示可恢復(fù)錯誤。換句話說,它檢查 t 以確定它是否與任何已知的可恢復(fù)錯誤(例如 SQL 連接錯誤或 ReST 客戶端超時)相匹配,如果匹配則返回 true,否則返回 false。這樣就能防止我們的消費者被不可恢復(fù)錯誤一直阻塞下去。

          誠然,要在可恢復(fù)錯誤和不可恢復(fù)錯誤之間消除歧義可能很困難。例如,一個 SQLException 可能指的是一次數(shù)據(jù)庫故障(可恢復(fù))或一次約束違反狀況(不可恢復(fù))。如有疑問,我們可能應(yīng)該假設(shè)錯誤是不可恢復(fù)的——為此要冒的風(fēng)險是將其他好的消息發(fā)送給隱藏主題,從而延遲它們的處理……但這也能避免我們無意間陷入泥潭,無休止地嘗試處理不可恢復(fù)錯誤。

          在消費者內(nèi)重試可恢復(fù)錯誤

          正如我們所討論的那樣,存在可恢復(fù)錯誤時,將消息發(fā)布到重試主題毫無意義。我們只會為下一條消息的失敗掃清道路。相反,消費者可以簡單地重試,直到條件恢復(fù)。

          當(dāng)然,出現(xiàn)可恢復(fù)錯誤意味著外部資源存在問題。我們不斷對這塊資源發(fā)送請求是無濟(jì)于事的。因此,我們希望對重試應(yīng)用一個退避策略。我們的偽 Java 代碼現(xiàn)在可能看起來像這樣:

          void?processMessage(KafkaMessage?km)?{
          ??try?{
          ????Message?m?=?km.getMessage();
          ????transformAndSave(m);
          ??}?catch?(Throwable?t)?{
          ????if?(isRecoverable(t))?{
          ??????doWithRetry(m,?Backoff.EXPONENTIAL,?this::transformAndSave);
          ????}?else?{
          ??????//?...
          ????}
          ??}
          }

          (注意:我們使用的任何退避機(jī)制都應(yīng)配置為在達(dá)到某個閾值時向我們發(fā)出警報,并通知我們潛在的嚴(yán)重錯誤)

          遇到不可恢復(fù)錯誤時,將消息直接發(fā)送到最后一個主題

          另一方面,當(dāng)我們的消費者遇到不可恢復(fù)錯誤時,我們可能希望立即隱藏(stash)該消息,以釋放后續(xù)消息。但在這里使用多個重試主題會有用嗎?答案是否定的。在轉(zhuǎn)到 DLQ 之前,我們的消息只會經(jīng)歷 n 次消費失敗而已。那么,為什么不從一開始就將消息粘貼在那里呢?

          與重試主題一樣,這個主題(在這里,我們將其稱為隱藏主題)將擁有自己的消費者,其與主消費者保持一致。但就像 DLQ 一樣,這個消費者并不總是在消費消息;它只有在我們明確需要時才會這么做。

          考慮排序

          來看看排序的情況。我們在這里重用之前的“用戶/登錄”示例。嘗試處理 Zo?名稱中的?字符時,Login 消費者可能會遇到錯誤。消費者將其識別為一個不可恢復(fù)錯誤,將消息放在一邊,然后繼續(xù)處理后續(xù)消息。不久之后,消費者將獲得 Zoiee 消息并成功處理它。

          53eef3acf0347009ff140e59ec36f858.webp

          Zo?消息已隱藏,并且 Zoiee 消息現(xiàn)在已成功處理完畢。目前,兩個有界上下文之間的數(shù)據(jù)是一致的。

          晚些時候,我們的團(tuán)隊會修復(fù)消費者,以便其可以正確處理特殊字符并重新部署它。然后,我們將 Zo?消息重新發(fā)布給消費者,消費者現(xiàn)在可以正確處理該消息了。

          c112c26cf508d42b873db55517a0ed12.webp

          當(dāng)更新的消費者隨后處理隱藏的 Zo?消息后,兩個有界上下文之間的數(shù)據(jù)將變得不一致。因此,當(dāng) User 有界上下文將用戶視為 Zoiee 時,Login 有界上下文會將她稱為 Zo?。

          顯然,我們沒有保持排序;Zo?是在 Zoiee 之前由 Login 消費者處理的,但正確的順序是倒過來的。隱藏一條消息后,我們可以開始隱藏所有消息,但在那種情況下我們實際上會陷入困境。幸運的是,我們不需要保持所有消息的順序,只需考慮與單個聚合相關(guān)聯(lián)的消息即可。因此,如果我們的消費者可以跟蹤已隱藏的特定聚合,它就可以確保屬于同一聚合的后續(xù)消息也被隱藏。

          收到隱藏主題中消息的警報后,我們可以取消部署消費者并修復(fù)其代碼(請注意:切勿修改消息本身;消息代表不可變的事件!)在修復(fù)并測試了我們的消費者之后,我們可以重新部署它。當(dāng)然,在繼續(xù)使用主要主題之前,我們將需要特別注意先處理隱藏主題中的所有記錄。這樣,我們將繼續(xù)保持正確的排序狀態(tài)。出于這個原因,我們將首先部署隱藏消費者,并且只有在其完成時(這意味著消費者組中的所有實例都完成,如果我們使用了多個消費者),我們才會取消部署它并部署主消費者。

          我們還應(yīng)該考慮以下事實:固定的消費者處理了隱藏消息后,它仍可能會遇到其他錯誤。在這種情況下,其錯誤處理行為應(yīng)像我們之前描述的那樣:

          • 如果錯誤是可恢復(fù)的,則使用退避策略重試;
          • 如果錯誤是不可恢復(fù)的,它將隱藏消息并繼續(xù)下一條消息。

          為此,我們可以考慮使用第二個隱藏主題。

          可以接受一些數(shù)據(jù)不一致?

          這樣的系統(tǒng)構(gòu)建起來可能會變得相當(dāng)復(fù)雜。它們可能很難構(gòu)建、測試和維護(hù)。因此,某些組織可能會想要確定出數(shù)據(jù)不一致的可能性,并判斷他們是否可以承受這種風(fēng)險。

          在許多情況下,這些組織可能會采用數(shù)據(jù)協(xié)調(diào)機(jī)制,以使他們的數(shù)據(jù)最終(是相對較長的“最終”)變得一致。為此也存在許多策略(超出了本文的范圍)。

          總結(jié)

          處理重試似乎很復(fù)雜,那是因為它就是這么麻煩——和一切正常時 Kafka 相對優(yōu)雅的風(fēng)格相比之下尤其明顯。我們構(gòu)建的任何合適的解決方案(無論是重試主題、隱藏主題還是其他解決方案)都將比我們想要的更復(fù)雜。

          不幸的是,如果我們希望在微服務(wù)之間建立彈性的異步通信流,那么我們就不能忽略它。

          本文介紹了一種流行的解決方案、它的缺點以及在設(shè)計替代解決方案時應(yīng)考慮的一些事項。到最后,想要構(gòu)建正確的解決方案,我們就應(yīng)該牢記一些事情,例如:

          • 了解 Kafka 通過主題、分區(qū)和分區(qū)鍵提供的功能。
          • 考慮到可恢復(fù)錯誤與不可恢復(fù)錯誤之間的差異。
          • 設(shè)計模式的用法,例如有界上下文和聚合。
          • 無論現(xiàn)在還是將來,都要搞清楚我們組織的用例特性。我們只是在移動獨立的記錄嗎?……在這種情況下,我們可能不關(guān)心排序;還是說我們正在傳播表示數(shù)據(jù)更改的事件?……在這種情況下,排序至關(guān)重要。
          • 仔細(xì)考慮我們是否愿意承受任何水平的數(shù)據(jù)不一致。
          猜你喜歡
          HDFS的快照講解
          Hadoop 數(shù)據(jù)遷移用法詳解
          Hbase修復(fù)工具Hbck
          數(shù)倉建模分層理論
          一文搞懂Hive的數(shù)據(jù)存儲與壓縮
          大數(shù)據(jù)組件重點學(xué)習(xí)這幾個
          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国庄三级HD中文久久精品 | 91久久精品日日躁夜夜躁欧美又粗又大 | 久久大香蕉伊人网 | 黄片在线免费观看免播放器 | 爱爱小视频网站 |