搜尋文件

輸入關鍵字,搜尋標題與正文。

    訊息佇列與事件驅動架構文件 03/12

    Queue、Pub/Sub 與持久日誌

    本文提要

    一個工作需要由某個 worker 接手,多個獨立下游則需要各自保有處理進度。 選擇傳遞模型時,先問誰應該收到、進度存在哪裡,以及消費後還能否讀取歷史。

    18px · 寬鬆行距

    閱讀設定

    排版預覽 · 示範文字(可捲動)

    檔案與目錄

    Linux 以一棵從根目錄 / 開始的樹狀結構組織檔案。目錄用來整理檔案,也可以包含其他目錄。

    pwd
    ls -la /home
    18px

    16、18、20、22px;隨瀏覽器文字大小縮放。

    欄寬套用於背景文章;此處預覽字級與行距。手機正文自動配合螢幕。

    文件與本文目錄
    本主題文件

    一個工作需要由某個 worker 接手,多個獨立下游則需要各自保有處理進度。 選擇傳遞模型時,先問誰應該收到、進度存在哪裡,以及消費後還能否讀取歷史。

    本文接續命令、工作與事件:GenerateExport 由匯出服務負責,ExportCompleted 則供通知、搜尋及統計各自反應。這三個下游名稱是本例需求,不代表每個匯出服務都需要這些元件。

    查證日期:2026-11-04。RabbitMQ 使用當日頁首標示 4.4 的線上文件,Kafka 固定 4.1,Google Cloud 使用當日文件。全部拓樸與歷程是概念推演,未部署或量測產品。

    工作佇列:分攤同一種責任

    工作佇列(work queue)讓多個 worker 分攤同一類工作。假設匯出 queue 內有 cmd-42、cmd-43,worker A 與 B 都具備完整匯出能力,broker 可把兩筆投遞分派給不同 worker。

    這個設計想達成「每項工作有處理者」,不是「每個 worker 都收到所有工作」。RabbitMQ 一般 active consumers 分攤同 queue 的投遞;priority、prefetch 與 Single Active Consumer 等設定會影響分配。也不能由分攤規則推論業務效果只做一次,失敗重投仍須處理。參考 RabbitMQ:Consumers。

    如果把通知、搜尋與統計三種不同能力的程序放進同一競爭佇列,讓它們各拿一部分事件,需求就不成立:通知程序處理了 evt-90,不代表搜尋也會取得它。需要三種反應時,要安排三份獨立消費責任。

    發布訂閱:先拆開下游進度,再擴大各自 worker

    發布/訂閱(publish/subscribe,Pub/Sub)讓發布者送出一項內容,由不同訂閱各自接收。以 Google Cloud Pub/Sub 的文件模型為例,同 topic 上的兩個 subscription 各自收到訊息;同一 subscription 內的多個 subscriber 則分攤投遞。參考 Google Cloud:Pub/Sub overview。

    本例先建立三個獨立訂閱,並假設沒有過濾條件排除 evt-90:

    表格・3 欄 3 列
    訂閱責任訂閱內的處理程序evt-90 完成代表什麼
    通知通知 worker A、B通知端達成自己的處理契約
    搜尋搜尋 worker C、D搜尋端完成索引更新
    統計統計 worker E統計端完成自己的紀錄

    通知 A 與 B 分攤通知工作;它們不與搜尋 C、D 競爭同一份進度。通知卡住時,搜尋可以繼續,但仍可能共用 broker、配額與其他依賴,獨立訂閱不是絕對容量隔離。

    RabbitMQ 可以用 exchange 把訊息路由到不同 queue,再讓各 queue 內的 worker 分攤。其 fanout exchange 將訊息送往每個綁定目的地;direct 與 topic exchange 則依不同比對規則路由。這裡的 topic exchange 是 RabbitMQ 的路由類型,不能直接當作 Kafka topic 或 Google Pub/Sub topic 的同義物。參考 RabbitMQ:Exchanges。

    持久日誌:資料保存與讀取位置分開

    持久日誌(durable log)把紀錄追加到可保存的歷史中,consumer 以位置追蹤讀到哪裡。消費某筆紀錄,不必等於從共享歷史刪除該筆紀錄。

    Kafka topic 可分成多個 partition(分區),事件追加到分區內,offset(位移)標示分區中的位置。順序範圍是單一 partition,不能由它推論跨 partition 的全域順序。事件消費後不立即刪除,保存仍由 topic 設定控制。參考 Kafka 4.1:Introduction。

    在 Kafka 4.1 的 traditional consumer group 模型裡,同 group 分攤分區,不同 group 保有各自進度。因此可讓通知、搜尋、統計分屬不同 group,再在各 group 內擴大 worker。4.1 也另有預覽的 share group,分工與確認方式不同;本篇只用 traditional 模型說明位置。參考 Kafka 4.1:Design,The Consumer、Share groups。

    下面以同一 partition 為例。表中「下次讀取位置」採本例示意,commit 的位置不能用來證明業務已完成:

    表格・3 欄 4 列
    觀察時點歷史與各自進度推論
    發布後offset 20 是 evt-90;通知與搜尋下一筆都在 20兩者尚未依各自進度越過此事件
    搜尋先處理搜尋提交下一位置 21,通知仍在 20搜尋的進度沒有代替通知前進
    搜尋重建在資料仍保留時,搜尋重新從 20 讀取可再次取得事件,但必須另處理重播副作用
    歷史已清理offset 20 的紀錄已不在可讀範圍保有舊位置不會憑空恢復被移除資料

    日誌也不是某個品牌的專屬模型。RabbitMQ streams 支援非破壞讀取與按大小/年齡保留,和 classic/quorum queue 的消費模型不同。參考 RabbitMQ:Streams。

    三個問題,比產品分類更有用

    表格・4 欄 3 列
    要回答的問題工作佇列視角獨立訂閱視角日誌視角
    誰應處理同一筆?同責任 worker 分攤每個符合條件的訂閱有自己的進度每個讀取群組可有自己的位置
    什麼代表前進?該投遞按協定結束該訂閱按自身協定前進讀取/提交位置;仍需核對效果
    能否取得歷史?不能由 queue 名稱推論取決於保存與重播能力仍受保存及清理政策限制

    這是三種分析視角,並非互斥的產品分類。一個產品可以同時提供競爭消費、獨立訂閱與可重讀資料;相同名稱也可能有不同保證。選型時應把需求改寫成可核對的問題:搜尋離線一天後能否追上?通知失敗是否拖住搜尋?已確認訊息能保存到何時?

    對照本例的最小演進

    起初只需要一組匯出 worker,工作 queue 即可表達分攤責任。當完成事實需要多個下游反應,再加入獨立訂閱。當搜尋需要重建歷史,還要確認保存政策、起始位置與重播契約;不能只新增一個訂閱,就假定過去所有事件都能取得。

    每次增加元件都對應具體需求,沒有要求先把 queue、Pub/Sub 與 log 全部部署。下一篇回頭檢查:這些傳遞方式的發布確認與消費確認,到底各自證明什麼?

    相關文件