Kafka 作為「大規模訊息骨幹」的預設答案已經超過十年,但這個地位放在自架 VPS 情境下越來越尷尬。一個能實際上生產的三節點 KRaft 叢集,起碼要 24 GB 以上的 RAM、加起來十幾顆 CPU 核心,還得處理 JVM tuning、topic partition 規劃、跨機房同步。對只有幾個服務要串接、每秒訊息量在中低水位的團隊來說,這個成本並不合理。
NATS 這幾年一直是 Kafka 之外的另一條路,但在功能面上有幾個明顯空缺——沒有原生的 atomic batch、缺 delayed message、也缺乏 counter 語意。NATS Server 2.12 在 2025 年 9 月釋出,一次把這三件事補上,2.14 的 release candidate 又把 cron 排程跟 batch ingest 排進來。到這個時點,NATS JetStream 已經不再是「輕量但功能少」的替代品,而是能認真對打 Kafka 的自架選項。
自架 Kafka 為什麼一直不舒服
Kafka 的門檻不在於它慢或不穩,而在它的營運成本被設計得很高。過去 Kafka 依賴 ZooKeeper 管 metadata,光是 ZooKeeper 本身就是一組三節點的 JVM 應用。3.3 之後 KRaft 模式進入 GA、3.5 之後預設啟用,但這只是把 ZooKeeper 內化,並沒有把資源需求變小——生產環境的 KRaft controller quorum 每臺仍然建議 4 GB 以上 heap,加上 broker 本身的 heap 跟 page cache 需求,記憶體用量隨便就破 8 GB。
第二個痛點是 topic 設計。Kafka 的 partition 是為了平行消費而存在,設計時就得預估 consumer 的最大平行度。partition 開太少擴不出去、開太多 metadata 又炸開;跨機房或跨區複製還得配 MirrorMaker 2 這樣的獨立元件。這些取捨在 GB 等級以上的日常流量才划得來,對只有幾個 microservice 需要事件通知的情境,複雜度遠超收益。
第三是升級路徑。Kafka 的 KRaft 遷移在正式釋出後仍然充滿驚喜,光是把運行中的 ZooKeeper 模式叢集切到 KRaft 就是一套完整的 migration 流程;4.x 之後 ZooKeeper 完全移除,還在跑舊模式的團隊變成必須先升到過渡版本再升過去。這種一週級別的維運工作,對兩三人的小團隊難以承受。
NATS 的定位一直不同
NATS 的架構出發點跟 Kafka 相反。它把「訊息路由」設計成應用之間的低延遲連接組織,核心行為只有 pub/sub、request/reply、queue group 三種模式。整臺 server 是一支 Go binary,壓縮後大約 20 MB,設定檔通常十幾行就寫完。叢集用 full-mesh 自組,加一個節點只要在設定裡多寫一行 route。
過去這個設計的取捨很明顯:NATS 不做持久化。訊息如果沒被訂閱者當下接到就消失,這個模型能撐住每秒百萬級的 fanout 吞吐量,代價是無法拿來做事件溯源、audit trail、或需要 replay 的工作。
JetStream 就是為了補這個缺口做出來的。它把 stream 的持久化能力疊在 core NATS 之上,同一支 binary、同一個 protocol、同一組客戶端,只要在設定開 jetstream: { store_dir: /data } 就能用。stream 支援 file 或 memory 儲存、可以設 replicas 做 quorum 複製、consumer 有 push 跟 pull 兩種模式,行為上跟 Kafka 的 consumer group 對得起來。
2.12 補了哪些過去被質疑的空缺
過去對 NATS 最常見的技術質疑集中在三件事:一次寫多筆訊息無法原子化、沒有排程延遲送出的機制、缺乏 counter 語意。這幾件事在 Kafka 這邊都有現成方案,於是變成「NATS 適合輕量情境、上規模還是要 Kafka」的分水嶺。2.12 這個版本把這條線抹掉。
Atomic batch publish 讓 client 可以把 N 筆訊息包在一個原子操作裡送出。訊息會先在 server 端 staging 驗證,全部通過才 commit,不會出現半途失敗、部分寫入的情況。這對 outbox pattern 這類需要跟資料庫 transaction 對齊的場景直接可用,過去要靠 idempotent 設計或去重 window 撐過去的工作,現在有原生語意可以依賴。
Delayed message scheduling 讓訊息可以指定 X 秒後才被投遞。這個功能長期以來是 SQS 相對 Kafka 的優勢之一,也是很多團隊為了做延遲任務去多架一組 Redis + BullMQ 的原因。2.12 把它塞進 stream 語意,工作排程、重試 backoff、預約通知這類需求不再需要額外元件。2.14 排入了 cron 表達式,接下來連週期性排程都會在 stream 內部完成。
Distributed counters 把 stream 當作可增減的計數器用。每個 subject 對應一個 big.Int、沒有上限,可以透過 stream mirroring 或 sourcing 做 CRDT 聚合。這個語意過去在 NATS 上得手動實作,原生支援之後,計數類的 metric、限流、庫存扣減都變得直接。
除了這三個功能,2.12 也加入 mirror 可以晉升成 primary 的能力,等於補上一條原地容災切換路徑。舊版本的 mirror 只能 read-only,出事時要切主寫入還得手動搬 stream 定義。
資源占用差距在哪
一個實務可跑的三節點 NATS JetStream 叢集,官方建議每臺至少 4 vCPU、8 GB RAM。這個數字看起來跟 Kafka 差不多,但要注意兩個結構性差異。第一,NATS 的記憶體使用主要來自 file store cache 跟 metadata,本質上是 Go runtime 的行為,設 GOMEMLIMIT 就能穩定壓在指定值以內;不像 JVM 的 heap 需要慢慢摸索 G1、ZGC 跟各種 flag 的組合。第二,NATS 沒有 ZooKeeper 或 KRaft controller 這種獨立元件——三個節點跑同一支 binary、彼此對等,故障恢復不會涉及 metadata quorum 的額外協調。
對訊息量在每秒幾千到幾萬筆的 workload,NATS 單節點就能勝任,三節點叢集完全是為了 HA 才存在。同樣的流量拿 Kafka 跑,光是 broker 本身就吃掉更多資源,還要處理 partition rebalance 的 GC 壓力。臺灣 VPS 常見的 4 vCPU / 8 GB 規格,跑 NATS 三節點叢集是舒服的,跑 Kafka 則在勉強線上。
什麼情況下仍該選 Kafka
有幾種情境 Kafka 仍然是更好的選擇,不必為了輕量硬去用 NATS。
多組獨立的下游團隊要以不同方式消費同一份資料流時,Kafka 的 partition 模型跟 consumer group offset 管理設計得成熟。想像一份訂單事件同時餵給 data warehouse、fraud detection、通知服務、報表引擎,四組都需要各自的 offset、各自的重播能力、各自的 lag 監控——這個場景 Kafka 生態的 Connect、Streams、Schema Registry 一路串起來,NATS 生態要拼湊得多。
要跟 Kafka 生態圈的既有工具打通時,Kafka 的相容性難以替代。Debezium 對接 CDC、Flink 或 Materialize 做 stream processing、Iceberg 的 sink connector,這些都是 Kafka wire protocol 為主的世界。NATS 有 Kafka bridge 但終究是橋接。
單一 topic 每秒突破十萬筆訊息、單筆訊息 MB 級的極端吞吐情境,Kafka 的順序寫入 log segment 架構在硬體上仍然是最有效率的。這種規模的自架情境通常已經進入專職 SRE 團隊管理的階段,成本考量的優先度早已被穩定性壓過。
反過來說,服務數量在十個以下、事件流量在每秒幾千筆以內、團隊想把訊息基礎設施的營運成本壓到最低,NATS 現在幾乎沒有拒絕的理由。
部署起來大概長什麼樣
單節點 JetStream 在 VPS 上開起來大約是這樣:
1 | jetstream { |
多節點就多寫 cluster 區塊,把三個 IP 互相寫進 routes 就完成。stream 的建立可以用 nats CLI 或 client library 動態做,不像 Kafka 需要事先開一輪 topic 設計會議。
要塞進 Docker、Kubernetes 也很直接。官方 Helm chart 把三節點 StatefulSet、PVC、跟 metrics exporter 都預設好,helm install 完就是能跑的叢集。這也是 NATS 相對 Kafka 一直維持的一個優勢——同一份設定可以從單機 VPS 一路擴展到多節點 K8s,不用改架構。
儲存端要注意的是 file store 走 OS page cache,NVMe SSD 的效能會直接反映在 stream 寫入延遲上。臺灣 VPS 的 NVMe 環境下,單節點 stream 寫入 latency 落在 sub-millisecond 是常態;跑在 EBS 或雲端 block storage 上,這個數字會拉高兩到五倍,對延遲敏感的應用要納入考量。
從 Kafka 遷過去的路徑
實務上很少會有「一次全部遷過去」的情境,比較常見的是逐個 topic 評估。事件流量小、consumer 少、沒有跟 Debezium 或 Flink 串接的 topic 可以優先搬。NATS 有官方的 Kafka bridge,讓兩邊在遷移期間並行——來源 Kafka 的訊息會被 bridge 轉發到 NATS stream,下游服務可以逐個切換 client,不必一刀兩斷。
真正需要注意的是資料模型的重新設計。Kafka 的 topic + partition 對應到 NATS 的 stream + subject,subject 支援分層命名(例如 orders.tw.web.created)以及 wildcard 訂閱,這個模型比 Kafka 的 partition key 靈活得多,但也意味著遷移不是 1:1 對應,而是重新規劃 subject 拓撲。這一步花掉的時間往往比技術遷移本身多。
訊息骨幹放回自己控制的機器
自架訊息系統的難度過去被 Kafka 拉高過一次。NATS 這幾年慢慢把功能面補齊到能對打 Kafka,2.12 之後幾乎不存在「因為缺功能不能用」的情境。剩下的取捨變回單純的規模跟生態問題,選擇邏輯終於變得健康。
跑訊息骨幹最直接影響體驗的兩個變數是網路穩定性與磁碟延遲。NCSE Network 的 VPS 主機位於臺灣是方電訊機房,搭載 Intel Gold CPU 與 NVMe SSD,同機房跨機節點延遲可壓在毫秒等級,適合架 NATS 三節點叢集或 Kafka broker cluster。想把訊息中間件從雲端託管拉回可控環境,臺灣本地的 VPS 是可以直接接的下一步。