脈絡:企業有兩套獨立的應用程式需要溝通,而且最好是透過訊息傳遞。

訊息系統不是一個大水桶#

一群應用程式一旦有了訊息系統可用,很容易讓人以為任何應用程式隨時都能和任何其他應用程式溝通。但訊息系統並不會神奇地把所有應用程式都連起來。

同樣地,情況也不是「應用程式隨機把資訊丟進訊息系統,其他應用程式隨機撈走碰到的東西」——即使這行得通,效率也糟透了。真實情況是:送出資訊的應用程式知道那是哪一類資訊,而想接收的應用程式要找的也不是隨便什麼資訊,而是它用得上的特定種類。

所以訊息系統不是一個「大家往裡丟、往裡撈」的水桶,而是一組連線——讓應用程式以預先決定、可預測的方式傳輸資訊。

解法#

應用程式有資訊要傳達時,不是把它甩進訊息系統,而是加進某個特定的通道;接收端也不是隨機撿,而是從某個特定的通道取出。

送出資訊的一方未必知道最終是哪個應用程式會來取,但它可以確信:不論誰來取,那一方都是對這份資訊有興趣的。原因在於訊息系統會為不同類型的資訊準備不同的通道——送件方會挑一個「專門用來傳這類資訊」的通道,收件方也會依自己想要的資訊類型挑選通道。

通道是邏輯位址#

通道是訊息系統中的邏輯位址,實際怎麼實作取決於訊息系統產品:

  • 也許每個訊息端點與其他每個端點都有直接連線
  • 也許它們全都透過一個中央樞紐相連
  • 也許好幾個邏輯通道被設定成同一條實體通道,但仍能分清哪則訊息該去哪個目的地

已定義的那組邏輯通道,把上述設定細節對應用程式藏了起來

通道不會自動長出來#

訊息系統不會預先配好應用程式所需的所有通道。設計應用程式與其間通訊的開發者必須先決定需要哪些通道,接著由安裝訊息系統軟體的系統管理員把這些通道設定起來。

有些訊息系統實作支援在應用程式執行期間建立新通道,但這其實沒什麼用——因為除了建立通道的那個應用程式之外,其他應用程式也得知道新通道的存在才能開始使用它。因此可用通道的數量與用途,傾向在部署時就固定下來。

初學者最常被騙到的一點是:「建立通道」到底該做什麼。

你可以寫 JMS 程式碼呼叫 createQueue,或寫 .NET 程式碼 new MessageQueue——但這兩段程式碼都沒有在訊息系統中配置一個新的佇列資源。它們只是取得對「訊息系統中已經存在、且早已透過管理工具另外建立好」的資源的存取權。

通道便宜,但不免費#

設計通道時還要記住:通道便宜,但不是免費的。

應用程式需要多個通道來傳輸不同類型的資訊,也需要把同一份資訊傳給許多其他應用程式。每個通道都需要記憶體來承載訊息;持久化通道還需要磁碟空間。即使企業系統有無限的記憶體與磁碟,任何訊息系統實作通常都有「能穩定服務多少通道」的硬性或實務上限。

因此:應用程式需要時就開新通道;但如果需要成千上萬個通道、或擴展方式可能導致需要成千上萬個通道,就得挑一套高度可擴展的訊息系統實作,並實測其擴展性

Datatype Channel 幫你判斷何時該再開一個通道;Selective Consumer 則讓一條實體通道在邏輯上表現得像多個通道。

邊欄:一點訊息傳遞的詞彙

透過 Message Channel 溝通的應用程式,我們該怎麼稱呼?外頭有一堆大致等價的說法:

  • 最通用的大概是 Sender / Receiver(寄件者/收件者)——一個應用程式把訊息送到通道,供另一個應用程式接收。
  • 另一組常見說法是 Producer / Consumer(生產者/消費者)
  • 同樣流行的是 Publisher / Subscriber(發布者/訂閱者)——它更貼近 Publish-Subscribe Channel,但也常被泛用。
  • 有時我們也說某個應用程式在通道上監聽(listen),另一個在對它說話(talk)
  • 在 Web services 的世界裡,一般講 Requester / Provider(請求者/提供者),通常隱含「請求者送訊息給提供者並收到回應」。
  • 更早以前叫 Client / Server(客戶端/伺服器)——意思等價,只是聽起來沒那麼潮。

接下來就開始混亂了:在 Web services 情境中,送訊息給提供者的應用程式有時被視為該服務的 Consumer——可以理解成「消費者送訊息給提供者,然後消費那個回應」。所幸這種用法只出現在 RPC 情境。

收送訊息的應用程式也可能被稱為訊息系統的 Client;更精確的說法是 EndpointMessage Endpoint

邊欄:通道命名

如果通道是邏輯位址,那這些位址長什麼樣子?細節取決於訊息系統的實作,但多數情況下通道以英數字名稱指涉,例如 MyChannel

許多訊息系統支援階層式的通道命名,讓你能像檔案系統的資料夾與子資料夾那樣組織通道。例如:

MyCorp/Prod/OrderProcessing/NewOrders

這表示一個屬於 MyCorp 正式環境應用程式、內含新訂單的通道。

接下來#

  • 通道有兩種:Point-to-Point ChannelPublish-Subscribe Channel
  • 在同一個通道上混用不同資料型別會造成大量混淆——用 Datatype Channel 避免它。
  • 使用訊息傳遞的應用程式常會受惠於一個專門處理無效訊息的通道:Invalid Message Channel
  • 想用訊息傳遞卻沒有訊息客戶端可用的應用程式,仍可透過 Channel Adapter 接上訊息系統。
  • 一組設計良好的通道會構成 Message Bus,對一整群應用程式而言就像一套訊息 API。
範例:股票交易與各家平台的通道建立方式

股票交易#

當股票交易應用程式要下單時,它把請求放到一個專供交易請求使用的 Message Channel 上;另一個處理交易請求的應用程式,就到同一個通道上找待處理的請求。若請求端需要查股票報價,它多半會用另一個專為報價設計的通道,好讓報價請求與交易請求分開。

J2EE JMS 參考實作#

J2EE SDK 附有 JMS 的參考實作,參考伺服器以 j2ee 指令啟動,通道則必須用 j2eeadmin 工具設定(可設定 queue 與 topic):

j2eeadmin -addJmsDestination jms/mytopic topic
j2eeadmin -addJmsDestination jms/myqueue queue

通道被管理(建立)之後,JMS 客戶端程式碼才能存取它:

Context jndiContext = new InitialContext();
Queue myQueue = (Queue) jndiContext.lookup("jms/myqueue");
Topic myTopic = (Topic) jndiContext.lookup("jms/mytopic");

注意:JNDI 查詢並沒有建立 queue——它早就被 j2eeadmin 建好了。查詢只是在 Java 中建立一個 Queue 實例,用來模擬並存取訊息系統中的那個佇列結構。

IBM WebSphere MQ#

若用的是實作了 JMS 的 IBM WebSphere MQ for Java,就用它的 JMS 管理工具建立目的地:

DEFINE Q(myQueue)

WebSphere MQ 在沒有完整 WebSphere Application Server 的情況下不含 JNDI 實作,因此不能像 J2EE 範例那樣用 JNDI 查詢,而必須透過 JMS session 取得:

Session session = // create the session
Queue queue = session.createQueue("myQueue");

Microsoft MSMQ#

MSMQ 提供多種建立佇列的方式。你可以用 Microsoft Message Queue Explorer 或電腦管理主控台建立、設定屬性與刪除佇列,也可以用程式碼建立:

using System.Messaging;
...
MessageQueue.Create("MyQueue");

建立之後,應用程式透過建立 MessageQueue 實例來存取它:

MessageQueue mq = new MessageQueue("MyQueue");

圖 3-2:在 MSMQ 中以電腦管理主控台建立佇列