有人認為模式只在設計階段有用。照工具箱的比喻,那就等於說「工具只在蓋房子時有用,修房子時就沒用了」。 事實是:只要用得好,模式在整個專案週期中都是有用的工具。
本節用與前一節相同的模式探索過程,解決那套「已經在運作」的系統所遇到的問題。
問題一:市場資料更新的閃爍#
交易員希望收到某支債券的新市場資料時,表格儲存格會閃爍,清楚標示變動。Java 客戶端收到帶新資料的訊息,觸發客戶端資料快取更新,最終造成表格閃爍。
效能資料顯示客戶端每秒收到好幾次更新,有些更新間隔不到一毫秒。
有兩個模式看起來能幫忙減緩訊息流:Aggregator 與 Message Filter。
先想到 Message Filter#
第一個念頭是用 Message Filter 控制流速——把「在參考訊息之後一小段時間內收到的更新」丟掉。例如忽略彼此間隔 5 毫秒內的訊息:過濾器快取上一則可接受訊息的時間,把接下來 5 毫秒內收到的全部丟掉。
其他應用程式或許承受不了這種程度的資料遺失,但因為價格更新頻率極高,這在我們的系統中完全可以接受。
每支債券約有 50 個顯示給使用者的資料欄位(包含價格),而不是每則訊息都更新每個欄位。若系統忽略連續的訊息,很可能就把重要資料丟掉了。

圖 13-16:以時間為基準的 Message Filter
改用 Aggregator#
Aggregator 用來把多則相關訊息調解成單一訊息,有可能減少訊息流。
作法是:保留第一則被聚合訊息中的債券資料副本,之後只更新後續訊息中新增或改變的欄位;最終再把聚合後的債券資料以一則訊息送給客戶端。
它值不值得?#
一個潛在缺點是:只有當「短時間內有許多關於同一支債券的訊息湧入」時,Aggregator 才能大幅減少訊息流量。
- 若 1000 則訊息落在 4 支感興趣的債券上 → 訊息流從 1000 降到 4
- 若 1000 則訊息落在 750 支債券上 → 只從 1000 降到 750,投入的力氣換來的收穫相對很小

圖 13-17:以 Aggregator 累積連續的部分更新
關鍵轉折:讓客戶端來控制節奏#
剩下的問題是:Aggregator 要怎麼知道何時該把聚合中的訊息送出? 模式描述了幾種演算法——經過一定時間後送出、資料集中所有必要欄位都齊了才送出,等等。
原因是 Aggregator 假設「消費其清空訊息的一方(這裡是客戶端)是 Event-Driven Consumer」——也就是依賴外部事件的消費者。
我們得把客戶端變成 Polling Consumer,讓它自己控制訊息流。
作法是:建立一條背景執行緒,持續循環走過那組債券,更新並閃爍自上次迭代以來的所有變動。
這樣客戶端就控制了「訊息何時被接收」,因而保證自己在高更新期間永遠不會被訊息淹沒。
實作很簡單:送一則 Command Message 給 Aggregator 發起更新,Aggregator 以一則含有「已更新欄位集合」的 Document Message 回應,由客戶端處理。
「選 Aggregator 而非 Message Filter」純粹是基於系統業務需求的決定。兩者都能解決效能問題,但用 Message Filter 會以犧牲系統資料完整性為代價。
問題二:正式環境的重大當機#
閃爍的效能修好後,系統上了正式環境。某一天整套系統掛掉——MQSeries 崩潰,連帶拖垮數個元件。
排查許久,最後追到 MQSeries 的 dead letter queue(Dead Letter Channel 的一種實作):這個佇列長到把整台伺服器拖垮。
檢視其中的訊息後發現它們全是已逾期的市場資料訊息。成因是「慢消費者」——處理訊息不夠快的消費者;訊息在等待被處理的期間逾時(見 Message Expiration),因而被送進 Dead Letter Channel。
Aggregator 這次不行#
合理的第一步是想用 Aggregator(我們剛用它解決過類似的閃爍速率問題)。但系統設計依賴客戶端立即把市場資料更新訊息轉給交易平台——系統無法等著收集訊息再聚合,因此 Aggregator 必須放棄。
Competing Consumers 也不行#
另外兩個處理「並行消費訊息」的模式是 Competing Consumers 與 Message Dispatcher。
Competing Consumers 的好處是並行處理進站訊息:同一通道上有數個消費者,每則進站訊息只由一個消費者處理,其他消費者則處理後續訊息。
Message Dispatcher 才是答案#
這達成了 Competing Consumers 的並行處理效益,卻能在發布訂閱通道上運作。
實作很簡單:
- 建立單一個名為 Dispatcher 的
JMSListener,它含有一組稱為 Performer 的其他JMSListener - Dispatcher 的
onMessage被呼叫時,它從集合中挑一個 Performer 來實際處理訊息
結果是一個永遠立刻返回的訊息監聽器,保證了穩定的訊息處理流動,不論訊息流速如何;而且它在發布訂閱通道上與在點對點通道上同樣好用。
有了這套基礎設施,客戶端幾乎能以任何速率接收訊息;即使客戶端收到後處理仍然慢,那也是「客戶端自己面對延遲處理與可能過時的市場資料」,而不是訊息在 JMS 通道中逾期。

圖 13-19:Message Dispatcher 在此情境中的作用
這個案例也展示了模式的極限#
我們遇到的效能問題,源自一個「不允許客戶端平行處理訊息」的設計缺陷。Message Dispatcher 大幅改善了問題,卻沒有完全解決它——因為真正的問題是客戶端本身成了瓶頸,而那是一千個模式也修不好的。
我們後來重構了訊息流架構,把訊息從 Pricing Gateway 直接路由到 Contribution Gateway,才真正解決。

圖 13-18:瓶頸所在
小結#
本章把模式套用到債券交易系統的數個面向上——從解決初期的前期設計問題,到修復一次幾乎讓人丟工作的正式環境當機。我們也看到這些模式早已存在於第三方產品、遺留元件,以及 JMS 與 TIBCO 訊息系統之中。
最重要的是:這些都是真實問題——與我們設計和維護自己系統時所經歷的架構、技術與業務問題是同一類。
希望讀完這套系統如何套用模式,能幫你更清楚地理解這些模式,也更知道如何把它們套用到自己的系統上。