脈絡:延續訂單處理的例子,假設公司管理層要向大客戶發布價格變動與促銷訊息。價格一變就送訊息通知客戶;跑促銷活動(例如十一月所有 widget 打九折)時也一樣。
但有些客戶只對特定商品的價格更新或促銷有興趣——我主要買 gadget,就未必想知道 widget 有沒有特價。
幾條走不通的路#
只訂閱相關通道#
最基本的方式是只訂閱承載相關訊息的通道,利用 Publish-Subscribe Channel 天生的路由能力。例如開一個 widget 更新通道、一個 gadget 更新通道,客戶自由訂閱其一或兩者。優點是新訂閱者能加入而不必改動系統。
例如若要讓消費者能接收「widget 或 gadget 降價超過 5%、10% 或 15%」的所有訊息,就已經需要 6 個通道(2 種商品 × 3 個門檻)。這既難管理,又因為配置了大量通道而消耗可觀資源。
讓路由器送給多個目的地#
我們可以改造 Content-Based Router 讓它把訊息路由給多個目的地(也就是 Recipient List 的概念)。這種預測式路由只送相關訊息給每個接收者,接收者不必額外做事。
但這等於把「維護每一位訂閱者的偏好」的負擔壓在訊息發起者身上。若接收者清單或他們的偏好變動頻繁,這會是維護惡夢。
廣播給所有元件,讓元件自己濾掉#
這假設我們能控制那些元件。在許多整合情境中並非如此——我們面對的是套裝應用、遺留應用,或不在本組織控制下的應用程式。
而且把過濾邏輯放進元件內部,會讓元件相依於特定型別的訊息。例如客戶若使用一個通用的價格監看元件,他可能想同時用它看 widget 與 gadget,但套用不同的條件。
解法#
Message Filter 只有一個輸出通道:
- 訊息內容符合準則 → 路由到輸出通道
- 不符合 → 丟棄
在我們的例子中,我們會定義單一個 Publish-Subscribe Channel 供每位客戶自由監聽,客戶再用 Message Filter 依自己選定的準則(商品類型、價格變動幅度)剔除訊息。
Message Filter 可以被描繪成 Content-Based Router 的特例:它把訊息路由到輸出通道,或路由到**「空通道」**——一個會丟棄所有發布到它的訊息的通道,類似許多作業系統中的
/dev/null,或 Null Object 模式。

圖 7-2:Message Filter 解法示意
無狀態與有狀態的過濾器#
widget/gadget 的例子是無狀態的 Message Filter:它只檢視單一訊息,僅根據該訊息所含資訊決定是否放行,因此不必跨訊息維護狀態。
無狀態元件的好處是可以並行跑多個實例來加速處理。
但 Message Filter 不一定得無狀態。
常見的有狀態例子是用 Message Filter 剔除重複訊息:假設每則訊息有唯一識別碼,過濾器就保存過去訊息的識別碼,把每則新訊息的識別碼與清單比對來辨識重複。
訊息系統內建的過濾功能#
階層式通道與萬用字元#
有些發布訂閱系統允許為通道定義階層結構(多數 JMS 實作都可以)。例如把促銷發布到 wgco.update.promotion.widget:
- 訂閱
wgco.update.*.widget→ 收到所有與 widget 相關的更新(促銷與價格變動) - 訂閱
wgco.update.promotion.*→ 收到 widget 與 gadget 的所有促銷,但沒有價格變動
通道階層讓我們能用附加限定參數的方式細化通道語意,客戶因而能靠通道名稱指定額外條件來過濾訊息。
但相較於 Message Filter,階層式命名的彈性仍然有限——例如「只放行價格變動超過 11.5% 的訊息」,就很難用通道名稱表達。
Selective Consumer 與訊息選擇器#
其他訊息系統在接收端應用程式內提供 Selective Consumer 的 API 支援:訊息選擇器是在應用程式看到訊息之前,先對進站訊息的表頭或屬性元素求值的運算式;條件不成立就忽略該訊息、不交給應用邏輯。
訊息選擇器等於一個內建於應用程式中的 Message Filter。使用它仍需要修改應用程式(在 EAI 中往往辦不到),但選擇規則的執行是內建在訊息基礎設施中的。
- Selective Consumer 不消費那些不符合準則的訊息
- Message Filter 把所有訊息從輸入通道移除,只把符合準則的發布到輸出通道
把過濾條件註冊給基礎設施的好處#
把過濾運算式註冊給訊息基礎設施的一個好處是:基礎設施能據此做出聰明的內部路由決策。
假設接收端與發送端位於不同網段(甚至跨越網際網路),把訊息一路送到 Message Filter 才發現要丟棄它,相當浪費。但我們又想用 Message Filter 機制,好讓控制權在接收者而非中央路由器手上。
若 Message Filter 是訊息基礎設施提供給訂閱者的 API 的一部分,基礎設施就能把過濾運算式往來源端推近——既維持「控制權在訂閱者」的原意,又讓基礎設施避免不必要的網路流量。這種行為很像動態的 Recipient List。
用過濾器實作路由功能#
我們可以用「一個廣播通道 + 一組 Message Filter」達成與 Content-Based Router 等價的功能。假設有兩個接收者:Gadget 只對 gadget 訊息有興趣,Widget 只對 widget 訊息有興趣。
- 選項一:Content-Based Router——評估每則訊息的內容,預測式地路由到適當的接收者。
- 選項二:廣播通道 + 一組 Message Filter——把訊息廣播到 Publish-Subscribe Channel,每個接收者各配一個過濾器剔除不想要的訊息。
兩者的差異#
| Content-Based Router | Pub-Sub Channel + Message Filter |
|---|---|
| 恰好一個消費者收到每則訊息 | 可以有多個消費者消費同一則訊息 |
| 中央控制與維護——預測式路由 | 分散控制與維護——反應式過濾 |
| 路由器必須知道參與者;參與者增減時路由器可能得更新 | 不需要知道參與者;增減參與者很容易 |
| 常用於業務交易(例如訂單) | 常用於事件通知/資訊性訊息 |
| 搭配佇列式通道通常更有效率 | 搭配發布訂閱通道通常更有效率 |

圖 7-3:選項一:使用 Content-Based Router

圖 7-4:選項二:使用廣播通道搭配一組 Message Filter
怎麼選#
有時由所需功能決定——若需要讓多個接收者處理同一則訊息,就必須用 Pub-Sub Channel + Message Filter。但多數情況下,決定因素是「路由決策該由哪一方控制與維護」:我們要保留中央控制,還是把它下放給接收者?
例如我們提供給頂級客戶的特別折扣,就不該送給非頂級客戶再期待他們忽略這些優惠。
網路流量的考量也會影響決定:
- 若有高效的廣播方式(例如內網用 IP multicast),用過濾器會非常有效率,還能避免單一路由器成為潛在瓶頸。
- 但若資訊要跨網際網路傳送,我們就只能用點對點連線;此時路由器有效率得多——它避免了把個別訊息送給所有參與者。
若想把控制權交給接收者、卻又因為網路效率必須使用路由器,可以採用動態的 Recipient List:它讓接收者表達自己的偏好並存進資料庫或規則庫,訊息抵達時再轉發給所有條件符合的接收者。