很久很久以前——大概就是你第一份工作那個系統——兩個服務要溝通,做法只有一種:A 想通知 B,就直接呼叫 B 的 API。再土一點的,A 直接把資料寫進 B 的資料庫。能動,就先這樣。

這條線一開始很好用。直到系統長大。

直連的甜蜜期,跟它後來的帳單

A 直接呼叫 B,問題不會馬上出現,會慢慢長出來。

第一個坑:B 掛了,A 跟著卡住。你只是想通知一聲「有筆新訂單」,結果 B 剛好在重啟,A 這邊的請求就卡在那裡等超時,甚至整條流程失敗。兩個本來各自獨立的服務,命被綁在一起了。

第二個坑:B 忙不過來,A 會把它打爆。促銷一開,A 每秒送一萬筆過去,B 只能處理三千,剩下七千不是排隊排到天荒地老,就是直接被拒。A 完全不知道 B 的死活,只管一直塞。

第三個坑,是最陰的。今天除了 B,C 也想知道「有新訂單」,明天又多一個 D。每加一個下游,你就得回去改 A 的程式碼,讓它「認識」C、認識 D,一個個去呼叫。三個服務要互相通知,你要拉三條線;十個服務,理論上要拉將近一百條。這團線最後會纏成一坨沒人敢動的毛球。

說穿了,直連真正的成本不在「慢」,在耦合——A 得認識每一個要通知的對象,還得等對方剛好活著、剛好有空。這才是後來所有麻煩的根。

中間那些年,大家怎麼湊合

發現直連會爆之後,工程師們試過幾種土炮解法。

最常見的是拿資料庫當信箱:A 把訊息寫進一張表,B 開個排程每幾秒去撈一次。這招確實把兩邊拆開了一點——A 寫完就走,不用等 B。但輪詢有延遲(你把間隔設 5 秒,訊息就可能慢 5 秒),把關聯式資料庫當佇列用,量一大鎖競爭就開始噴,而且撈完還要記得刪、要處理撈到一半掛掉的情況。弄到後來,你發現自己在用一個不擅長這件事的工具,硬幹一個佇列的活。

另一派是繼續同步呼叫,但加上 retry、加上 circuit breaker,B 掛了就重試、重試太多次就先跳過。這治了一點標,但那三個坑的根還在:A 依然得認識 B,依然得在意 B 活著沒。

轉捩點:LinkedIn 把「訊息」重新想成一條 log

2010 年前後,LinkedIn 正在爆炸性成長,內部幾百個系統互相通知的線纏成前面說的那種毛球。他們沒有再多接幾條線,而是退一步問了個更根本的問題:能不能在中間放一個東西,讓「發訊息的人」跟「收訊息的人」徹底不用互相認識?

他們做出來的東西叫 Kafka,2011 年開源。它的核心概念簡單到有點反直覺:把訊息當成一條只能往後追加、不能改的 log(就是一長串按順序排好的紀錄)。生產者只管把訊息往 log 屁股後面丟,完全不用管誰會來讀;消費者各自記著「我讀到第幾筆了」,照自己的速度往後撈。

打個比方你就懂了。直連像打電話——你得知道對方號碼,對方還得剛好接得起來,兩個人必須同時在線。Kafka 像公司的公佈欄:你把公告貼上去就走人,不用知道誰會看、也不用等誰在場;有空的人自己過來讀,A 部門讀 A 部門的進度,B 部門讀 B 部門的,互不干擾。更妙的是這張公佈欄不會擦掉,新來的人可以從頭讀,出錯的人可以倒帶重讀。

這根 log 一放進去,那團 N×N 的毛球就解開了。生產者只認識 Kafka,消費者也只認識 Kafka,中間的線從「每個人拉到每個人」變成「每個人拉到中間這一根」。

所以它到底買到了什麼

這裡是重點,也是最多人一開始會誤會的地方。

大部分人第一次聽到訊息佇列,理解都是「喔,就是為了非同步、為了讓系統跑更快嘛」。速度確實有——A 丟完就走不用等,尖峰時訊息先在 log 裡排隊、B 照自己節奏慢慢消化(這叫削峰)。但這些都只是副作用。

它真正買到的,是解耦:讓生產者跟消費者在「時間」和「認知」兩個維度上都鬆綁。時間上,兩邊不用同時在線,B 半夜掛了,A 白天丟的訊息還乖乖躺在 log 裡等它回來;認知上,兩邊不用互相認識,你要新增一個消費者 D,A 一行程式碼都不用改,D 自己來訂閱就好。

回頭看前面那三個坑——B 掛了 A 跟著卡、B 被打爆、每加一個下游都要改 A——它們全是同一個病:耦合。Kafka 做的,就是花「多養一個中間人」的成本,一次把這個耦合買斷。

別急著到處塞

當然,中間人不是免費的。多一個 Kafka,就多一個要部署、要監控、要擴容的元件;訊息可能重複投遞、可能亂序,「剛好只處理一次」(exactly-once)是出了名難搞的題目;而且一旦走非同步,系統變成最終一致,你要追一筆資料的來龍去脈會比同步呼叫難得多。

所以什麼時候別用?如果你就兩個服務、一次簡單的同步呼叫能解決,硬塞一個 Kafka 進去,只是把一個小問題包裝成一個分散式難題。如果你要的是「送出去、馬上拿到強一致的回應」,那更不該用——公佈欄本來就不是拿來即問即答的。用 Spring Boot 接 Kafka,程式碼可能就 KafkaTemplate 送、@KafkaListener 收幾行的事,接起來很快;但「該不該接」這個判斷,比怎麼接值錢得多。

從直連、到拿 DB 硬湊、再到一條 log,這條路其實還沒走完——再往下是把整個系統都當成「事件的串流」在設計。但那是另一個故事了。現在你只要在按下「加一個訊息佇列」之前,先回答一個最樸素的問題:你這兩個系統,到底需不需要同時活著、互相認識?如果不需要——恭喜,這時候才輪到 Kafka 上場。