脈絡:你正在用 Message Router 把訊息路由到多個目的地。
效率與相依的兩難#
Message Router 非常有效率,因為它能把訊息直接送到正確的目的地。
其他路由方案——尤其是反應式過濾(見 Message Filter 與 Routing Slip)——效率較低,因為它們採試誤法:先把訊息送到第一個可能的目的地,若是正確的就接受,否則再傳給第二個,依此類推。
分散式的路由方案還有另一個風險:一則訊息可能有多個接收者,或一個都沒有。若沒有中央路由元件,這兩種狀況都可能無人察覺。
但要達到這種準確度,Message Router 就必須內含每個目的地的知識與路由規則。
解法#
除了一般的輸入與輸出通道之外,Dynamic Router 多用一個控制通道:
- 系統啟動時,每個潛在接收者在控制通道上送一則特殊訊息給 Dynamic Router,宣告自己的存在,並列出自己能處理訊息的條件
- Dynamic Router 把每個參與者的「偏好」存進規則庫
- 訊息抵達時,Dynamic Router 評估所有規則,把訊息路由給規則被滿足的那個接收者
這讓我們得到有效率的預測式路由,卻不必背負「路由器相依於每個潛在接收者」的維護代價。

圖 7-5:Dynamic Router 解法示意
組態的兩種作法#
- 各參與者啟動時主動宣告——這要求每個參與者知道 Dynamic Router 使用的控制佇列,也要求 Dynamic Router 把規則持久化儲存;否則路由器一旦故障重啟,就無法回復路由規則。
- 由 Dynamic Router 廣播訊息觸發各方回覆控制訊息——這種組態更強健,但需要額外的 Publish-Subscribe Channel。
把控制通道擴充成允許參與者送出「訂閱」與「取消訂閱」訊息,可能很有價值——這讓接收者能在執行期把自己加入或移出路由方案。
規則衝突#
因為接收者彼此獨立,Dynamic Router 必須處理規則衝突——多個接收者宣告對同一類訊息有興趣。可用的策略有:
- 忽略與既有規則衝突的控制訊息——確保路由規則無衝突。
但這樣一來,路由表的狀態會取決於各接收者啟動的先後順序。若所有接收者同時啟動、同時往控制佇列宣告偏好,可能導致無法預測的行為。
- 送給第一個條件相符的接收者——允許路由表存在衝突,但在訊息進來時才化解它們。
- 送給所有條件相符的接收者——能容忍衝突,但這把 Dynamic Router 變成了 Recipient List。
一般而言,Content-Based Router 的行為意味著每則輸入訊息對應一則輸出訊息,而最後這個策略違反了這條規則。
主要代價#
Dynamic Router 的主要負債是方案的複雜度,以及對動態設定的系統除錯的困難。
兩個對照#
Dynamic Router 是「訊息式中介軟體執行類似低階 IP 網路功能」的又一例——它的運作方式非常接近 IP 路由中用來在網路之間轉送封包的動態路由表,而接收者用來設定它的協定,則類比於 IP 的 Routing Information Protocol(RIP)。
Dynamic Router 與 Dispatcher 的差別在於:它可以比單純的查表更聰明。
常見用途:SOA 的動態服務發現#
Dynamic Router 的一個常見用途是服務導向架構中的動態服務發現:
- 客戶端想存取某個服務時,送一則含有服務名稱的訊息給 Dynamic Router
- Dynamic Router 維護一份服務目錄——所有服務的名稱與它們監聽的通道
- 路由器把請求的服務名稱與目錄比對,並把訊息路由到正確的通道
這種配置允許同一個服務由多個提供者提供:客戶端可以持續往單一通道送命令訊息,而不必操心指定服務提供者的性質或位置。
範例:用 C# 與 MSMQ 實作動態路由器
這個範例建立在 Content-Based Router 的範例之上,把它強化成 Dynamic Router。新元件監聽兩個通道:inQueue 與 controlQueue。控制佇列接受 "X:QueueName" 格式的訊息,讓路由器把所有內文以字母 X 開頭的訊息路由到 QueueName。
class DynamicRouter
{
protected MessageQueue inQueue;
protected MessageQueue controlQueue;
protected MessageQueue dunnoQueue;
protected IDictionary routingTable = (IDictionary)(new Hashtable());
public DynamicRouter(MessageQueue inQueue, MessageQueue controlQueue, MessageQueue dunnoQueue)
{
this.inQueue = inQueue;
this.controlQueue = controlQueue;
this.dunnoQueue = dunnoQueue;
inQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnMessage);
inQueue.BeginReceive();
controlQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnControlMessage);
controlQueue.BeginReceive();
}
protected void OnMessage(Object source, ReceiveCompletedEventArgs asyncResult)
{
MessageQueue mq = (MessageQueue)source;
mq.Formatter = new System.Messaging.XmlMessageFormatter(
new String[] {"System.String,mscorlib"});
Message message = mq.EndReceive(asyncResult.AsyncResult);
String key = ((String)message.Body).Substring(0, 1);
if (routingTable.Contains(key))
{
MessageQueue destination = (MessageQueue)routingTable[key];
destination.Send(message);
}
else
dunnoQueue.Send(message);
mq.BeginReceive();
}
// control message format is X:QueueName as a single string
protected void OnControlMessage(Object source, ReceiveCompletedEventArgs asyncResult)
{
MessageQueue mq = (MessageQueue)source;
mq.Formatter = new System.Messaging.XmlMessageFormatter(
new String[] {"System.String,mscorlib"});
Message message = mq.EndReceive(asyncResult.AsyncResult);
String text = ((String)message.Body);
String [] split = (text.Split(new char[] {':'}, 2));
if (split.Length == 2)
{
String key = split[0];
String queueName = split[1];
MessageQueue queue = FindQueue(queueName);
routingTable.Add(key, queue);
}
else
{
dunnoQueue.Send(message);
}
mq.BeginReceive();
}
protected MessageQueue FindQueue(string queueName)
{
if (!MessageQueue.Exists(queueName))
{
return MessageQueue.Create(queueName);
}
else
return new MessageQueue(queueName);
}
}這個範例的衝突化解機制非常簡單——後到者獲勝。若兩個接收者都表示想收
X開頭的訊息,只有第二個會收到,因為 hashmap 對每個鍵只存一個佇列。另外注意
dunnoQueue現在會收到兩類訊息:找不到相符路由規則的進站訊息,以及格式不符要求的控制訊息。