在 Windows Azure 應用程式上的狀態管理


本文將介紹在 Windows Azure 上建置雲端應用程式時的狀態管理方法,讓應用程式能在多個執行個體間能保持一致的狀態資訊,確保使用者在執行時可持續的保持狀態。

Windows Azure VM 的類型

在 Windows Azure 上目前有兩種 VM(虛擬機器)的類型,分別是 Web Role與Worker Role 兩種,Web Role 專司 Web 應用程式或 HTTP 服務的互動處理,而 Worker Role 則是負責後端的運算以及高運算需求的應用程式處理,在規劃雲端應用程式的架構時,需要特別針對不同的應用程式執行需求來切割 VM,最常見的方法大概就是切層,將雲端應用程式切分成兩或三個層次,不同的層次使用不同的 VM 來執行,典型的作法是,將使用者介面服務(User Services)交由 Web Role 來負責,而應用程式邏輯(Business Services)則交由 Worker Role 來執行。

而當應用程式需要擴張(Scale)時,只要修改不同層次 VM 的執行個體數量,就能獲得更多的 VM 資源來負載應用程式的要求。

而目前 Windows Azure 上有五種 VM,可由應用程式來選擇,每種 VM 都有不同的運算資源配置以及 I/O 的優先權,當然運算資源愈大的 VM,成本也會愈高。

執行個體尺寸 CPU 記憶體 本地儲存大小 I/O 優先權 費用
Extra Small 1.0 GHz 768 MB 20 GB $0.05
Small 1.6 GHz 1.75 GB 225 GB $0.12
Medium 2 x 1.6 GHz 3.5 GB 490 GB $0.24
Large 4 x 1.6 GHz 7 GB 1,000 GB $0.48
Extra large 8 x 1.6 GHz 14 GB 2,040 GB $0.96

NOTE

Extra Small(XS)尺寸的 VM 是在今年的 PDC 2010 所宣布的,可用來作為在雲端上的應用程式測試平台或展示 Windows Azure 應用程式之用,每小時的成本為 0.05 美元,相當於一個月 36 美元,約台幣 1,200 元。

 

雖然 Windows Azure 上有不同尺寸的 VM 可以使用,但應用程式要能夠順暢穩定的執行,重要的還是在應用程式的開發上,以及因應 Windows Azure 的環境與架構進行規劃和設計,畢竟應用程式在雲端環境上的執行不比在本地端執行,一切都要將可擴充性(Scalability)考量進去,讓應用程式有近乎無限的可擴充性是設計雲端應用程式的重點之一。但擴充性所帶來的挑戰對許多已經習慣設計本地應用程式的開發人員來說可能會適應不良,本文要介紹的應用程式狀態管理就是其中一個挑戰。

狀態管理(Session Management)

只要是開發 Web-based 應用程式,並且是給多人使用的應用程式時,一定會遇到如何保持使用者狀態的問題,最典型的例子就是登入的狀態(login state),很多初學的開發人員幾乎都是以 Session 來做狀態管理,不論是登入的 token 或是使用者在網站中的活動記錄(activities)等等,Session 一向都是在 Web 應用程式上做狀態管理最容易的物件,比 Cookie 或 Query String 等都要容易。

若是有寫過 ASP.NET 應用程式的開發人員,應該都會知道 Session 物件的用法,也多少會知道 Session 物件的運作方法,它是一種會保存在伺服器記憶體的資料物件,且使用者只能有自己的 Session 物件,每位使用者都會有自己的 Session ID,ASP.NET 核心利用 Session ID 自記憶體中取出資料並回傳給使用者,但這個再平常不過的動作,一到了雲端的環境上可能就行不通了,當然這問題不是出在程式本身,而是在雲端環境的架構上,讀者也不用擔心,本文會教給讀者在雲端環境上的因應作法。

以往我們在開發 ASP.NET 應用程式時,多半都是以應用程式只會在一台伺服器上執行為開發的標準,而現階段的資訊環境也大多是只有一台伺服器,所以 Session 的管理通常都是用以伺服器記憶體為主(in-process)的保存方法,這樣的設計方式經過多年的運作下來也沒有太大的問題,就算是到了雲端環境,只要 Web Role 只有一個執行個體的話,其實也就和只在一台伺服器執行是一模一樣的。

然而筆者在前面也提出,雲端應用程式的重要特質之一就是可擴充性,當 Web 應用程式的 VM 無法負擔用戶的要求時,系統的管理人員可以向雲端要求多個相同的執行個體以支援大量的用戶要求,如果應用程式無法因應如此的變更的話,發生 Session 遺失的機率是近乎 100%。因為在多個執行個體(俗稱的 Web Farm)執行時,Windows Azure 的 Load Balancer(負載平衡器)會自動將不同的使用者帶向不同的執行個體,就有如在企業內建置多台伺服器一般,Load Balancer 會監控不同執行個體的負載量後,依序將使用者的要求提交給不同的執行個體來處理。此時如果使用者的狀態仍然是以 In-process 模式來保存的話,在使用者要求派送給不同的執行個體時,使用者的狀態就會消失。

所以,在雲端環境上的 Web 應用程式的狀態管理必須要以多伺服器環境的方式來設計,為了要讓使用者可以在多伺服器環境上仍能保持狀態的資訊,Web 應用程式就不能將狀態資料只保存到單一電腦上,而是要向後來儲存,最好的儲存方式是以多伺服器所共用的儲存區,像是資料庫或是大家都可存取到的空間。以 Windows Azure 來說,開發人員可以選擇使用 Table Storage 或是 SQL Azure 資料庫。

所幸,ASP.NET 在最初的設計就考量了 Web Farm 的環境,因此有四種不同的 Session 狀態管理作法:

狀態管理作法 說明 雲端環境適用性
In-Proc 將狀態管理資訊保存在伺服器記憶體內。 若只使用單一執行個體,則可以使用,但不可使用於多執行個體環境。
StateServer 將狀態管理資訊保存在 ASP.NET State Server 內。 Windows Azure 的 Web Role 並沒有安裝 ASP.NET State Server,因此不可使用。
SQLServer 將狀態管理資訊保存在 SQL Server 內。 使用 SQL Azure 資料庫來保存,但要額外的花費(至少每月 $9.99 美元)
Custom 由開發人員自行實作 Session State Provider,以支援自訂的狀態管理功能。 可撰寫 Session State Provider 連接 Table Storage,將狀態資料保存在 Table Storage 內,需要額外花費,每 GB 的月費為 $0.15 美元,儲存的交易次數每 10,000 次為 $0.01 美元。

在本文中會介紹使用 Table Storage 或 SQL Azure 來儲存狀態資料的作法,以讓讀者了解在雲端上的應用程式如何處理狀態管理的問題。

實作狀態管理功能-Table Storage

由於 Session State 本身是一種結構化的資料,在 Windows Azure Storage 三劍客中,最適合的非表格儲存體(Table Storage)莫屬,以 Table Storage 來儲存時的成本也是比較低的,即便是每月有 1GB 的狀態資料量,以及 1,000,000 次的交易數量,總費用也不過 1.15 美元,相當於 50 元新台幣,相較於 SQL Azure 每月要 9.99 美元(約合 310 元新台幣)來說,是比較便宜的儲存空間。因此如果讀者的系統中沒有使用 SQL Azure 資料庫時,使用 Table Storage 會是比較輕鬆(省錢)的作法。不過若要使用 Table Storage,需要另外開發可存取 Table Storage 的 Session State Provider,然後使用 Custom 的 Session State 設定,才可以在 ASP.NET 應用程式中使用 Table Storage 來保存 Session 資料,所幸,微軟已經在公開的範例中提供了由微軟自己開發的 Table Storage Session Provider,開發人員可以直接由範例中取用它。

現在,我們就來撰寫一支 Web Farm 應用程式,並以 Table Storage 來保存狀態資料。

Step 1. 請先到微軟網站下載 Windows Azure Training Kit,並解壓縮它。

 

NOTE

Windows Azure Training Kit 可在 https://www.microsoft.com/downloads/en/details.aspx?FamilyID=413e88f8-5966-4a83-b309-53b7b77edf78 找到,筆者在本文會以 September 2010 的版本作示範。

 

Step 2. 請在 Windows Azure Training Kit 解壓縮資料夾的 Labs\BuildingWebFormAppsWithWindowsAzureVS2010\Source\Assets\AspProviders 資料夾中,並以 Visual Studio 2010 打開 AspProviders 專案,並且直接按 Crtl+Shift+B 建置這個專案,以產生 bin 資料夾以及其下的 AspProviders.dll 組件。

Step 3. 請開啟另一份 Visual Studio 2010(或是將 AspProviders 專案關閉),並新增一個雲端應用程式專案,並命名為 WebAppLoadBalanceCloud:

並加入一個 Web Role 專案,命名為 LoadBalanceWebApp。

在專案建立完成後,我們會得到類似下列的專案結構:

Step 4. 請在 WebAppLoadBalanceCloud 專案中,展開角色節點,並在 LoadBalanceWebApp 上按右鍵,選擇「屬性」以開啟 LoadBalanceWebApp 的雲端應用程式屬性設定,接著點按「設定」頁籤,在設定中加入 DataConnectionString 的設定,型別選擇連接字串,並設定它使用開發儲存體(如下列第一張圖片),完成時會如第二張圖片所示。

Step 5. 請在 LoadBalanceWebApp 專案中,加入在 Step 1 時建置的 AspProviders.dll 的參考。在加入完成後,您可以在專案的參考清單中看到 AspProviders 組件。

Step 6. 請以 HTML 檢視打開 Default.aspx 檔案,並以下列 HTML 指令取代原有在 <asp:Content> 內的 HTML 指令:

[HTML]

<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent"> 虛擬機器IP:<asp:Label ID="labelIpAddress" runat="server" />,執行個體編號:<asp:Label ID="labelInstanceNum" runat="server" /> <br /> <br /> 目前 Session 資料: <asp:BulletedList ID="blSessionList" runat="server"> </asp:BulletedList> <br /> 請輸入 Session 資料:<br /> <br /> 鍵:<asp:TextBox ID="txtKey" runat="server"></asp:TextBox><asp:RequiredFieldValidator ID="rfvKey" runat="server" ControlToValidate="txtKey" ErrorMessage="請輸入 Key。" /> <br /> 值:<asp:TextBox ID="txtValue" runat="server"></asp:TextBox><asp:RequiredFieldValidator ID="rfvValue" runat="server" ControlToValidate="txtValue" ErrorMessage="請輸入值。" /> <br /> <br /> &nbsp;<asp:Button ID="cmdPostSession" runat="server" Text=" 加入 Session " onclick="cmdPostSession_Click" /> </asp:Content>

上列的指令在設計模式中如下圖:

Step 7. 請打開 Default.aspx.cs 檔案,並以下列程式碼取代原有的程式碼:

[C#]

using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using Microsoft.WindowsAzure.ServiceRuntime; namespace LoadBalanceWebApp { public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { this.labelIpAddress.Text = Request.UserHostAddress; this.labelInstanceNum.Text = RoleEnvironment.CurrentRoleInstance.Id; if (!Page.IsPostBack) { List<string> sessions = new List<string>(); for (int i = 0; i < Session.Count; i++) sessions.Add(string.Format("Key={0},Value={1}", Session.Keys[i], Session[i].ToString())); this.blSessionList.DataSource = sessions; this.blSessionList.DataBind(); } } protected void cmdPostSession_Click(object sender, EventArgs e) { if (Page.IsValid) { Session[this.txtKey.Text] = this.txtValue.Text; List<string> sessions = new List<string>(); for (int i = 0; i < Session.Count; i++) sessions.Add(string.Format("Key={0},Value={1}", Session.Keys[i], Session[i].ToString())); this.blSessionList.DataSource = sessions; this.blSessionList.DataBind(); } } } }

此段程式碼會載入目前可用的 Session 資料顯示在畫面,同時允許新增資料到 Session 中,這會在稍後我們測試 Web Farm 環境時的 Session 同步狀態。

Step 8. 請打開 Web.config 檔案,並加入下列的 <appSettings> 設定::

<appSettings> <add key="DataConnectionString" value="UseDevelopmentStorage=true"/> </appSettings>

另外,再於 <system.web> 區段中加入下列 Session State 的設定,這段設定會使用 AspProviders.dll 內的 Microsoft.Samples.ServiceHosting.AspProviders.TableStorageSessionStateProvider 來連接 Table Storage 以保存 Session 狀態資料:

<sessionState mode="Custom" customProvider="TableStorageSessionStateProvider"> <providers> <clear/> <add name="TableStorageSessionStateProvider" type="Microsoft.Samples.ServiceHosting.AspProviders.TableStorageSessionStateProvider" applicationName="WebFormAzureStore" /> </providers> </sessionState>

Step 9. 請開啟 WebRole.cs,並在 OnStart() 事件常式中加入下列紅字部份的程式碼:

public override bool OnStart() { DiagnosticMonitor.Start("DiagnosticsConnectionString"); CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) => { configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)); }); Trace.WriteLine("Current Instance: " + RoleEnvironment.CurrentRoleInstance.Id); return base.OnStart(); }

這段程式碼會命令 Windows Azure Fabric Agent 向 ServiceConfiguration.cscfg 中讀取設定資料。

Step 10. 請再回到 WebAppLoadBalanceCloud 雲端專案中,將執行個體加大,筆者設定為 5 個執行個體:

Step 11. 按 F5 以 Development Fabric 啟動應用程式。

 

NOTE

您可能會在第一次執行時看到這個訊息:

這是因為一次啟動多個執行個體,但 Table Storage 還沒有建立該表格,以致於有兩個以上的執行個體同時在呼叫 CloudTableClient.CreateTableIfNotExist() 時都判斷當時儲存區內沒有表格的登錄資料所致。您可以停止應用程式並再次啟動,就不會有這個訊息了。您也可以先以單一執行個體來啟動,先讓表格被登錄後,再以多個執行個體來啟動。

 

啟動完成後,您可以打開 Development Fabric 來觀察執行個體的運行狀況:

接著,回到由 Visual Studio 所打開的瀏覽器,會看到應用程式的首頁:

您可以先按 F5 來重新整理(間隔不要太短),您會發現執行個體編號會隨機的改變,表示內建的 Load Balancer 會視情況調配處理要求的伺服器:

接著,請在 Session 資料中新增幾筆資料,這些資料都會顯示在畫面上:

然後,請按 F5 來重新整理(間隔不要太短),您會發現執行個體編號會改變,但 Session 的資料仍然存在。

 

NOTE

重新整理時,請注意不要讓表單重新傳送資料。

 

經過此範例,讀者應該知道如何使用微軟所提供的 TableStorageSessionProvider 元件結合 Windows Azure Table Storage 來在 Web Farm 上保存狀態的資料。

實作狀態管理功能-SQL Azure

除了 Table Storage 外,讀者也可以選擇使用 SQL Azure 資料庫來保存狀態資料,若要使用 SQL Azure 的話,除了要在 SQL Azure 上建立一個資料庫外,還要在資料庫內登錄 Session 所要使用的資料表與資料物件(如預存程序),才可以被 ASP.NET 應用程式取用。但麻煩的是,原本由 .NET Framework 所內建的 ASP.NET SQL Server 註冊工具(aspnet_regsql.exe)所使用的 SQL 指令無法相容於 SQL Azure 資料庫,因此我們必須要自行處理在 SQL Azure 中登錄必要物件的過程。不過,SQL Azure Team 很佛心的幫我們做掉了這一段,因此我們只需要將指令送到 SQL Azure 中執行即可,我們接下來就來進行。

Step 1. 請到 SQL Azure Team Blog 的網站:https://blogs.msdn.com/b/sqlazure/archive/2010/08/04/10046103.aspx,下載附件的 AzureSession.zip 壓縮檔解壓縮,會得到 ASPStateInstall.sql 以及 InstallSqlState.sql 兩個指令碼檔案,前者是建立名稱為 ASPState 的資料庫,後者則是建立 Session 必要的資料庫物件。

 

NOTE

下載點在文章的最下方:

 

Step 2. 請開啟 SQL Server Management Studio(必須是 2008 R2 的版本),然後連到 SQL Azure Server 上的 master 資料庫,並執行 ASPStateInstall.sql,然後切換到 ASPState 資料庫,執行 InstallSqlState.sql,以完成資料庫的設定。

 

NOTE

若您有自己的資料庫,您可以略過安裝資料庫的部份,直接在您的資料庫執行 InstallSqlState.sql 即可。

 

Step 3. 請修改在前面所做的 LoadBalanceWebApp 專案的 Web.config 檔,先將原本使用 Table Storage 的 Session State 部份註解掉,再加上下列設定:

<sessionState mode="SQLServer" sqlConnectionString="Server=...;Trusted_Connection=False;Encrypt=True;" cookieless="false" timeout="20" allowCustomSqlDatabase="true" />

 

NOTE

其中的 sqlConnectionString 部份,請依您的 SQL Azure 資料庫連線字串來設定。

 

設定完成後,請按 F5 啟動應用程式,並依照 Step 11 的部份進行測試,您會發現結果會與使用 Table Storage 時一樣,但狀態資訊會存到 SQL Azure 資料庫。

結語

在本文中筆者講述了在雲端應用程式上基於可擴充性所導致的狀態管理問題,並使用 Table Storage 與 SQL Azure 處理在雲端應用程式上跨執行個體(Web Farm)的狀態保存問題,讓讀者可以對雲端應用程式上的狀態管理議題有較深入的了解,並且知道要如何去處理與解決可能發生的跨執行個體的狀態保存問題。

 

範例程式下載

本文的範例程式可由此下載:
http://cid-266e4a8b12eeb19e.office.live.com/self.aspx/Sample%20Codes/WindowsAzureAppStateManagment.rar