脈絡:應用程式正用訊息傳遞來做遠端程序呼叫(RPC)或傳送文件。

訊息傳遞失去了 RPC 的一項保證#

RPC 的一個好處是它被喚起在單一個遠端行程上:要嘛那個接收端執行了這個程序,要嘛沒有(於是拋出例外)。而且因為只被呼叫一次,它就只執行一次。

但改用訊息傳遞後,一旦呼叫被包成 Message 放上 Message Channel,可能有許多接收者都看得到它、都決定去執行那個程序

幾條走不通的路#

  • 禁止一個通道有多個接收者——訊息系統可以這麼做,但這會不必要地限制那些想把資料送給多個接收者的呼叫端。
  • 讓通道上所有接收者互相協調,確保只有一個真的執行——這很複雜、造成大量通訊開銷,而且會增加本來各自獨立的接收者之間的耦合

一個通道有多個接收者其實是我們想要的——那樣才能並行消費多則訊息。真正的要求是:任一則訊息只能被其中一個接收者消費。

解法#

若通道有多個接收者,只有其中一個能成功消費某則特定訊息。多個接收者同時嘗試消費同一則訊息時,通道保證只有一個成功,因此接收者之間不必互相協調。通道仍可以有多個接收者來並行消費多則訊息,但單一訊息只會被單一接收者消費。

與競爭消費者的關係#

當 Point-to-Point Channel 只有一個消費者時,「訊息只被消費一次」並不令人意外。當通道有多個消費者時,它們就成為 Competing Consumers(競爭消費者),由通道確保每則訊息只被其中一個消費者收到。

消費訊息這件工作因此高度可擴展——它能被負載平衡到跑在多台電腦、多個應用程式中的多個消費者身上。

接下來#

  • 要把訊息送給所有可用接收者,改用 Publish-Subscribe Channel
  • 要用訊息傳遞實作 RPC,就用 Request-Reply 搭配一對 Point-to-Point Channel:呼叫是 Command Message,回覆是 Document Message
範例:股票交易、JMS Queue 與 .NET MessageQueue

股票交易#

在股票交易系統中,「執行某筆交易」的請求應該恰好由一個接收者消費並執行,因此該訊息應放在 Point-to-Point Channel 上。

JMS Queue#

在 JMS 中,點對點通道實作 Queue 介面。寄件端用 QueueSender 送訊息,每個接收端各用自己的 QueueReceiver 收訊息。

送訊息:

Queue queue = // obtain the queue via JNDI
QueueConnectionFactory factory = // obtain the connection factory via JNDI
QueueConnection connection = factory.createQueueConnection();
QueueSession session = connection.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
QueueSender sender = session.createSender(queue);

Message message = session.createTextMessage("The contents of the message.");

sender.send(message);

收訊息:

Queue queue = // obtain the queue via JNDI
QueueConnectionFactory factory = // obtain the connection factory via JNDI
QueueConnection connection = factory.createQueueConnection();
QueueSession session = connection.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
QueueReceiver receiver = session.createReceiver(queue);

TextMessage message = (TextMessage) receiver.receive();
String contents = message.getText();

JMS 1.1 統一了點對點與發布/訂閱兩個領域的客戶端 API,因此上述程式碼可簡化成使用 DestinationConnectionFactoryConnectionSessionMessageProducerMessageConsumer,而不必用 Queue 專屬的對應型別。

.NET MessageQueue#

在 .NET 中由 MessageQueue 類別實作點對點通道。實作 .NET 訊息傳遞的 MSMQ 在 3.0 之前只支援點對點,因此 .NET 支援的也就是點對點。JMS 把連線工廠、連線、session、寄件端與佇列的職責分開,MessageQueue一手包辦

MessageQueue queue = new MessageQueue("MyQueue");
queue.Send("The contents of the message.");
MessageQueue queue = new MessageQueue("MyQueue");
Message message = queue.Receive();
String contents = (String) message.Body();