Message Endpoint 就是應用程式連上訊息系統以收送訊息的方式。
身為應用程式設計師,當你對著 JMS 或
System.Messaging這類訊息 API 寫程式時,你寫的就是端點程式碼。若使用商業中介軟體套件,多數這類編碼已由廠商提供的函式庫與工具替你完成。
收與送都適用的模式#
- 封裝訊息程式碼——一般而言,應用程式不該知道自己正在用訊息傳遞與其他應用程式整合。它的多數程式碼寫的時候都不該想到訊息;只有在與他人整合的接點上,才該有一層薄薄的程式碼負責整合工作——當整合以訊息實作時,那層把應用程式接上訊息系統的薄程式碼就是 Messaging Gateway。
- 資料轉譯——收發雙方的內部資料表示法相同、且訊息格式也用同一套表示法,當然很棒;但情況往往不是這樣。此時用 Messaging Mapper 在應用程式格式與訊息格式之間轉換資料。
- 外部控制的交易——訊息系統內部使用交易;對外,預設每次 send 或 receive 方法呼叫各自跑在自己的交易中。但訊息的生產者與消費者可以選擇用 Transactional Client 從外部控制這些交易——當你需要把多則訊息批次處理、或需要把訊息與其他交易性服務協調在一起時,這很有用。
訊息消費者的模式#
貫穿全章的主題:節流#
**節流(throttling)**指的是應用程式控制自己消費訊息的速率。
任何伺服器都面臨的潛在問題是:大量客戶端請求可能壓垮伺服器。用遠端程序呼叫時,伺服器幾乎完全受制於客戶端的呼叫速率;用訊息傳遞時,伺服器同樣無法控制客戶端送請求的速率——但它能控制自己處理請求的速率。
應用程式不必以「訊息系統遞送的速度」去接收與處理訊息,它可以用可持續的節奏處理,讓多出來的訊息排隊等候。反過來說,若訊息堆積太多而伺服器還有餘力,它也能用並行的訊息消費者提高吞吐量。
成對出現的替代方案#
本章許多模式成對出現,代表互斥的替代方案。同一個應用程式可以讓某些端點採一種、某些端點採另一種,但單一端點只能實作其中一種。不同對之間的選擇可以組合,因而產生大量的實作選擇。
- 同步還是非同步消費者——選 Polling Consumer 或 Event-Driven Consumer。
- 訊息指派 vs. 訊息爭搶——最簡單的作法是 Competing Consumers:一個 Point-to-Point Channel 有多個消費者,由訊息系統的實作決定誰拿到訊息。若想控制「訊息對應到哪個消費者」的過程,就用 Message Dispatcher——它是單一個消費者,收到訊息後把它委派給執行者處理。
兩種作法都能靠限制消費者/執行者的數量來節流;而 Message Dispatcher 中的 dispatcher 還能實作明確的節流行為。
- 全收還是過濾——預設情況下,通道上遞送的任何訊息,對監聽該通道的任何端點都是可消費的。若消費者只想消費特定型別或描述的訊息,就用 Selective Consumer 描述自己願意接收什麼,訊息系統就只會把符合描述的訊息提供給它。
- 離線期間的訂閱——若某個訂閱者對某通道上發布的資料有興趣、將來也還會有興趣,但目前斷線或停機維護中呢?它會錯過期間發布的訊息嗎?預設會——訂閱只在訂閱者連線期間有效。要避免錯過,就讓它成為 Durable Subscriber。
- 冪等性——有時同一則訊息會被遞送不只一次(訊息系統無法確定它是否已成功送達,或通道的服務品質為了效能而被調降)。但接收端往往假設每則訊息恰好被遞送一次,重複處理就會出問題。設計成 Idempotent Receiver 的接收端能處理重複訊息,防止它們在應用程式中造成麻煩。
- 同步還是非同步服務——應用程式該把服務暴露成同步呼叫(遠端程序呼叫)還是非同步(訊息傳遞)?
不同客戶端可能偏好不同作法,不同情境也可能要求不同作法。既然很難只挑一邊,那就兩者都要:Service Activator 把 Message Channel 接到應用程式中的同步服務,訊息一到就喚起該服務——同步客戶端直接呼叫服務,非同步客戶端則靠送訊息來喚起它。
兩個貫穿的主題#
交易式用戶端與其他模式的搭配困難#
- Event-Driven Consumer 通常無法正確地從外部控制交易
- Message Dispatcher 必須小心設計才能做到
- 從外部管理交易的 Competing Consumers 可能踩到嚴重問題
最保險的用法是搭配單一個 Polling Consumer,但那未必是令人滿意的方案。
特別提及:JMS 風格的 Message-Driven Bean
JMS 風格的 message-driven bean(MDB,一種 Enterprise JavaBean)值得特別一提。一個 MDB 同時是:
- Event-Driven Consumer
- 支援 J2EE 分散式(XAResource)交易的 Transactional Client
- 能被動態池化成 Competing Consumers——甚至對 Publish-Subscribe Channel 也可以
這是一個在自己的應用程式碼中實作起來既困難又繁瑣的組合,但相容的 EJB 容器(如 BEA WebLogic、IBM WebSphere)已把它做成現成功能。
MDB 框架怎麼實作的?本質上,容器實作了一個帶有動態大小之可重用執行者池的 Message Dispatcher,其中每個執行者用自己的 session 與交易來消費訊息。
單一端點常組合多個模式#
最後請記得:單一個 Message Endpoint 很可能組合本章的數個模式。
- 一組 Competing Consumers 可以實作成同時是 Selective Consumer 的 Polling Consumer,並對應用程式中的某個服務扮演 Service Activator
- 一個 Message Dispatcher 可以同時是 Event-Driven Consumer 與 Durable Subscriber,並使用 Messaging Mapper
- 不論端點還實作了什麼模式,它都該是一個 Messaging Gateway
所以別想「該用哪一個模式」,要想「該用哪些組合」——這正是用模式解決問題的美妙之處。