如何構(gòu)建應(yīng)用程序引擎以及使用 Windows Azure Storage 實現(xiàn)異步消息傳送
心存懷疑的開發(fā)人員提出的最大疑問之一是他們?nèi)绾卧谠浦欣^續(xù)運行后臺進(jìn)程,即他們的引擎如何繼續(xù)運行。本文旨在通過向您演示如何構(gòu)建應(yīng)用程序引擎以及使用 Windows Azure Storage 實現(xiàn)異步消息傳送和處理來為您揭開云中缺乏后臺處理的神秘面紗。 為了證明開發(fā)人員可以拋開其有形的基礎(chǔ)結(jié)構(gòu)這條“安全毛毯”并將其應(yīng)用程序引擎置于云中,我們將介紹如何實現(xiàn)電子商務(wù)應(yīng)用程序的一個小型子集 Hollywood Hackers,您可以從中購買到 Hollywood 用于完全忽略物理法則和過時的常識的所有神奇技術(shù)。 我們將介紹的兩個主要方案如下: 將異步文本消息 (“toasts”) 發(fā)送給使用該應(yīng)用程序的用戶,以通知他們發(fā)生的重要事件(如已提交他們的購物車)或在員工之間發(fā)送消息。此方案使用 Windows Azure Queue、Windows Azure Table 和 Windows Azure 工作者角色。 此方案使用 Windows Azure Queue 和 Windows Azure 工作者角色將購物車提交給執(zhí)行引擎。 使用隊列存儲進(jìn)行內(nèi)部應(yīng)用程序消息傳送 在介紹具體的方案之前,我們需要先介紹一些有關(guān) Windows Azure Queue 的基礎(chǔ)知識。云中的隊列與傳統(tǒng)的 .NET 應(yīng)用程序中的隊列的運行方式不太一樣。在處理 AppDomain 中的數(shù)據(jù)時,您知道該數(shù)據(jù)只有一份,它完整地位于單一托管進(jìn)程中。 而在云中,您的一部分?jǐn)?shù)據(jù)可能在加利福尼亞,而另一部分可能在紐約,并且您可能會安排一個工作者角色在德克薩斯州對該數(shù)據(jù)進(jìn)行處理,而另一工作者角色在北達(dá)科他州進(jìn)行數(shù)據(jù)處理。 很多開發(fā)人員在適應(yīng)這種分布式計算和分布式數(shù)據(jù)時面臨著一些不熟悉的問題,例如對可能出現(xiàn)的故障進(jìn)行編碼、針對數(shù)據(jù)提交形成多次重試的概念甚至冪等性的理念。 只要您不將 Windows Azure Queue 視為進(jìn)程內(nèi)的常規(guī) CLR 隊列,其工作方式其實非常簡單。首先,應(yīng)用程序?qū)⑾蜿犃蝎@取一些數(shù)量的消息(需要記住,一次不會超過 20 條)并提供一個超時。此超時控制對其他隊列處理客戶端隱藏這些消息的時間。當(dāng)應(yīng)用程序成功完成需要對隊列消息進(jìn)行的所有處理后,將刪除該消息。 如果應(yīng)用程序引發(fā)異?;蛱幚黻犃邢⑹?,則在超時期限過后,其他客戶端可以再次看到該消息。因此,當(dāng)一個工作者角色處理失敗后,其他工作者角色可以繼續(xù)進(jìn)行處理。向隊列提交消息非常簡單:應(yīng)用程序直接或借助客戶端庫形成適當(dāng)?shù)?HTTP POST 消息,然后提交字符串或字節(jié)數(shù)組。隊列專門用于進(jìn)行內(nèi)部應(yīng)用程序消息傳送和非永久存儲,因此這些消息占用的空間需要相當(dāng)小。 如上所述,您可能安排多個工作者角色都嘗試處理同一消息。雖然隱藏當(dāng)前正在處理的消息的不可見超時很有幫助,但不能確保萬無一失。要完全避免沖突,您應(yīng)該將您的引擎處理設(shè)計為冪等。換句話說,同一隊列消息應(yīng)該可以由一個或多個工作者角色處理多次,而不會使應(yīng)用程序處于不一致的狀態(tài)。 理想情況下,您希望工作者角色可以檢測出是否已完成對給定消息的處理。在編寫工作者角色來處理隊列消息時,請牢記您的代碼可能會嘗試處理已處理過的消息,盡管這個可能性微乎其微。 圖 1 中的代碼段顯示了如何使用隨 Windows Azure SDK 一起提供的 StorageClient 程序集創(chuàng)建消息并將其提交給 Windows Azure Queue。StorageClient 庫實際上只是 Windows Azure Storage HTTP 接口周圍的包裝。 圖 1 創(chuàng)建消息并將其提交給 Windows Azure Queue 對于本文中的其他示例,我們使用了可以簡化此過程的一些包裝類(位于 Hollywood Hackers 的 CodePlex 站點中,網(wǎng)址為:hollywoodhackers.codeplex.com/SourceControl/ListDownloadableCommits.aspx)。 異步消息傳送 (Toast) 當(dāng)今,交互式網(wǎng)站不僅是一種時尚,更是一種需求。用戶已經(jīng)習(xí)慣了完全交互式網(wǎng)站,以致于當(dāng)他們遇到一個靜態(tài)的非交互式頁面時會認(rèn)為什么地方出問題了??紤]到這一點,我們希望可以在我們的用戶使用這樣的站點時向他們發(fā)送通知。 為此,我們將利用 Windows Azure Queue 和 Windows Azure Table 存儲機(jī)制構(gòu)建一個消息傳遞框架??蛻舳藢⑹褂门c jQuery Gritter 插件結(jié)合的 jQuery 在用戶的瀏覽器中將通知顯示為一個 toast,類似于當(dāng)您收到新的 Outlook 電子郵件、即時消息或警報聲時在 Windows 系統(tǒng)托盤上方淡出的消息。 當(dāng)需要向某個用戶發(fā)送通知時,該用戶將被插入到隊列中。因為工作者角色負(fù)責(zé)處理隊列中的每個項目,所以該角色將動態(tài)確定如何處理每個項目。在本例中,引擎只需要執(zhí)行一個操作,但在復(fù)雜的 CRM 網(wǎng)站或支持站點中,要執(zhí)行的操作可能不計其數(shù)。 工作者角色在隊列中遇到用戶通知時,會將該通知存儲在表存儲中并將其從隊列中刪除。這樣,消息可以保留很長時間并等待用戶登錄進(jìn)行處理。隊列存儲中的消息的最大保存期限比較短,不會超過幾天。當(dāng)用戶訪問網(wǎng)站時,我們的 jQuery 腳本將異步獲取表中的所有消息,并通過在控制器上調(diào)用可返回 JavaScript Object Notation (JSON) 的方法在瀏覽器中以常見的形式顯示這些消息。 盡管隊列只處理字符串或字節(jié)數(shù)組,但我們可以通過將任何類型的結(jié)構(gòu)化數(shù)據(jù)序列化為二進(jìn)制文件來將其存儲在隊列中,然后在我們需要使用時再將其轉(zhuǎn)換回來。這成為將強(qiáng)類型化的對象傳遞到隊列中的出色技術(shù)。我們會將此技術(shù)構(gòu)建到我們的隊列消息的基類中。然后,我們的系統(tǒng)消息類可以包含我們的數(shù)據(jù),而且可以將整個對象提交到隊列中并根據(jù)需要進(jìn)行利用(請參見圖 2)。 圖 2 在隊列中存儲結(jié)構(gòu)化數(shù)據(jù) 請記住,要使用 BinaryFormatter 類,需要以完全信任模式(可以通過服務(wù)配置文件啟用此模式)運行 Windows Azure 工作者角色。 string accountName;
string accountSharedKey;
string queueBaseUri;
string StorageCredentialsAccountAndKey credentials;
if (RoleEnvironment.IsAvailable)
{
// We are running in a cloud - INCLUDING LOCAL!
accountName =
RoleEnvironment.GetConfigurationSettingValue(AccountName);
accountSharedKey =
RoleEnvironment.GetConfigurationSettingValue(AccountSharedKey);
queueBaseUri = RoleEnvironment.GetConfigurationSettingValue
(QueueStorageEndpoint);
}
else
{
accountName = ConfigurationManager.AppSettings[AccountName];
accountSharedKey =
ConfigurationManager.AppSettings[AccountSharedKey];
queueBaseUri =
ConfigurationManager.AppSettings[QueueStorageEndpoint];
}
credentials =
new StorageCredentialsAccountAndKey(accountName, accountSharedKey);
CloudQueueClient client =
new CloudQueueClient(queueBaseUri, credentials);
CloudQueue queue = client.GetQueueReference(queueName);
CloudQueueMessage m = new CloudQueueMessage(
/* string or byte[] representing message to enqueue */);
Queue.AddMessage(m);namespace HollywoodHackers.Storage.Queue
{
[Serializable]
public class QueueMessageBase
{
public byte[] ToBinary()
{
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
ms.Position = 0;
bf.Serialize(ms, this);
byte[] output = ms.GetBuffer();
ms.Close();
return output;
}
public static T FromMessageT>(CloudQueueMessage m)
{
byte[] buffer = m.AsBytes();
MemoryStream ms = new MemoryStream(buffer);
ms.Position = 0;
BinaryFormatter bf = new BinaryFormatter();
return (T)bf.Deserialize(ms);
}
}
[Serializable]
public class ToastQueueMessage : QueueMessageBase
{
public ToastQueueMessage()
: base()
{
}
public string TargetUserName { get; set; }
public string MessageText { get; set; }
public string Title { get; set; }
public DateTime CreatedOn { get; set; }
}
評論