基于云存儲(chǔ)實(shí)現(xiàn)用Windows Azure Storage增強(qiáng)應(yīng)用程序的引擎
概述:您可以在云中運(yùn)行后臺(tái)進(jìn)程。Kevin Hoffman 和 Nate Dudek 使用購(gòu)物車(chē)示例演示如何構(gòu)建應(yīng)用程序引擎,以及如何使用Azure Storage實(shí)現(xiàn)異步消息傳送和處理。
開(kāi)發(fā)人員容易依賴(lài)其有形的、可感知的基礎(chǔ)結(jié)構(gòu),就像其是“安全毛毯”。他們知道如何使用、如何操作,如果出現(xiàn)問(wèn)題,他們知道問(wèn)題出在哪兒。而這通常會(huì)成為開(kāi)發(fā)人員采用更新的技術(shù)(例如云計(jì)算)的障礙。
心存懷疑的開(kāi)發(fā)人員提出的最大疑問(wèn)之一是他們?nèi)绾卧谠浦欣^續(xù)運(yùn)行后臺(tái)進(jìn)程,即他們的引擎如何繼續(xù)運(yùn)行。本文旨在通過(guò)向您演示如何構(gòu)建應(yīng)用程序引擎以及使用 Windows Azure Storage 實(shí)現(xiàn)異步消息傳送和處理來(lái)為您揭開(kāi)云中缺乏后臺(tái)處理的神秘面紗。
為了證明開(kāi)發(fā)人員可以?huà)侀_(kāi)其有形的基礎(chǔ)結(jié)構(gòu)這條“安全毛毯”并將其應(yīng)用程序引擎置于云中,我們將介紹如何實(shí)現(xiàn)電子商務(wù)應(yīng)用程序的一個(gè)小型子集 Hollywood Hackers,您可以從中購(gòu)買(mǎi)到 Hollywood 用于完全忽略物理法則和過(guò)時(shí)的常識(shí)的所有神奇技術(shù)。
我們將介紹的兩個(gè)主要方案如下:
將異步文本消息 (“toasts”) 發(fā)送給使用該應(yīng)用程序的用戶(hù),以通知他們發(fā)生的重要事件(如已提交他們的購(gòu)物車(chē))或在員工之間發(fā)送消息。此方案使用 Windows Azure Queue、Windows Azure Table 和 Windows Azure 工作者角色。
此方案使用 Windows Azure Queue 和 Windows Azure 工作者角色將購(gòu)物車(chē)提交給執(zhí)行引擎。
使用隊(duì)列存儲(chǔ)進(jìn)行內(nèi)部應(yīng)用程序消息傳送
在介紹具體的方案之前,我們需要先介紹一些有關(guān) Windows Azure Queue 的基礎(chǔ)知識(shí)。云中的隊(duì)列與傳統(tǒng)的 .NET 應(yīng)用程序中的隊(duì)列的運(yùn)行方式不太一樣。在處理 AppDomain 中的數(shù)據(jù)時(shí),您知道該數(shù)據(jù)只有一份,它完整地位于單一托管進(jìn)程中。
而在云中,您的一部分?jǐn)?shù)據(jù)可能在加利福尼亞,而另一部分可能在紐約,并且您可能會(huì)安排一個(gè)工作者角色在德克薩斯州對(duì)該數(shù)據(jù)進(jìn)行處理,而另一工作者角色在北達(dá)科他州進(jìn)行數(shù)據(jù)處理。
很多開(kāi)發(fā)人員在適應(yīng)這種分布式計(jì)算和分布式數(shù)據(jù)時(shí)面臨著一些不熟悉的問(wèn)題,例如對(duì)可能出現(xiàn)的故障進(jìn)行編碼、針對(duì)數(shù)據(jù)提交形成多次重試的概念甚至冪等性的理念。
只要您不將 Windows Azure Queue 視為進(jìn)程內(nèi)的常規(guī) CLR 隊(duì)列,其工作方式其實(shí)非常簡(jiǎn)單。首先,應(yīng)用程序?qū)⑾蜿?duì)列獲取一些數(shù)量的消息(需要記住,一次不會(huì)超過(guò) 20 條)并提供一個(gè)超時(shí)。此超時(shí)控制對(duì)其他隊(duì)列處理客戶(hù)端隱藏這些消息的時(shí)間。當(dāng)應(yīng)用程序成功完成需要對(duì)隊(duì)列消息進(jìn)行的所有處理后,將刪除該消息。
如果應(yīng)用程序引發(fā)異?;蛱幚黻?duì)列消息失敗,則在超時(shí)期限過(guò)后,其他客戶(hù)端可以再次看到該消息。因此,當(dāng)一個(gè)工作者角色處理失敗后,其他工作者角色可以繼續(xù)進(jìn)行處理。向隊(duì)列提交消息非常簡(jiǎn)單:應(yīng)用程序直接或借助客戶(hù)端庫(kù)形成適當(dāng)?shù)?HTTP POST 消息,然后提交字符串或字節(jié)數(shù)組。隊(duì)列專(zhuān)門(mén)用于進(jìn)行內(nèi)部應(yīng)用程序消息傳送和非永久存儲(chǔ),因此這些消息占用的空間需要相當(dāng)小。
如上所述,您可能安排多個(gè)工作者角色都嘗試處理同一消息。雖然隱藏當(dāng)前正在處理的消息的不可見(jiàn)超時(shí)很有幫助,但不能確保萬(wàn)無(wú)一失。要完全避免沖突,您應(yīng)該將您的引擎處理設(shè)計(jì)為冪等。換句話(huà)說(shuō),同一隊(duì)列消息應(yīng)該可以由一個(gè)或多個(gè)工作者角色處理多次,而不會(huì)使應(yīng)用程序處于不一致的狀態(tài)。
理想情況下,您希望工作者角色可以檢測(cè)出是否已完成對(duì)給定消息的處理。在編寫(xiě)工作者角色來(lái)處理隊(duì)列消息時(shí),請(qǐng)牢記您的代碼可能會(huì)嘗試處理已處理過(guò)的消息,盡管這個(gè)可能性微乎其微。
圖 1 中的代碼段顯示了如何使用隨 Windows Azure SDK 一起提供的 StorageClient 程序集創(chuàng)建消息并將其提交給 Windows Azure Queue。StorageClient 庫(kù)實(shí)際上只是 Windows Azure Storage HTTP 接口周?chē)陌b。
圖 1 創(chuàng)建消息并將其提交給 Windows Azure Queue
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);
對(duì)于本文中的其他示例,我們使用了可以簡(jiǎn)化此過(guò)程的一些包裝類(lèi)(位于 Hollywood Hackers 的 CodePlex 站點(diǎn)中,網(wǎng)址為:hollywoodhackers.codeplex.com/SourceControl/ListDownloadableCommits.aspx)。
異步消息傳送 (Toast)
當(dāng)今,交互式網(wǎng)站不僅是一種時(shí)尚,更是一種需求。用戶(hù)已經(jīng)習(xí)慣了完全交互式網(wǎng)站,以致于當(dāng)他們遇到一個(gè)靜態(tài)的非交互式頁(yè)面時(shí)會(huì)認(rèn)為什么地方出問(wèn)題了??紤]到這一點(diǎn),我們希望可以在我們的用戶(hù)使用這樣的站點(diǎn)時(shí)向他們發(fā)送通知。
為此,我們將利用 Windows Azure Queue 和 Windows Azure Table 存儲(chǔ)機(jī)制構(gòu)建一個(gè)消息傳遞框架。客戶(hù)端將使用與 jQuery Gritter 插件結(jié)合的 jQuery 在用戶(hù)的瀏覽器中將通知顯示為一個(gè) toast,類(lèi)似于當(dāng)您收到新的 Outlook 電子郵件、即時(shí)消息或警報(bào)聲時(shí)在 Windows 系統(tǒng)托盤(pán)上方淡出的消息。
當(dāng)需要向某個(gè)用戶(hù)發(fā)送通知時(shí),該用戶(hù)將被插入到隊(duì)列中。因?yàn)楣ぷ髡呓巧?fù)責(zé)處理隊(duì)列中的每個(gè)項(xiàng)目,所以該角色將動(dòng)態(tài)確定如何處理每個(gè)項(xiàng)目。在本例中,引擎只需要執(zhí)行一個(gè)操作,但在復(fù)雜的 CRM 網(wǎng)站或支持站點(diǎn)中,要執(zhí)行的操作可能不計(jì)其數(shù)。
工作者角色在隊(duì)列中遇到用戶(hù)通知時(shí),會(huì)將該通知存儲(chǔ)在表存儲(chǔ)中并將其從隊(duì)列中刪除。這樣,消息可以保留很長(zhǎng)時(shí)間并等待用戶(hù)登錄進(jìn)行處理。隊(duì)列存儲(chǔ)中的消息的最大保存期限比較短,不會(huì)超過(guò)幾天。當(dāng)用戶(hù)訪(fǎng)問(wèn)網(wǎng)站時(shí),我們的 jQuery 腳本將異步獲取表中的所有消息,并通過(guò)在控制器上調(diào)用可返回 JavaScript Object Notation (JSON) 的方法在瀏覽器中以常見(jiàn)的形式顯示這些消息。
盡管隊(duì)列只處理字符串或字節(jié)數(shù)組,但我們可以通過(guò)將任何類(lèi)型的結(jié)構(gòu)化數(shù)據(jù)序列化為二進(jìn)制文件來(lái)將其存儲(chǔ)在隊(duì)列中,然后在我們需要使用時(shí)再將其轉(zhuǎn)換回來(lái)。這成為將強(qiáng)類(lèi)型化的對(duì)象傳遞到隊(duì)列中的出色技術(shù)。我們會(huì)將此技術(shù)構(gòu)建到我們的隊(duì)列消息的基類(lèi)中。然后,我們的系統(tǒng)消息類(lèi)可以包含我們的數(shù)據(jù),而且可以將整個(gè)對(duì)象提交到隊(duì)列中并根據(jù)需要進(jìn)行利用(請(qǐng)參見(jiàn)圖 2)。
圖 2 在隊(duì)列中存儲(chǔ)結(jié)構(gòu)化數(shù)據(jù)
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 FromMessage
{
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; }
}
請(qǐng)記住,要使用 BinaryFormatter 類(lèi),需要以完全信任模式(可以通過(guò)服務(wù)配置文件啟用此模式)運(yùn)行 Windows Azure 工作者角色。
現(xiàn)在,我們需要一個(gè)簡(jiǎn)單的包裝來(lái)與我們的隊(duì)列交互。從本質(zhì)上說(shuō),我們需要能夠?qū)⑾⒉迦腙?duì)列,獲取任何掛起的消息并清除該隊(duì)列(請(qǐng)參見(jiàn)圖 3)。
圖 3 用于與隊(duì)列交互的包裝
namespace HollywoodHackers.Storage.Queue
{
public class StdQueue
StorageBase where T : QueueMessageBase, new()
{
protected CloudQueue queue;
protected CloudQueueClient client;
public StdQueue(string queueName)
{
client = new CloudQueueClient
(StorageBase.QueueBaseUri, StorageBase.Credentials);
queue = client.GetQueueReference(queueName);
queue.CreateIfNotExist();
}
public void AddMessage(T message)
{
CloudQueueMessage msg =
new CloudQueueMessage(message.ToBinary());
queue.AddMessage(msg);
}
public void DeleteMessage(CloudQueueMessage msg)
{
queue.DeleteMessage(msg);
}
public CloudQueueMessage GetMessage()
{
return queue.GetMessage(TimeSpan.FromSeconds(60));
}
}
public class ToastQueue : StdQueue
{
public ToastQueue()
: base("toasts")
{
}
}
}
我們還需要為表存儲(chǔ)設(shè)置一個(gè)包裝,以便在用戶(hù)登錄到站點(diǎn)之前可以存儲(chǔ)用戶(hù)通知。可以使用 PartitionKey(行集合的標(biāo)識(shí)符)和 RowKey(可唯一標(biāo)識(shí)特定分區(qū)中的每個(gè)單獨(dú)行)組織表數(shù)據(jù)。選擇 PartitionKey 和 RowKey 使用的數(shù)據(jù)是在使用表存儲(chǔ)時(shí)所做的最重要的設(shè)計(jì)決策之一。
這些特點(diǎn)允許跨存儲(chǔ)節(jié)點(diǎn)進(jìn)行負(fù)載平衡,并在應(yīng)用程序中提供內(nèi)置的可伸縮性選項(xiàng)。不考慮數(shù)據(jù)的數(shù)據(jù)中心關(guān)聯(lián)性,使用同一分區(qū)鍵的表存儲(chǔ)中的行將保留在相同的物理數(shù)據(jù)存儲(chǔ)中。因?yàn)獒槍?duì)每個(gè)用戶(hù)存儲(chǔ)對(duì)應(yīng)的消息,所以分區(qū)鍵將是 UserName,而 RowKey 則成為標(biāo)識(shí)每行的 GUID(請(qǐng)參見(jiàn)圖 4)。
圖 4 表存儲(chǔ)的包裝
namespace HollywoodHackers.Storage.Repositories
{
public class UserTextNotificationRepository : StorageBase
{
public const string EntitySetName =
"UserTextNotifications";
CloudTableClient tableClient;
UserTextNotificationContext notificationContext;
public UserTextNotificationRepository()
: base()
{
tableClient = new CloudTableClient
(StorageBase.TableBaseUri, StorageBase.Credentials);
notificationContext = new UserTextNotificationContext
(StorageBase.TableBaseUri,StorageBase.Credentials);
tableClient.CreateTableIfNotExist(EntitySetName);
}
public UserTextNotification[]
GetNotificationsForUser(string userName)
{
var q = from notification in
notificationContext.UserNotifications
where notification.TargetUserName ==
userName select notification;
return q.ToArray();
}
public void AddNotification
(UserTextNotification notification)
{
notification.RowKey = Guid.NewGuid().ToString();
notificationContext.AddObject
(EntitySetName, notification);
notificationContext.SaveChanges();
}
}
}
因?yàn)槲覀兊拇鎯?chǔ)機(jī)制已經(jīng)確定,所以我們需要一個(gè)工作者角色作為引擎;以便在我們的電子商務(wù)站點(diǎn)的后臺(tái)處理消息。為此,我們定義了一個(gè)從 Microsoft.ServiceHosting.ServiceRuntime.RoleEntryPoint 類(lèi)繼承的類(lèi),并將其與云服務(wù)項(xiàng)目中的工作者角色關(guān)聯(lián)(請(qǐng)參見(jiàn)圖 5)。
圖 5 作為引擎的工作者角色
public class WorkerRole : RoleEntryPoint
{
ShoppingCartQueue cartQueue;
ToastQueue toastQueue;
UserTextNotificationRepository toastRepository;
public override void Run()
{
// This is a sample worker implementation.
//Replace with your logic.
Trace.WriteLine("WorkerRole1 entry point called",
"Information");
toastRepository = new UserTextNotificationRepository();
InitQueue();
while (true)
{
Thread.Sleep(10000);
Trace.WriteLine("Working", "Information");
ProcessNewTextNotifications();
ProcessShoppingCarts();
}
}
private void InitQueue()
{
cartQueue = new ShoppingCartQueue();
toastQueue = new ToastQueue();
}
private void ProcessNewTextNotifications()
{
CloudQueueMessage cqm = toastQueue.GetMessage();
while (cqm != null)
{
ToastQueueMessage message =
QueueMessageBase.FromMessage
toastRepository.AddNotification(new
UserTextNotification()
{
MessageText = message.MessageText,
MessageDate = DateTime.Now,
TargetUserName = message.TargetUserName,
Title = message.Title
});
toastQueue.DeleteMessage(cqm);
cqm = toastQueue.GetMessage();
}
}
private void ProcessShoppingCarts()
{
// We will add this later in the article!
}
public override bool OnStart()
{
// Set the maximum number of concurrent connections
ServicePointManager.DefaultConnectionLimit = 12;
DiagnosticMonitor.Start("DiagnosticsConnectionString");
// For information on handling configuration changes
// see the MSDN topic at
//http://go.microsoft.com/fwlink/?LinkId=166357.
RoleEnvironment.Changing += RoleEnvironmentChanging;
return base.OnStart();
}
private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
{
// If a configuration setting is changing
if (e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))
{
// Set e.Cancel to true to restart this role instance
e.Cancel = true;
}
}
}
讓我們看一下工作者角色代碼。在初始化和設(shè)置所需的隊(duì)列和表存儲(chǔ)之后,此代碼將進(jìn)入一個(gè)循環(huán)。每 10 秒鐘,它就會(huì)處理一次隊(duì)列中的消息。每次我們通過(guò)處理循環(huán)時(shí)都將獲取隊(duì)列中的消息,直到最終返回 null,這表示隊(duì)列為空。
您從隊(duì)列中看到的消息永遠(yuǎn)不會(huì)超過(guò) 20 個(gè),如果不信,您可以反復(fù)嘗試來(lái)驗(yàn)證一下。對(duì)隊(duì)列進(jìn)行的任何處理都有時(shí)間限制,必須在該時(shí)間范圍內(nèi)對(duì)每個(gè)隊(duì)列消息執(zhí)行有意義的操作,否則隊(duì)列消息將被視為超時(shí),并在隊(duì)列中顯示備份,以便可以由其他工作者來(lái)處理此消息。每個(gè)消息都會(huì)作為用戶(hù)通知添加到表存儲(chǔ)中。關(guān)于工作者角色需要記住的重要一點(diǎn)是:一旦入口點(diǎn)方法完成,工作者角色也就結(jié)束了。這就是您需要在一個(gè)循環(huán)中保持邏輯運(yùn)行的原因。
從客戶(hù)端的角度來(lái)說(shuō),我們需要能夠以 JSON 形式返回消息,以便 jQuery 可以異步輪詢(xún)并顯示新的用戶(hù)通知。為此,我們會(huì)將一些代碼添加到消息控制器中,以便可以訪(fǎng)問(wèn)這些通知(請(qǐng)參見(jiàn)圖 6)。
圖 6 以 JSON 形式返回消息
public JsonResult GetMessages()
{
if (User.Identity.IsAuthenticated)
{
UserTextNotification[] userToasts =
toastRepository.GetNotifications(User.Identity.Name);
object[] data =
(from UserTextNotification toast in userToasts
select new { title = toast.Title ?? "Notification",
text = toast.MessageText }).ToArray();
return Json(data, JsonRequestBehavior.AllowGet);
}
else
return Json(null);
}
在 Visual Studio 2010 beta 2 下的 ASP.NET MVC 2(我們用于撰寫(xiě)本文的環(huán)境)中,如果沒(méi)有 JsonRequestBehavior.AllowGet 選項(xiàng),您無(wú)法將 JSON 數(shù)據(jù)返回到 jQuery 或其他客戶(hù)端。在 ASP.NET MVC 1 中,不需要此選項(xiàng)?,F(xiàn)在,我們可以編寫(xiě) JavaScript,它每 15 秒將調(diào)用一次 GetMessages 方法并將以 toast 形式消息顯示通知(請(qǐng)參見(jiàn)圖 7)。
圖 7 以 toast 形式消息顯示的通知
$(document).ready(function() {
setInterval(function() {
$.ajax({
contentType: "application/json; charset=utf-8",
dataType: "json",
url: "/SystemMessage/GetMessages",
success: function(data) {
for (msg in data) {
$.gritter.add({
title: data[msg].title,
text: data[msg].text,
sticky: false
});
}
}
})
}, 15000)
});
提交和處理購(gòu)物車(chē)
在我們的示例應(yīng)用程序中,我們希望使用隊(duì)列存儲(chǔ)執(zhí)行的另一個(gè)關(guān)鍵方案是提交購(gòu)物車(chē)。Hollywood Hackers 有一個(gè)第三方履行系統(tǒng)(Hollywood Hackers 無(wú)法在其空間有限的倉(cāng)庫(kù)中保留所有小工具),所以引擎需要對(duì)購(gòu)物車(chē)進(jìn)行一些處理。一旦引擎完成其處理,它會(huì)向用戶(hù)通知隊(duì)列提交一個(gè)消息,告知用戶(hù)已經(jīng)對(duì)購(gòu)物車(chē)進(jìn)行了處理(或者出現(xiàn)了問(wèn)題)。如果處理購(gòu)物車(chē)時(shí)用戶(hù)處于在線(xiàn)狀態(tài),該用戶(hù)將收到系統(tǒng)彈出的一個(gè) toast 消息。如果用戶(hù)不在線(xiàn),則會(huì)在其下次登錄到該站點(diǎn)時(shí)收到該彈出消息,如圖 8 所示。
圖 8 示例用戶(hù)通知
查看原圖(大圖)
我們首先需要的是一些包裝類(lèi),使我們可以與購(gòu)物車(chē)隊(duì)列交互。這些包裝非常簡(jiǎn)單,如果要查看它們的源代碼,可以在 CodePlex 站點(diǎn)上查看。
與標(biāo)準(zhǔn) CRUD(創(chuàng)建、讀取、更新、刪除)存儲(chǔ)庫(kù)不同的是,隊(duì)列中的讀取操作不是單純的讀取操作。請(qǐng)記住,只要獲取隊(duì)列中的消息,必須在有限的時(shí)間內(nèi)處理該消息,操作失敗或刪除消息都會(huì)顯示處理完成。這種模式不能順利地轉(zhuǎn)換到存儲(chǔ)庫(kù)模式,所以我們已經(jīng)不再借助包裝類(lèi)執(zhí)行此操作。
現(xiàn)在,我們已經(jīng)擁有了要與購(gòu)物車(chē)隊(duì)列交互的代碼,我們可以將一些代碼放在購(gòu)物車(chē)控制器中,以便將購(gòu)物車(chē)內(nèi)容提交到隊(duì)列中(請(qǐng)參見(jiàn)圖 9)。
圖 9 向隊(duì)列提交購(gòu)物車(chē)
public ActionResult Submit()
{
ShoppingCartMessage cart = new ShoppingCartMessage();
cart.UserName = User.Identity.Name;
cart.Discounts = 12.50f;
cart.CartID = Guid.NewGuid().ToString();
List
items.Add(new ShoppingCartItem()
{ Quantity = 12, SKU = "10000101010",
UnitPrice = 15.75f });
items.Add(new ShoppingCartItem()
{ Quantity = 27, SKU = "12390123j213",
UnitPrice = 99.92f });
cart.CartItems = items.ToArray();
cartQueue.AddMessage(cart);
return View();
}
在實(shí)際情況下,您可能會(huì)從進(jìn)程外狀態(tài)(例如會(huì)話(huà)存儲(chǔ)、緩存或窗體發(fā)布)獲得購(gòu)物車(chē)。為了簡(jiǎn)化本文代碼,我們僅僅構(gòu)建了購(gòu)物車(chē)的內(nèi)容。
最后,購(gòu)物車(chē)內(nèi)容已處于隊(duì)列中,我們可以修改工作者角色,以便它可以定期檢查隊(duì)列中掛起的購(gòu)物車(chē)。它每次會(huì)從隊(duì)列中選擇一個(gè)購(gòu)物車(chē),用整整一分鐘對(duì)該購(gòu)物車(chē)進(jìn)行處理,然后向用戶(hù)通知隊(duì)列提交一個(gè)消息,告知用戶(hù)已經(jīng)對(duì)該購(gòu)物車(chē)進(jìn)行了處理(請(qǐng)參見(jiàn)圖 10)。
圖 10 檢查隊(duì)列中掛起的購(gòu)物車(chē)
private void ProcessShoppingCarts()
{
CloudQueueMessage cqm = cartQueue.GetMessage();
while (cqm != null)
{
ShoppingCartMessage cart =
QueueMessageBase.FromMessage
toastRepository.AddNotification(new UserTextNotification()
{
MessageText = String.Format
("Your shopping cart containing {0} items has been processed.",
cart.CartItems.Length),
MessageDate = DateTime.Now,
TargetUserName = cart.UserName
});
cartQueue.DeleteMessage(cqm);
cqm = cartQueue.GetMessage();
}
}
經(jīng)過(guò)對(duì)用戶(hù)通知表中的隊(duì)列消息的存取操作,位于主頁(yè)面中的 jQuery Gritter 代碼然后會(huì)在下一個(gè) 15 秒的輪詢(xún)周期中檢測(cè)是否存在新消息,然后向用戶(hù)顯示購(gòu)物車(chē) toast 通知。
總結(jié)和后續(xù)操作
本文的目的是使開(kāi)發(fā)人員可以?huà)侀_(kāi)其有形的數(shù)據(jù)中心這條“安全毛毯”,認(rèn)識(shí)到他們可以使用 Windows Azure 執(zhí)行很多操作,而不僅僅是創(chuàng)建簡(jiǎn)單的“Hello World”網(wǎng)站。借助 Windows Azure Queues 和 Windows Azure 表存儲(chǔ)的強(qiáng)大功能,以及利用這些強(qiáng)大功能在應(yīng)用程序和其工作者角色之間進(jìn)行異步消息傳送,您可以真正使用 Windows Azure 增強(qiáng)應(yīng)用程序的引擎了。
為使文章簡(jiǎn)明易懂,我們將很多代碼都保留為原樣,沒(méi)有進(jìn)行重構(gòu)。作為熟悉新 Windows Azure Muscle 的練習(xí),請(qǐng)嘗試重構(gòu)本文中的一些代碼,以加深對(duì)隊(duì)列的熟練使用,甚至創(chuàng)建一個(gè)獨(dú)立的程序集,其中包含為任何 ASP.NET MVC 網(wǎng)站進(jìn)行異步消息傳送和通知所需的所有代碼。
主要是親自動(dòng)手實(shí)踐,創(chuàng)建一些站點(diǎn)并看看您都可以執(zhí)行哪些操作。本文中的代碼位于 Hollywood Hackers 的 CodePlex 站點(diǎn)中。
評(píng)論
查看更多