<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Supercluster on Tarragon</title><link>https://tarrragon.github.io/blog/tags/supercluster/</link><description>Recent content in Supercluster on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 16 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/supercluster/index.xml" rel="self" type="application/rss+xml"/><item><title>NATS JetStream 設計與 supercluster / leaf node：stream、consumer、跨區拓樸與多租戶</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/jetstream-supercluster-design/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/jetstream-supercluster-design/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS&lt;/a> overview 的 implementation-layer deep article。Overview 回答「NATS 該不該選、Core NATS vs JetStream 怎麼分」；要不要從 core NATS 跨進 JetStream 的決策入口見 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/jetstream-durability-consumer/" data-link-title="NATS core 到 JetStream：fire-and-forget 在哪裡不夠、跨過去要付什麼" data-link-desc="Core NATS 的 fire-and-forget 在 consumer 重啟或 rolling deploy 時掉訊息——這不是 bug、是設計。需要訊息不丟就跨進 JetStream（persistence &amp;#43; at-least-once &amp;#43; redelivery）。本文展開 core 與 JetStream 的邊界、stream 與 consumer 的求值模型、實機驗證的 durable pull consumer、5 個把 JetStream consumer 寫成丟訊息與重投風暴的 production 踩坑">core 到 JetStream 的邊界&lt;/a>；本文回答「JetStream stream / consumer 的每個旋鈕怎麼設、設錯踩什麼坑、跨區拓樸怎麼鋪、多租戶怎麼隔離」。寫作結構依 &lt;a href="https://tarrragon.github.io/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">Vendor 深度技術文章的寫作方法論&lt;/a> 的 6 段框架。&lt;/p>&lt;/blockquote>
&lt;h2 id="jetstream-把-fire-and-forget-升級成-durable-log">JetStream 把 fire-and-forget 升級成 durable log&lt;/h2>
&lt;p>JetStream 是 NATS 內建的持久化層、責任是把 Core NATS 的 fire-and-forget subject 轉成 append-only 的 durable stream、並讓 consumer 能 ack、重投、replay。Core NATS 的訊息一旦沒有 active subscriber 就消失；JetStream 把符合特定 subject 的訊息攔截下來寫進 stream、即使沒有任何 consumer 在線也會留存到 retention 上限。&lt;/p>
&lt;p>兩個概念要先分清楚、後面所有配置都掛在這個分界上。Stream 是 &lt;em>儲存&lt;/em> 責任：定義「哪些 subject 的訊息要存、存多久、存多少、存哪裡」。Consumer 是 &lt;em>投遞&lt;/em> 責任：定義「從 stream 的哪個位置開始讀、怎麼 ack、ack 不回來要不要重投、重投幾次」。同一個 stream 可以掛多個 consumer、各自有獨立的讀取游標跟重投狀態、互不影響。這個 stream / consumer 二分是 JetStream 跟 Kafka（topic / consumer group）對應、但跟 RabbitMQ（queue 本身就綁消費）不同的核心模型差異。&lt;/p>
&lt;p>本文用一個訂單事件流當主線：subject 設計成 &lt;code>orders.created.&amp;lt;region&amp;gt;&lt;/code>、stream 名 &lt;code>orders&lt;/code>、subject filter &lt;code>orders.&amp;gt;&lt;/code>。實機環境用單機 NATS server 加 &lt;code>-js&lt;/code>、CLI 用 &lt;code>natsio/nats-box&lt;/code> 容器；跨節點的 Cluster / quorum 段用 3 節點 docker compose 驗證、Supercluster / Leaf node 因拓樸複雜以 case 敘述加官方文件 caveat 標註。&lt;/p>
&lt;h2 id="stream-設計storageretentiondiscard容量上限">Stream 設計：storage、retention、discard、容量上限&lt;/h2>
&lt;p>Stream 的設計責任是回答四個彼此獨立的問題：訊息存在哪種介質、用什麼規則決定保留、超過上限時丟哪一端、上限本身設多大。這四個旋鈕組合錯了不會在建立時報錯、而是在 production 流量打進來才以丟訊息或塞爆 disk 的形式爆出來。&lt;/p>
&lt;h3 id="storagefile-vs-memory">Storage：file vs memory&lt;/h3>
&lt;p>Storage type 決定訊息寫在 disk 還是 RAM。&lt;code>file&lt;/code> storage 把 stream 寫進 disk、server 重啟後資料還在、是需要 durability 的事件流預設選擇；&lt;code>memory&lt;/code> storage 把 stream 放 RAM、吞吐跟延遲更好但 server 重啟即全失、適合短期 fan-out 或可重建的快取型資料。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS</a> overview 的 implementation-layer deep article。Overview 回答「NATS 該不該選、Core NATS vs JetStream 怎麼分」；要不要從 core NATS 跨進 JetStream 的決策入口見 <a href="/blog/backend/03-message-queue/vendors/nats/jetstream-durability-consumer/" data-link-title="NATS core 到 JetStream：fire-and-forget 在哪裡不夠、跨過去要付什麼" data-link-desc="Core NATS 的 fire-and-forget 在 consumer 重啟或 rolling deploy 時掉訊息——這不是 bug、是設計。需要訊息不丟就跨進 JetStream（persistence &#43; at-least-once &#43; redelivery）。本文展開 core 與 JetStream 的邊界、stream 與 consumer 的求值模型、實機驗證的 durable pull consumer、5 個把 JetStream consumer 寫成丟訊息與重投風暴的 production 踩坑">core 到 JetStream 的邊界</a>；本文回答「JetStream stream / consumer 的每個旋鈕怎麼設、設錯踩什麼坑、跨區拓樸怎麼鋪、多租戶怎麼隔離」。寫作結構依 <a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">Vendor 深度技術文章的寫作方法論</a> 的 6 段框架。</p></blockquote>
<h2 id="jetstream-把-fire-and-forget-升級成-durable-log">JetStream 把 fire-and-forget 升級成 durable log</h2>
<p>JetStream 是 NATS 內建的持久化層、責任是把 Core NATS 的 fire-and-forget subject 轉成 append-only 的 durable stream、並讓 consumer 能 ack、重投、replay。Core NATS 的訊息一旦沒有 active subscriber 就消失；JetStream 把符合特定 subject 的訊息攔截下來寫進 stream、即使沒有任何 consumer 在線也會留存到 retention 上限。</p>
<p>兩個概念要先分清楚、後面所有配置都掛在這個分界上。Stream 是 <em>儲存</em> 責任：定義「哪些 subject 的訊息要存、存多久、存多少、存哪裡」。Consumer 是 <em>投遞</em> 責任：定義「從 stream 的哪個位置開始讀、怎麼 ack、ack 不回來要不要重投、重投幾次」。同一個 stream 可以掛多個 consumer、各自有獨立的讀取游標跟重投狀態、互不影響。這個 stream / consumer 二分是 JetStream 跟 Kafka（topic / consumer group）對應、但跟 RabbitMQ（queue 本身就綁消費）不同的核心模型差異。</p>
<p>本文用一個訂單事件流當主線：subject 設計成 <code>orders.created.&lt;region&gt;</code>、stream 名 <code>orders</code>、subject filter <code>orders.&gt;</code>。實機環境用單機 NATS server 加 <code>-js</code>、CLI 用 <code>natsio/nats-box</code> 容器；跨節點的 Cluster / quorum 段用 3 節點 docker compose 驗證、Supercluster / Leaf node 因拓樸複雜以 case 敘述加官方文件 caveat 標註。</p>
<h2 id="stream-設計storageretentiondiscard容量上限">Stream 設計：storage、retention、discard、容量上限</h2>
<p>Stream 的設計責任是回答四個彼此獨立的問題：訊息存在哪種介質、用什麼規則決定保留、超過上限時丟哪一端、上限本身設多大。這四個旋鈕組合錯了不會在建立時報錯、而是在 production 流量打進來才以丟訊息或塞爆 disk 的形式爆出來。</p>
<h3 id="storagefile-vs-memory">Storage：file vs memory</h3>
<p>Storage type 決定訊息寫在 disk 還是 RAM。<code>file</code> storage 把 stream 寫進 disk、server 重啟後資料還在、是需要 durability 的事件流預設選擇；<code>memory</code> storage 把 stream 放 RAM、吞吐跟延遲更好但 server 重啟即全失、適合短期 fan-out 或可重建的快取型資料。</p>
<p>實機建一個 file storage、limits retention、discard old 的 stream：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl">nats --server nats://localhost:4232 stream add orders <span class="se">\
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="se"></span>  --subjects <span class="s1">&#39;orders.&gt;&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="se"></span>  --storage file <span class="se">\
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="se"></span>  --retention limits <span class="se">\
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="se"></span>  --discard old <span class="se">\
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="se"></span>  --max-msgs <span class="m">1000</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="se"></span>  --max-bytes 10MB <span class="se">\
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="se"></span>  --max-age 1h <span class="se">\
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="se"></span>  --replicas <span class="m">1</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="se"></span>  --defaults</span></span></code></pre></div><p><code>nats stream info orders</code> 回報的配置確認旋鈕都生效：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">                     Subjects: orders.&gt;
</span></span><span class="line"><span class="ln">2</span><span class="cl">                      Storage: File
</span></span><span class="line"><span class="ln">3</span><span class="cl">                    Retention: Limits
</span></span><span class="line"><span class="ln">4</span><span class="cl">               Discard Policy: Old
</span></span><span class="line"><span class="ln">5</span><span class="cl">             Maximum Messages: 1,000
</span></span><span class="line"><span class="ln">6</span><span class="cl">                Maximum Bytes: 10 MiB
</span></span><span class="line"><span class="ln">7</span><span class="cl">                  Maximum Age: 1h0m0s</span></span></code></pre></div><p>選 memory 的判讀訊號：訊息可從上游重建（例如 metrics 採樣、可重抓的 snapshot）、或 consumer 一定在線且消費速度跟得上、且單 stream 資料量遠小於可用 RAM。一旦這三條有一條不成立、預設回到 file storage。</p>
<h3 id="retentionlimits-vs-interest-vs-workqueue">Retention：limits vs interest vs workqueue</h3>
<p>Retention policy 決定「訊息什麼時候從 stream 移除」、是 stream 三種使用形態的分水嶺。</p>
<p><code>limits</code> retention 是時間 / 容量驅動：訊息留到撞上 MaxMsgs / MaxBytes / MaxAge 任一上限才移除、跟有沒有人消費無關。這是「事件 log」形態、適合需要 replay、多個獨立 consumer 各讀各的場景。訂單事件流用 limits、因為審計、對帳、即時處理可能是三個獨立 consumer、訊息不能因為某個 consumer ack 了就消失。</p>
<p><code>interest</code> retention 是訂閱驅動：當 stream 上 <em>所有</em> 已註冊的 consumer 都 ack 了某筆訊息、該訊息立刻移除。它介於 limits 跟 workqueue 之間、適合「只要所有關心的 consumer 都收到就不必再留」的扇出場景。</p>
<p><code>workqueue</code> retention 是任務佇列形態：每筆訊息只會被 <em>一個</em> consumer 成功 ack、ack 後立刻刪除。它把 stream 當成工作分派佇列、語意接近 RabbitMQ 的 work queue。實機驗證 workqueue 的 retention 在 info 反映：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">nats --server nats://localhost:4232 stream add wq <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --subjects <span class="s1">&#39;wq.&gt;&#39;</span> --storage memory --retention work <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --max-msgs <span class="m">100</span> --replicas <span class="m">1</span> --defaults
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># nats stream info wq → Retention: WorkQueue</span></span></span></code></pre></div><p>判讀路由：需要多 consumer 各自 replay → limits；需要扇出且所有訂閱者收齊就清 → interest；需要競爭式單次消費的任務派工 → workqueue。選 workqueue 卻又掛兩個 filter 重疊的 consumer 會在建 consumer 時被拒、因為 workqueue 不允許同一筆訊息被兩個 consumer 認領。</p>
<h3 id="discardold-vs-new">Discard：old vs new</h3>
<p>Discard policy 決定 stream <em>撞上 MaxMsgs / MaxBytes 上限後</em> 丟哪一端。這個旋鈕的選擇直接對應業務對「舊資料」跟「新資料」誰更重要的判斷、選錯會靜默丟訊息。</p>
<p><code>discard old</code> 在達上限時丟掉最舊的訊息、騰空間給新訊息。實機驗證：max-msgs 設 3、連發 5 筆、stream 留下最後 3 筆：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">discard old, max-msgs 3, published 5:
</span></span><span class="line"><span class="ln">2</span><span class="cl">                     Messages: 3
</span></span><span class="line"><span class="ln">3</span><span class="cl">               First Sequence: 3
</span></span><span class="line"><span class="ln">4</span><span class="cl">                Last Sequence: 5</span></span></code></pre></div><p>最舊的 seq 1、2 被丟、保留 seq 3-5。這對應「新資料比舊資料重要」的場景：即時儀表板、最新狀態快照、寧可丟歷史也要保住最新。</p>
<p><code>discard new</code> 在達上限時拒絕新訊息、保住已存的舊訊息。同樣 max-msgs 3、連發 5 筆：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">discard new, max-msgs 3, published 5:
</span></span><span class="line"><span class="ln">2</span><span class="cl">                     Messages: 3
</span></span><span class="line"><span class="ln">3</span><span class="cl">               First Sequence: 1
</span></span><span class="line"><span class="ln">4</span><span class="cl">                Last Sequence: 3</span></span></code></pre></div><p>保留 seq 1-3、後到的 seq 4、5 進不來。這對應「舊資料是已承諾的工作、不能丟」的場景：任務佇列在塞滿時應拒收新任務（並對上游施加 backpressure）、而不是把排隊中的任務擠掉。</p>
<p>discard new 有個容易踩的投遞行為差異、見故障演練 Case 2。</p>
<h3 id="容量上限maxmsgs--maxbytes--maxage">容量上限：MaxMsgs / MaxBytes / MaxAge</h3>
<p>三個上限是 OR 關係：任一撞到就觸發 discard / 移除。MaxMsgs 限筆數、MaxBytes 限總位元組、MaxAge 限訊息存活時間。實務上三者搭配使用：MaxAge 防止無限累積（例如事件流只保留 7 天）、MaxBytes 是 disk 的硬護欄（防單 stream 撐爆 volume）、MaxMsgs 在訊息大小均勻時當作粗略筆數控制。</p>
<p>容量規劃的判讀順序是先定 MaxAge（業務需要 replay 多久）、再用「平均訊息大小 × 預估 throughput × MaxAge」反推 MaxBytes 是否在 disk 預算內、超出就縮短 MaxAge 或拆 stream。把 MaxBytes 設成 unlimited 而只靠 MaxMsgs 是常見的容量事故來源：訊息大小一旦變大（例如 payload 夾帶了 base64 附件）、筆數沒到上限但 disk 已滿。</p>
<h2 id="consumer-設計pullpushackackwaitmaxdeliverreplay">Consumer 設計：pull/push、ack、AckWait、MaxDeliver、replay</h2>
<p>Consumer 的設計責任是控制「訊息怎麼從 stream 送到處理端、處理端怎麼確認、確認不回來怎麼辦」。它的每個旋鈕都圍繞同一個核心張力：在 at-least-once 投遞下、如何在「不漏處理」跟「不過度重投」之間取得平衡。對應的概念基礎見 <a href="/blog/backend/knowledge-cards/delivery-semantics/" data-link-title="Delivery Semantics" data-link-desc="說明事件投遞語意如何定義遺失、重複、順序與補償策略">Delivery Semantics</a> 與 <a href="/blog/backend/knowledge-cards/processing-semantics/" data-link-title="Processing Semantics" data-link-desc="說明 consumer 處理事件後業務結果是否正確，與投遞成功分屬不同責任">Processing Semantics</a> 知識卡。</p>
<h3 id="pull-vs-push">Pull vs push</h3>
<p>Pull consumer 由處理端主動拉：consumer 發 pull request 帶 batch size、server 才送對應數量的訊息。流量控制天然落在消費端、消費端有多少處理能力就拉多少、是現代 JetStream 應用的預設模式。Push consumer 由 server 主動推到一個 delivery subject、處理端訂閱那個 subject、適合需要 server 端 flow control 或既有 Core NATS 訂閱模型遷移的場景。</p>
<p>實機建一個 pull consumer、explicit ack、AckWait 30s、MaxDeliver 5、replay instant：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">nats --server nats://localhost:4232 consumer add orders worker <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --pull <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --deliver all <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --ack explicit <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --wait 30s <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --max-deliver <span class="m">5</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="se"></span>  --replay instant <span class="se">\
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="se"></span>  --filter <span class="s1">&#39;orders.&gt;&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="se"></span>  --defaults</span></span></code></pre></div><p><code>nats consumer info orders worker</code> 確認配置：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">                    Name: worker
</span></span><span class="line"><span class="ln">2</span><span class="cl">               Pull Mode: true
</span></span><span class="line"><span class="ln">3</span><span class="cl">          Deliver Policy: All
</span></span><span class="line"><span class="ln">4</span><span class="cl">              Ack Policy: Explicit
</span></span><span class="line"><span class="ln">5</span><span class="cl">                Ack Wait: 30.00s
</span></span><span class="line"><span class="ln">6</span><span class="cl">           Replay Policy: Instant
</span></span><span class="line"><span class="ln">7</span><span class="cl">      Maximum Deliveries: 5</span></span></code></pre></div><p>push consumer 改用 <code>--target &lt;subject&gt;</code> 取代 <code>--pull</code>、info 會回報 <code>Delivery Subject:</code> 而非 Pull Mode。</p>
<h3 id="ackpolicyexplicit-是預設選擇">AckPolicy：explicit 是預設選擇</h3>
<p>Ack policy 決定 consumer 怎麼確認訊息已處理。<code>explicit</code> 要求對每一筆訊息單獨 ack、是 at-least-once 處理的基礎、production 預設選擇。<code>all</code> 用累積 ack：ack 第 N 筆等於 ack 了第 N 筆以前全部、吞吐高但一筆處理失敗會讓整段重投。<code>none</code> 完全不 ack、投遞即視為完成、語意退化成接近 fire-and-forget、只適合可容忍丟失的場景。</p>
<p>explicit ack 之所以是預設、是因為它讓每筆訊息的處理結果獨立可追蹤：哪筆 ack 了、哪筆還 outstanding、哪筆重投超限、都能在 consumer info 看到。實機發 3 筆訊息後、consumer info 的 <code>Unprocessed Messages</code> 反映 stream 中尚未投遞的 backlog：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">nats --server nats://localhost:4232 pub orders.created.us-1 <span class="s2">&#34;order-1&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 發 3 筆後：</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># nats consumer info orders worker →</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">#     Unprocessed Messages: 3</span></span></span></code></pre></div><p>拉出訊息但不 ack、consumer info 的 <code>Outstanding Acks</code> 反映已投遞但未確認的數量：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">        Outstanding Acks: 3 out of maximum 1,000</span></span></code></pre></div><p>這兩個數字是診斷 consumer 健康的第一手訊號：<code>Unprocessed</code> 高代表 consumer 拉得太慢或停了（stream backlog）；<code>Outstanding Acks</code> 持續高代表訊息拉出去了但處理端沒 ack（處理慢或卡住）。這個區分對應 overview 排錯段的「pending 是 ack-pending 還是 stream backlog」判讀。</p>
<h3 id="ackwait--maxdeliver重投的兩個邊界">AckWait + MaxDeliver：重投的兩個邊界</h3>
<p>AckWait 是 server 等待 ack 的時間窗：訊息投遞後、若 AckWait 內沒收到 ack、server 視為投遞失敗、重新投遞。MaxDeliver 是同一筆訊息的投遞次數上限：達到後不再重投、訊息進入 terminal 狀態（可導向 advisory / DLQ 機制）。</p>
<p>這兩個旋鈕共同定義重投行為。AckWait 要設成 <em>略大於 consumer 處理一筆訊息的 p99 時間</em>：太短會在 consumer 還在正常處理時就誤判失敗重投、造成重複處理（見故障演練 Case 1）；太長會讓真正卡死的訊息遲遲不重投、拖慢 recovery。MaxDeliver 是 poison message 的護欄：一筆訊息若處理永遠失敗（例如 payload 格式壞）、沒有 MaxDeliver 它會無限重投佔住 consumer。對應 <a href="/blog/backend/knowledge-cards/redelivery-loop/" data-link-title="Redelivery Loop" data-link-desc="說明同一訊息反覆投遞失敗如何消耗 consumer 容量">Redelivery Loop</a> 知識卡描述的失控重投。</p>
<h3 id="replayinstant-vs-original">Replay：instant vs original</h3>
<p>Replay policy 只在 consumer 從歷史位置讀（例如 <code>--deliver all</code> 重讀整個 stream）時生效、決定投遞節奏。<code>instant</code> 以 server 最快速度投遞、是處理 backlog 或重建狀態的預設。<code>original</code> 按訊息 <em>原始寫入的時間間隔</em> 重放：若原始訊息間隔 1 秒寫入、replay 也間隔 1 秒投遞、用於需要重現時序的測試或模擬。實機兩種都可建：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">nats consumer add orders replayorig ... --replay original  <span class="c1"># Replay Policy: Original</span></span></span></code></pre></div><h2 id="cluster--supercluster--leaf-node三層拓樸">Cluster / Supercluster / Leaf node：三層拓樸</h2>
<p>NATS 的拓樸分三層、各解一個不同尺度的問題：Cluster 解單區內的高可用、Supercluster 解跨區的延展、Leaf node 解邊緣到中心的連接。三者可組合、但職責不重疊。</p>
<h3 id="cluster單區-raft-高可用">Cluster：單區 Raft 高可用</h3>
<p>Cluster 是同一 region 內多個 NATS server 用 full mesh route 互連、JetStream 的 stream 透過 Raft 在多個 replica 間複製。Replica 數（R1 / R3 / R5）決定容錯：R3 容忍 1 節點失效、R5 容忍 2 節點。Raft 要求多數派（quorum）才能寫入、所以 R3 需要至少 2 節點健康。</p>
<p>實機用 3 節點 docker compose 起 cluster、建 R3 stream、stream info 顯示 Raft group 與 replica 狀態：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">nats --server nats://n1:4222 stream add rep3 <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --subjects <span class="s1">&#39;rep3.&gt;&#39;</span> --storage file --retention limits <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --discard old --max-msgs <span class="m">1000</span> --replicas <span class="m">3</span> --defaults</span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">                     Replicas: 3
</span></span><span class="line"><span class="ln">2</span><span class="cl">Cluster Information:
</span></span><span class="line"><span class="ln">3</span><span class="cl">                Cluster Group: S-R3F-unEqlH8C
</span></span><span class="line"><span class="ln">4</span><span class="cl">                       Leader: n2 (222ms)
</span></span><span class="line"><span class="ln">5</span><span class="cl">                      Replica: n1, current, seen 217ms ago
</span></span><span class="line"><span class="ln">6</span><span class="cl">                      Replica: n3, current, seen 219ms ago</span></span></code></pre></div><p>Leader 是 Raft 選出的寫入協調者、其餘 replica 跟隨。<code>current</code> 代表該 replica 與 leader 同步；落後會顯示 <code>outdated</code> 加落後的 operation 數。失去 quorum 的行為見故障演練 Case 4。</p>
<h3 id="supercluster跨區-gateway-延展">Supercluster：跨區 gateway 延展</h3>
<p>Supercluster 用 gateway 連接多個 Cluster、形成跨 region / 跨雲的單一 NATS 邏輯網路。Gateway 之間是按需轉發、不是 full mesh：訊息只在有訂閱者的 region 之間流動、避免跨區頻寬被無謂的全量複製吃掉。Supercluster 讓 publisher 在任一 region 發訊息、訂閱者在另一 region 收到、同時讓每個 Cluster 維持自己的 JetStream Raft 群組與本地高可用。</p>
<blockquote>
<p>以下 Supercluster 行為依 <a href="https://docs.nats.io/running-a-nats-service/configuration/gateways">NATS 官方文件</a> 描述、未在本文實機環境驗證（gateway 多區拓樸需要跨 region 部署）。</p></blockquote>
<p><a href="/blog/backend/03-message-queue/cases/nats-form3-multi-cloud-payments/" data-link-title="3.C35 Form3：NATS JetStream 多雲低延遲支付" data-link-desc="Form3 服務 Tier-1 銀行、500ms SLA、SNS/SQS 吃 300ms 預算、改 NATS&#43;JetStream 跨雲 6x 延遲改善。">3.C35 Form3</a> 是 Leaf node 跨雲橋接的代表案例（Supercluster 為相應的一般拓樸選項、case 本身明確點到的是 Leaf node）：服務 Tier-1 銀行、要求 500ms 端到端 SLA、AWS SNS/SQS 約 300ms 延遲吃掉預算。Form3 用 JetStream 跨雲橋接、達到約 6× 延遲改善、並做到「AWS 整個 region 掛掉時不喪失處理能力」。這個案例揭露的判讀是：金融支付的硬 latency 預算逼出特定拓樸選型、不是把 Kafka / SQS 通用化套上去。</p>
<h3 id="leaf-node邊緣連中心">Leaf node：邊緣連中心</h3>
<p>Leaf node 是輕量 NATS server、跑在邊緣（工廠、店面、IoT gateway）、透過單一 leaf connection 連回中心 hub。它在邊緣本地提供完整的 NATS / JetStream 能力（本地 publish / subscribe / 本地持久化）、同時把需要的 subject 透過 leaf connection 雙向橋接到 hub。Leaf node 的價值在於：邊緣到中心的網路斷線時、邊緣端的本地 JetStream 持續收訊息、連線恢復後再同步、不丟資料。</p>
<blockquote>
<p>以下 Leaf node 行為依 <a href="https://docs.nats.io/running-a-nats-service/configuration/leafnodes">NATS 官方文件</a> 與下列 case 描述、未在本文實機環境驗證（leaf 拓樸需要 hub + edge 雙端部署）。</p></blockquote>
<p><a href="/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &#43; JetStream &#43; KV &#43; Object Store。">3.C37 MachineMetrics</a> 是 Leaf node 邊緣到雲端的完整案例：跨數百客戶廠區、數千機台、單機最高 1000Hz 採樣、工廠網路斷斷續續、Kinesis 等 cloud-only 工具無法跑在資源受限 edge。MachineMetrics 用 Leaf node 做 hub-and-spoke、edge 端用 JetStream 做本地持久化抵抗斷線。這個案例揭露的判讀是：broker 的功能集合（messaging + 本地持久化 + KV + Object Store + auth）決定它能不能取代邊緣的多套工具。</p>
<p><a href="/blog/backend/03-message-queue/cases/nats-iflow-ot-it-integration/" data-link-title="3.C41 i-flow：NATS 做 OT/IT 跨層整合 bus" data-link-desc="i-flow 每日 4 億筆 data operation、200&#43; OT/IT connector、客戶含 Bosch / Sto / Lenze、NATS 當邊緣到 central 整合 bus。">3.C41 i-flow</a> 是多工廠 leaf node 拓樸的另一證據：每日 4 億筆 data operation、200+ OT/IT connector、用 leaf node hub-and-spoke 把多工廠接到 central、而不是每工廠自管一套 cluster。判讀：多工廠場景的運維成本由「每個邊緣點是不是要獨立維運一套 cluster」決定、leaf node 把邊緣端壓到單一 server。</p>
<h2 id="subject-based-acl-與多租戶">Subject-based ACL 與多租戶</h2>
<p>NATS 多租戶的主機制是 account：account 是完全隔離的 subject 命名空間、不同 account 之間預設互不可見、即使 subject 名稱相同也不會互通。Account 之內再用 subject-level permission 控制每個 user 能 publish / subscribe 哪些 subject。這兩層組合起來：account 給租戶硬隔離、subject permission 給租戶內的角色細分權限。</p>
<p>跨 account 的受控互通用 import / export：一個 account 把特定 subject export 出來、另一個 account 顯式 import、才會打通那條 subject。預設不通、互通是顯式授權的結果、這讓多租戶的資料流動可審計。對應 MachineMetrics 案例用 decentralized auth 隔離不同客戶廠區的設計：每個客戶是一個 account、廠區設備在 account 內用 subject permission 限定只能發自己廠區的 subject。</p>
<p>多租戶設計的判讀訊號：租戶之間要完全隔離、用 account；同租戶內的不同服務 / 角色要限權、用 subject permission；少數需要跨租戶共享的 subject（例如全域控制信號）、用 import / export 顯式打通、不要為了方便把不同租戶塞進同 account。</p>
<h2 id="production-故障演練">Production 故障演練</h2>
<p>deep article 的差異化價值在故障演練。以下四個都是 JetStream stream / consumer / 拓樸層的典型事故、前兩個有本文實機驗證、後兩個結合實機（quorum）與 case 敘述。</p>
<h3 id="case-1ackwait-太短造成重複處理">Case 1：AckWait 太短造成重複處理</h3>
<p><strong>徵兆</strong>：consumer 正常運行、處理邏輯沒報錯、但下游出現大量重複副作用（重複扣款、重複寄信、重複寫入）。consumer info 的 <code>Redelivered Messages</code> 持續上升、即使處理端沒有任何 exception。</p>
<p><strong>根因</strong>：AckWait 設得比 consumer 處理一筆訊息的實際耗時短。訊息投遞後 consumer 還在處理、AckWait 就到期、server 判定投遞失敗、把同一筆訊息重投給（可能是另一個）consumer 實例、於是同一筆訊息被處理兩次。實機重現：建一個 AckWait 1s 的 consumer、拉出訊息不 ack、過 1s 後再拉、<code>tries</code> 從 1 變 2：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">第一次拉：subj: orders.created.us-1 / tries: 1 / str seq: 1
</span></span><span class="line"><span class="ln">2</span><span class="cl">過 1s 後：subj: orders.created.us-1 / tries: 2 / str seq: 1
</span></span><span class="line"><span class="ln">3</span><span class="cl">consumer info → Redelivered Messages: 3</span></span></code></pre></div><p><strong>修法</strong>：</p>
<ol>
<li><strong>量測再設值</strong>：AckWait 設成 consumer 處理 p99 時間的 2-3 倍、而不是拍腦袋設 30s。處理一筆要 5s 的 worker 配 AckWait 30s、處理一筆要 45s 的 worker 配 AckWait 30s 就會持續誤判重投。</li>
<li><strong>長任務用 in-progress ack</strong>：處理時間本就偏長且方差大的任務、處理端在處理中定期送 <code>AckProgress</code>（working ack）延長 AckWait、而不是把 AckWait 設成一個無法涵蓋最壞情況的固定大值。</li>
<li><strong>處理端做冪等</strong>：at-least-once 投遞下重複是常態而非異常、副作用以業務 key 去重（對應 <a href="/blog/backend/knowledge-cards/processing-semantics/" data-link-title="Processing Semantics" data-link-desc="說明 consumer 處理事件後業務結果是否正確，與投遞成功分屬不同責任">Processing Semantics</a> 的冪等要求）。AckWait 只能降低重複頻率、不能消除重複。</li>
</ol>
<h3 id="case-2discard-policy-選錯靜默丟訊息">Case 2：discard policy 選錯靜默丟訊息</h3>
<p><strong>徵兆</strong>：上游 publisher 一切正常、沒收到任何 error、但下游 consumer 發現訊息有缺口（seq 跳號）、或最舊的歷史訊息神祕消失。對帳時帳目對不上、但日誌裡找不到任何失敗紀錄。</p>
<p><strong>根因</strong>：兩種情況。其一、stream 用 <code>discard old</code>、流量超過 MaxMsgs / MaxBytes、最舊的訊息被靜默丟棄騰空間——這在「事件 log 需要完整 replay」的場景是資料遺失。其二、stream 用 <code>discard new</code>、滿了之後新訊息被拒、但 publisher 用的是 <em>Core NATS publish</em>（不等 stream ack）、所以 publisher 端看到「發送成功」、訊息其實沒進 stream。實機重現後者的危險：對一個 discard new 已滿的 stream 用 Core pub 與 JetStream-aware pub、結果完全不同：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Core pub（不等 ack）：    Published 8 bytes to &#34;dnew.x&#34;        ← 看似成功、實際丟失
</span></span><span class="line"><span class="ln">2</span><span class="cl">JetStream pub（等 ack）： nats: error: maximum messages exceeded (10077)  ← 正確報錯</span></span></code></pre></div><p><strong>修法</strong>：</p>
<ol>
<li><strong>publisher 一律用 JetStream-aware publish</strong>：等 stream 的 PubAck 回來才算發送成功、才能在 stream 滿、quorum 失效、subject 不匹配時收到明確 error。用 Core pub 發進 JetStream subject 等於放棄所有投遞保證。</li>
<li><strong>discard policy 對齊業務語意</strong>：事件 log（需要完整歷史）配 limits + 充足 MaxAge、絕不靠 discard old 當容量控制；任務佇列配 discard new + 上游 backpressure、滿了就讓 producer 慢下來而不是擠掉排隊任務。</li>
<li><strong>監控 discard 計數</strong>：stream 的 discard 不是錯誤狀態、不會觸發 alert。要主動監控訊息 seq 連續性與 stream 的訊息移除速率、把「非預期的 discard」變成可觀測訊號。</li>
</ol>
<h3 id="case-3leaf-node-斷線重連">Case 3：Leaf node 斷線重連</h3>
<p><strong>徵兆</strong>：邊緣端（工廠 / 店面）到中心 hub 的網路抖動、leaf connection 反覆斷開重連、hub 端看到某些 subject 的訊息延遲尖刺、邊緣端 reconnect 計數持續累加。網路恢復後、邊緣累積的訊息一次湧入 hub、造成 hub 端短暫的處理尖峰。</p>
<p><strong>根因</strong>：邊緣到中心是廣域網、品質不如資料中心內網。Leaf connection 斷線期間、邊緣端的本地 JetStream 持續收訊息並本地持久化（這正是 leaf node 的設計目的）；連線恢復後、累積的 backlog 一次同步到 hub、形成尖峰。若邊緣端沒有本地 JetStream、斷線期間的訊息直接丟失。</p>
<blockquote>
<p>以下根因與修法依 NATS 官方 leaf node 文件與 <a href="/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &#43; JetStream &#43; KV &#43; Object Store。">MachineMetrics</a> / <a href="/blog/backend/03-message-queue/cases/nats-iflow-ot-it-integration/" data-link-title="3.C41 i-flow：NATS 做 OT/IT 跨層整合 bus" data-link-desc="i-flow 每日 4 億筆 data operation、200&#43; OT/IT connector、客戶含 Bosch / Sto / Lenze、NATS 當邊緣到 central 整合 bus。">i-flow</a> case 描述、未在本文實機環境驗證。</p></blockquote>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>邊緣端必開本地 JetStream</strong>：把斷線容忍從「依賴網路不斷」改成「斷線期間本地持久化、恢復後同步」。這是 MachineMetrics 用 edge JetStream 取代 SQLite 的核心理由——工廠網路斷斷續續是常態、不是異常。</li>
<li><strong>hub 端對同步尖峰做 flow control</strong>：恢復連線後的 backlog 同步用 consumer 端的 pull batch 限速、避免邊緣 backlog 一次打爆 hub 的處理能力。</li>
<li><strong>監控 reconnect 與 latency</strong>：leaf 連線的 reconnect 次數與 subject mapping latency 是邊緣網路品質的直接訊號（對應 overview 排錯段「leaf node 連線不穩」）。reconnect 頻繁代表網路或 hub 容量要處理、不是調 leaf 參數能解。</li>
</ol>
<h3 id="case-4stream-replica-失去-quorum">Case 4：Stream replica 失去 quorum</h3>
<p><strong>徵兆</strong>：R3 stream 突然無法寫入、publisher 的 JetStream publish 卡住後回 <code>no responders available</code>；stream info 顯示 <code>Leader:</code> 欄位空白、多數 replica 標 OFFLINE。讀取可能還能從存活節點拿到舊資料、但寫入完全停擺。</p>
<p><strong>根因</strong>：JetStream 的 stream 用 Raft 複製、寫入需要多數派確認。R3 stream 需要至少 2 節點健康才有 quorum；同時失去 2 節點就只剩 1 節點、達不到多數、Raft 無法選出 leader、stream 變成無法寫入。實機重現：3 節點 cluster 的 R3 stream、停掉 2 個節點、stream info 顯示無 leader、JetStream publish 報錯：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">停 2 節點後 stream info：
</span></span><span class="line"><span class="ln">2</span><span class="cl">                       Leader:
</span></span><span class="line"><span class="ln">3</span><span class="cl">                      Replica: n1, current, seen 3.35s ago
</span></span><span class="line"><span class="ln">4</span><span class="cl">                      Replica: n2, outdated, OFFLINE, not seen
</span></span><span class="line"><span class="ln">5</span><span class="cl">                      Replica: n3, outdated, OFFLINE, not seen
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">此時 JetStream publish：
</span></span><span class="line"><span class="ln">8</span><span class="cl">                      nats: error: nats: no responders available for request</span></span></code></pre></div><p>恢復 1 個節點（回到 2/3 多數）後、Raft 立即重選 leader、stream 恢復可寫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">啟動 n2 後：
</span></span><span class="line"><span class="ln">2</span><span class="cl">                       Leader: n1 (506ms)
</span></span><span class="line"><span class="ln">3</span><span class="cl">                      Replica: n2, current, seen 499ms ago
</span></span><span class="line"><span class="ln">4</span><span class="cl">                      Replica: n3, outdated, OFFLINE, not seen, 4 operations behind</span></span></code></pre></div><p><strong>修法</strong>：</p>
<ol>
<li><strong>replica 數對齊容錯目標</strong>：要容忍 1 節點失效用 R3、容忍 2 節點用 R5；不要為了省資源把關鍵 stream 設 R1（單點、節點掛了 stream 直接不可用）。</li>
<li><strong>replica 跨 failure domain 散開</strong>：R3 的 3 個 replica 要落在不同 availability zone / rack、避免單一 AZ 故障同時帶走 2 個 replica 直接失去 quorum。</li>
<li><strong>監控 replica 健康而非只看 leader</strong>：stream info 的每個 replica 的 <code>current</code> / <code>outdated</code> / <code>OFFLINE</code> 狀態是 quorum 餘裕的直接訊號。R3 已經有 1 個 replica OFFLINE 時 quorum 餘裕只剩 0、要當成 P1 處理、不能等到第 2 個也掛才反應（對應 overview 排錯段「JetStream raft 不一致」）。</li>
</ol>
<h2 id="容量與規模判讀">容量與規模判讀</h2>
<p>JetStream 的配置在不同規模下適用性不同、超出範圍要換拓樸而非調參數。</p>
<table>
  <thead>
      <tr>
          <th>規模訊號</th>
          <th>適用拓樸</th>
          <th>換檔訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>單區、中等吞吐、需要 HA</td>
          <td>單 Cluster R3</td>
          <td>單區頻寬 / 節點數撐不住 → 加節點 reshard 或拆 stream</td>
      </tr>
      <tr>
          <td>跨 region / 跨雲、訂閱者分散各區</td>
          <td>Supercluster（多 Cluster + gateway）</td>
          <td>需要邊緣本地持久化 → 疊加 Leaf node</td>
      </tr>
      <tr>
          <td>大量邊緣點、網路不穩、邊緣要本地能力</td>
          <td>Leaf node hub-and-spoke</td>
          <td>邊緣點 &gt; 數百、每點要獨立運維 → 評估 managed（Synadia）</td>
      </tr>
  </tbody>
</table>
<p><strong>單 Cluster R3</strong> 是多數中等規模服務的起點：單區內高可用、JetStream Raft 處理節點故障、運維只有一套 cluster。撞到天花板的訊號是單區頻寬或單節點 disk / CPU 到上限、此時先評估加節點重分配或把熱 stream 拆出去、而不是急著上 supercluster。</p>
<p><strong>Supercluster</strong> 在訂閱者地理分散、或要求單區整個掛掉仍能服務時才值得引入。它的成本是跨區 gateway 的運維複雜度與跨區頻寬、不該為了「以後可能要跨區」提前鋪。Form3 的判讀是硬 SLA（500ms、region 全掛仍可用）逼出來的、不是預設架構。</p>
<p><strong>Leaf node hub-and-spoke</strong> 在邊緣點多、邊緣網路不穩、邊緣要本地持久化 / KV / 計算能力時適用。當邊緣點數量大到每點獨立運維成本不可接受、評估走 managed NATS（Synadia Cloud）把運維外包、而不是自建更大的 hub。</p>
<h2 id="整合與下一步">整合與下一步</h2>
<p>本文聚焦 JetStream stream / consumer / 拓樸的 implementation；以下是往上下游的銜接。</p>
<h3 id="回-vendor-overview-與相鄰章節">回 vendor overview 與相鄰章節</h3>
<ul>
<li>上游 vendor 頁：<a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS overview</a>——Core NATS vs JetStream 的選型判讀、排錯快速判讀、何時改走其他 broker</li>
<li>跨 vendor consumer 設計：<a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>——本文的 pull/push、ack、重投放回語言無關的 consumer 設計框架</li>
<li>投遞與處理語意基礎：<a href="/blog/backend/knowledge-cards/delivery-semantics/" data-link-title="Delivery Semantics" data-link-desc="說明事件投遞語意如何定義遺失、重複、順序與補償策略">Delivery Semantics</a> / <a href="/blog/backend/knowledge-cards/processing-semantics/" data-link-title="Processing Semantics" data-link-desc="說明 consumer 處理事件後業務結果是否正確，與投遞成功分屬不同責任">Processing Semantics</a> / <a href="/blog/backend/knowledge-cards/redelivery-loop/" data-link-title="Redelivery Loop" data-link-desc="說明同一訊息反覆投遞失敗如何消耗 consumer 容量">Redelivery Loop</a> 知識卡</li>
</ul>
<h3 id="對應-case">對應 case</h3>
<ul>
<li><a href="/blog/backend/03-message-queue/cases/nats-form3-multi-cloud-payments/" data-link-title="3.C35 Form3：NATS JetStream 多雲低延遲支付" data-link-desc="Form3 服務 Tier-1 銀行、500ms SLA、SNS/SQS 吃 300ms 預算、改 NATS&#43;JetStream 跨雲 6x 延遲改善。">3.C35 Form3</a>——Supercluster + Leaf node 跨雲低延遲支付、硬 SLA 驅動拓樸</li>
<li><a href="/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &#43; JetStream &#43; KV &#43; Object Store。">3.C37 MachineMetrics</a>——Leaf node + edge JetStream + KV + Object Store + 多租戶 auth 的完整邊緣案例</li>
<li><a href="/blog/backend/03-message-queue/cases/nats-iflow-ot-it-integration/" data-link-title="3.C41 i-flow：NATS 做 OT/IT 跨層整合 bus" data-link-desc="i-flow 每日 4 億筆 data operation、200&#43; OT/IT connector、客戶含 Bosch / Sto / Lenze、NATS 當邊緣到 central 整合 bus。">3.C41 i-flow</a>——多工廠 leaf node hub-and-spoke、運維成本驅動拓樸選型</li>
</ul>
<h3 id="後續可深入的議題">後續可深入的議題</h3>
<ul>
<li><strong>JetStream KV / Object Store</strong>：基於 stream 的 key-value 與 blob 儲存、何時用 NATS KV vs 真的 KV 服務（Redis / etcd）、見 overview 進階主題段</li>
<li><strong>Leaf node 多節點實機驗證</strong>：本文 Supercluster / Leaf node 段以 case + 官方文件敘述；補一篇 hub + edge 雙端 compose 的實機演練（含斷線注入、backlog 同步觀測）是自然延伸</li>
<li><strong>Subject mapping 與 transform</strong>：leaf node 跨層的 subject 重映射、跨 account import / export 的細部配置</li>
</ul>
]]></content:encoded></item></channel></rss>