三、PetShop資料訪問層之訊息處理
在進行系統設計時,除了對安全、事務等問題給與足夠的重視外,效能也是一個不可避免的問題所在,尤其是一個B/S結構的軟體系統,必須充分地考慮訪問量、資料流量、伺服器負荷的問題。解決效能的瓶頸,除了對硬體系統進行升級外,軟體設計的合理性尤為重要。
在前面我曾提到,分層式結構設計可能會在一定程度上影響資料訪問的效能,然而與它給設計人員帶來的好處相比,幾乎可以忽略。要提供整個系統的效能,還可以從資料庫的最佳化著手,例如串連池的使用、建立索引、最佳化查詢策略等等,例如在PetShop中就利用了資料庫的Cache,對於資料量較大的訂單資料,則利用分庫的方式為其單獨建立了Order和Inventory資料庫。而在軟體設計上,比較有用的方式是利用多線程與非同步處理方式。
在PetShop4.0中,使用了Microsoft Messaging Queue(MSMQ)技術來完成非同步處理,利用訊息佇列臨時存放要插入的資料,使得資料訪問因為不需要訪問資料庫從而提供了訪問效能,至於隊列中的資料,則等待系統閒置時候再進行處理,將其最終插入到資料庫中。
PetShop4.0中的訊息處理,主要分為如下幾部分:訊息介面IMessaging、訊息工廠MessagingFactory、MSMQ實現MSMQMessaging以及資料幕後處理應用程式OrderProcessor。
從模組化分上,PetShop自始自終地履行了“面向介面設計”的原則,將訊息處理的介面與實現分開,並通過原廠模式封裝訊息實現對象的建立,以達到鬆散耦合的目的。
由於在PetShop中僅對訂單的處理使用了非同步處理方式,因此在訊息介面IMessaging中,僅定義了一個IOrder介面,其類圖如下:
在對訊息介面的實現中,考慮到未來的擴充中會有其他的資料對象會使用MSMQ,因此定義了一個Queue的基類,實現訊息Receive和Send的基本操作:
public virtual object Receive()
{
try
{
using (Message message = queue.Receive(timeout, transactionType))
return message;
}
catch (MessageQueueException mqex)
{
if (mqex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout)
throw new TimeoutException();
throw;
}
}
public virtual void Send(object msg)
{
queue.Send(msg, transactionType);
}
其中queue對象是System.Messaging.MessageQueue類型,作為存放資料的隊列。MSMQ隊列是一個可持久的隊列,因此不必擔心使用者不間斷地下訂單會導致訂單資料的丟失。在PetShopQueue設定了timeout值,OrderProcessor會根據timeout值定期掃描隊列中的訂單資料。
MSMQMessaging模組中,Order對象實現了IMessaging模組中定義的介面IOrder,同時它還繼承了基類PetShopQueue,其定義如下:
public class Order:PetShopQueue, PetShop.IMessaging.IOrder
方法的實現代碼如下:
public new OrderInfo Receive()
{
// This method involves in distributed transaction and need Automatic Transaction type
base.transactionType = MessageQueueTransactionType.Automatic;
return (OrderInfo)((Message)base.Receive()).Body;
} public OrderInfo Receive(int timeout)
{
base.timeout = TimeSpan.FromSeconds(Convert.ToDouble(timeout));
return Receive();
}
public void Send(OrderInfo orderMessage)
{
// This method does not involve in distributed transaction and optimizes performance using Single type
base.transactionType = MessageQueueTransactionType.Single;
base.Send(orderMessage);
}
所以,最後的類圖應該如下:
注意在Order類的Receive()方法中,是用new關鍵字而不是override關鍵字來重寫其父類PetShopQueue的Receive()虛方法。因此,如果是執行個體化如下的對象,將會調用PetShopQueue的Receive()方法,而不是子類Order的Receive()方法:
PetShopQueue queue = new Order();
queue.Receive();
從設計上來看,由於PetShop採用“面向介面設計”的原則,如果我們要建立Order對象,應該採用如下的方式:
IOrder order = new Order();
order.Receive();
考慮到IOrder的實現有可能的變化,PetShop仍然利用了原廠模式,將IOrder對象的建立用專門的工廠模組進行了封裝:
在類QueueAccess中,通過CreateOrder()方法利用反射技術建立正確的IOrder類型對象:
public static PetShop.IMessaging.IOrder CreateOrder()
{
string className = path + “.Order”;
return PetShop.IMessaging.IOrder)Assembly.Load(path).CreateInstance(className);
}
path的值通過設定檔擷取:
private static readonly string path = ConfigurationManager.AppSettings[”OrderMessaging”];
而設定檔中,OrderMessaging的值設定如下:
<add key=”OrderMessaging” value=”PetShop.MSMQMessaging”/>
之所以利用原廠模式來負責對象的建立,是便於在業務層中對其調用,例如在BLL模組中OrderAsynchronous類:
public class OrderAsynchronous : IOrderStrategy
{
private static readonly PetShop.IMessaging.IOrder asynchOrder = PetShop.MessagingFactory.QueueAccess.CreateOrder();
public void Insert(PetShop.Model.OrderInfo order)
{
asynchOrder.Send(order);
}
}
一旦IOrder介面的實現發生變化,這種實現方式就可以使得客戶僅需要修改設定檔,而不需要修改代碼,如此就可以避免程式集的重新編譯和部署,使得系統能夠靈活應對需求的改變。例如定義一個實現IOrder介面的SpecialOrder,則可以新增一個模組,如PetShop.SpecialMSMQMessaging,而類名則仍然為Order,那麼此時我們僅需要修改設定檔中OrderMessaging的值即可:
<add key=”OrderMessaging” value=”PetShop.SpecialMSMQMessaging”/>
OrderProcessor是一個控制台應用程式,不過可以根據需求將其設計為Windows Service。它的目的就是接收訊息佇列中的訂單資料,然後將其插入到Order和Inventory資料庫中。它利用了多線程技術,以達到提高系統效能的目的。
在OrderProcessor應用程式中,主函數Main用於控制線程,而核心的執行任務則由方法ProcessOrders()實現:
private static void ProcessOrders()
{
// the transaction timeout should be long enough to handle all of orders in the batch
TimeSpan tsTimeout = TimeSpan.FromSeconds(Convert.ToDouble(transactionTimeout * batchSize));
Order order = new Order();
while (true)
{
// queue timeout variables
TimeSpan datetimeStarting = new TimeSpan(DateTime.Now.Ticks);
double elapsedTime = 0;
int processedItems = 0;
ArrayList queueOrders = new ArrayList();
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, tsTimeout))
{
// Receive the orders from the queue
for (int j = 0; j < batchSize; j++)
{
try
{
//only receive more queued orders if there is enough time
if ((elapsedTime + queueTimeout + transactionTimeout) < tsTimeout.TotalSeconds)
{
queueOrders.Add(order.ReceiveFromQueue(queueTimeout));
}
else
{
j = batchSize; // exit loop
}
//update elapsed time
elapsedTime = new TimeSpan(DateTime.Now.Ticks).TotalSeconds - datetimeStarting.TotalSeconds;
}
catch (TimeoutException)
{
//exit loop because no more messages are waiting
j = batchSize;
}
}
//process the queued orders
for (int k = 0; k < queueOrders.Count; k++)
{
order.Insert((OrderInfo)queueOrders[k]);
processedItems++;
totalOrdersProcessed++;
}
//batch complete or MSMQ receive timed out
ts.Complete();
}
Console.WriteLine("(Thread Id " + Thread.CurrentThread.ManagedThreadId + ") batch finished, " + processedItems + " items, in " + elapsedTime.ToString() + " seconds.");
}
}
首先,它會通過PetShop.BLL.Order類的公用方法ReceiveFromQueue()來擷取訊息佇列中的訂單資料,並將其放入到一個ArrayList對象中,然而再調用PetShop.BLL.Order類的Insert方法將其插入到Order和Inventory資料庫中。
在PetShop.BLL.Order類中,並不是直接執行插入訂單的操作,而是調用了IOrderStrategy介面的Insert()方法:
public void Insert(OrderInfo order)
{
// Call credit card procesor
ProcessCreditCard(order);
// Insert the order (a)synchrounously based on configuration
orderInsertStrategy.Insert(order);
}
在這裡,運用了一個策略模式,類圖如下所示:
在PetShop.BLL.Order類中,仍然利用設定檔來動態建立IOrderStategy對象:
private static readonly PetShop.IBLLStrategy.IOrderStrategy orderInsertStrategy = LoadInsertStrategy();
private static PetShop.IBLLStrategy.IOrderStrategy LoadInsertStrategy()
{
// Look up which strategy to use from config file
string path = ConfigurationManager.AppSettings[”OrderStrategyAssembly”];
string className = ConfigurationManager.AppSettings[”OrderStrategyClass”];
// Using the evidence given in the config file load the appropriate assembly and class
return (PetShop.IBLLStrategy.IOrderStrategy)Assembly.Load(path).CreateInstance(className);
}
由於OrderProcessor是一個單獨的應用程式,因此它使用的設定檔與PetShop不同,是存放在應用程式的App.config檔案中,在該檔案中,對IOrderStategy的配置為:
<add key=”OrderStrategyAssembly” value=”PetShop.BLL” />
<add key=”OrderStrategyClass” value=”PetShop.BLL.OrderSynchronous” />
因此,以非同步方式插入訂單的流程如所示:
Microsoft Messaging Queue(MSMQ)技術除用於非同步處理以外,它主要還是一種分散式處理技術。分散式處理中,一個重要的技術要素就是有關訊息的處理,而在System.Messaging命名空間中,已經提供了Message類,可以用於承載訊息的傳遞,前提上訊息的發送方與接收方在資料定義上應有統一的介面規範。
MSMQ在分散式處理的運用,在我參與的項目中已經有了實現。在為一個汽車製造商開發一個大型系統時,分銷商Dealer作為.Net用戶端,需要將資料傳遞到管理中心,並且該資料將被Oracle的EBS(E-Business System)使用。由於分銷商管理系統(DMS)採用的是C/S結構,資料庫為SQL Server,而汽車製造商管理中心的EBS資料庫為Oracle。這裡就涉及到兩個系統之間資料的傳遞。
實現架構如下:
首先Dealer的資料通過MSMQ傳遞到MSMQ Server,此時可以將資料插入到SQL Server資料庫中,同時利用FTP將資料傳送到專門的檔案伺服器上。然後利用IBM的EAI技術(公司專屬應用程式整合,Enterprise Application Itegration)定期將檔案伺服器中的檔案,利用介面規範寫入到EAI資料庫伺服器中,並最終寫道EBS的Oracle資料庫中。
上述架構是一個典型的分散式處理結構,而技術實現的核心就是MSMQ和EAI。由於我們已經定義了統一的介面規範,在通過訊息佇列形成檔案後,此時的資料就已經與平台無關了,使得在.Net平台下的分銷商管理系統能夠與Oracle的EBS整合起來,完成資料的處理。