Windows Azure - 使用Windows Azure Storage解決Sessios問題

21 April 2012 — Written by Sky Chang
#ASP.NET#Azure

2012/6/16 更新 目前官方已經提出新的Cache來解決Session了,請參考這篇這篇

2012/5/28 更新 根據官方說法,如果使用Table當Sesson,碰上一個Role裡面有多個Web Site時,可會出問題,所以官方建議使用Caching或是SQL Server,是比較建議的做法。

有玩過Windows Azure的人,應該都會知道Web Role可以是多個的 ( 只要口袋夠深 ),就好比下面這張圖 (在微軟的邊做邊學找到的)一樣,我們可以在HTTP的請求進來的時候,利用六台的Web Role來進行服務,這也就是Load Balance的機制。

image

其實Windows Azure這點很棒的,因為我們可以輕鬆地去平衡大量的需求,當流量多的時候,我們就多開幾台Web Role,當人少的時候,我們就少開幾台Web Role!而且我們不需要做任何的事情!

當然,話是這樣說,現實總是比較殘酷一點的,Load Balance機制有一個最大的特點,舉例來說,我們現在有Web RoleA和Web RoleB兩台提供服務,當進入Default.aspx( 首頁 )的時候,可能會在Web RoleA,而點下Login.aspx ( 登入頁面 )的時候,可能是由Web RoleB來提供服務,超酷的吧!!

但問題來了,如果今天在Web RoleB登入,並在Web RoleB利用Session來記錄登入資訊,當下一頁跑到Web RoleA的時候,我相信,客戶就會打電話進來罵了,然後我們就會被老闆罵了…。

會發生這個原因其實很簡單,因為Session通常都記錄在記憶體裡面,而我們登入的資訊記錄在Web RoleB的記憶體上,當下一頁切換到Web RoleA的時候,當然就會找不到內容。

話說回來,我相信如果如果比較大的公司,有導入Load Balance機制的程式開發人員,一定都會針對這方面來,避免使用Session寫程式,或是將資訊寫到資料庫,或是利用Cookie等等的方法,但隨著時代進步,超方便的Windows Azure上市,讓中小企業也可以享受到Load Balance機制,而這時如果舊有的程式,沒有針對Load Balance機制去做設計,然後就發佈到雲端上,就會發生上面所講的問題,被老闆罵了。

( 如果只放一台Web Role,當然就不會有這種問題了。 )

沒圖沒真相

所謂沒圖沒真相,所以小弟我稍微寫了一個超簡單的程式來驗證一下。

小弟我寫了兩頁A.aspx和B.aspx。

image

反正是自己機器上面模擬,就給他開個四台吧。

image

A.aspx的頁面也超級簡單,就放一個Label和一個超連結。

`<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="A.aspx.cs" Inherits="AzureAspNetSession.A" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
        <a href=B.aspx>Go B Page</a>
    </div>
    </form>
</body>
</html>
`

A.aspx.cs的程式碼也超簡單,但這邊我使用了RoleEnvironment.CurrentRoleInstance.Id來識別目前這台Web Role是哪一台,另外,就是將session存了一個A PageSession的字串。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace AzureAspNetSession
{
    public partial class A : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Label1.Text = RoleEnvironment.CurrentRoleInstance.Id;
            Session.Clear();
            Session["Test"] = "A PageSession";
        }
    }
}

B.aspx

`<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="B.aspx.cs" Inherits="AzureAspNetSession.B" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
        <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label>
    </div>
    </form>
</body>
</html>
`

B.aspx.cs的程式碼也很簡單,如果Session不是空,就Show出來,如果是空,就Show出Session is null,當然,也要把目前是哪一台Web Role Show出來。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace AzureAspNetSession
{
    public partial class B : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Label1.Text = RoleEnvironment.CurrentRoleInstance.Id;
            if(Session["Test"] != null)
            {
                Label2.Text = Session["Test"].ToString();
            }else
            {
                Label2.Text = "Session is null";
            }
        }
    }
}

然後執行看看,我們可以看到,目前是連到第二台Web Role,然後我們按下 Go B Page的超連結。

image

到B.aspx,Session卻是Null!,那是因為我們現在連到第一台。

image

所以,這個實驗可以發現,如果連到別台,Session是真的可能會抓不到,所以建議,如果寫這方面程式的時候,盡量不要用Session。

現實是殘酷的

沒錯,現實是殘酷的,當老闆要求幾天內將程式搬到Windows Azure上去,而且要開兩台以上;但是程式裡面已經一堆Session的時候,該怎麼辦!? ( 實際上可能還會考慮到更多問題,反正這只是一個梗,大家就不要太計較了XDD ),其實還有幾種方法,我們這邊就先使用最簡單的方法吧!也就是將Session存到Windows Azure Storage裡面去。

動手寫程式

我們延續之前的範例,首先,我們要先在Web.Config裡面的System.web下,加上這一串設定。

<?xml version="1.0" encoding="utf-8"?>

<!--
  如需如何設定 ASP.NET 應用程式的詳細資訊,請造訪
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->

<configuration>
    <system.web>
      <compilation debug="true" targetFramework="4.0" />
      <!-- 加入以下 -->
      <sessionState mode="Custom"
                       customProvider="TableStorageSessionStateProvider">
        <providers>
          <clear/>
          <add name="TableStorageSessionStateProvider" 
               type="Microsoft.Samples.ServiceHosting.AspProviders.TableStorageSessionStateProvider"/>
        </providers>
      </sessionState>
      <!-- 到此為止 -->
    </system.web>

</configuration>

接下來,很不幸的,我們必須去下載一個dll,大家可以從這邊下載,這是官網提供的一個範例,內容就是在講如何使用Windows Azure Storage來存Session,下載完後,將之解壓縮,我們就可以看到下有一個C#\AspProviders\bin\Debug\AspProviders.dll檔案,這個檔案就是關鍵,然後我們就可以回到我們的專案,將這個檔案參考進來,參考進來後會如下圖。

image

接下來,點一下AspProviders,然後看一下"複製到本機"是否為True。

image

接下來,我們要設定連線字串,這裡我們使用的是Local的Storage,如果要使用Cloud上的,可以自己在這邊設定,如果不會修改,可以參考這篇

image

設定完後,就可以開始執行看看了,一樣我們先從A.aspx開始。

image

當我們切換到B.aspx時,可以發現,現在在另外一台Web Role上,但是他卻抓的Session了。

image

我們可以從伺服器總管查看Blob和Table,發現,他現在將Session存在這邊了。

image

這樣就完成嚕!!

後記

目前官方並沒有正式提供Library來實作這塊,官方比較建議利用Azure Caching來配合Session或是SQL Server,但不管怎樣,Table至少是最便宜的方法,至於好或不好,就見仁見智嚕。

參考資料

Sky & Study4.TW