最近項目由於要負載均衡因此就使用了sessionState的Session共享,可是卻發現多臺服務器中有個別服務器的Session沒有共享,因而就有了這篇文章,下面開始說說。web
這個基本上就分兩種狀況:一種就是在多臺服務器上,在IIS中創建的網站的標識符同樣(要想都同樣,須要在建網站的時候輸入一樣的描述符,就我知道的方 法,只有在第一次建這個網站的時候,輸入的描述符要同樣,建好後再改描述符是沒用的,它會保留之前的標識符,另外這種狀況有一個特例,就是默認網站,雖然 在iis中能夠看到默認網站的標識符都是1,可是它不屬於第一種狀況,它屬於第二狀況)。
另一種狀況是,網站的標識符不同,好比在兩臺服務器上不一樣名的網站,或者在同一臺服務器上的兩個或多個網站(在同一IIS下這種狀況根本就不可能會有相同標識符的網站),還有就是默認網站的狀況。
這個我在網上有看到別人說,說到要起相同網站名的文章有一篇,但那個說的不對,它只是強調起相同的名字,其實真正是要IIS中網站的標識符同樣,不是它們的網站名(也就是描述符)同樣,同一IIS下也能有兩個網站擁有相同的標識符。這點必定要注意。
另外要說的是,若是使用cookie來存儲sessionid的話,這個必定要確保這些共享session的網站在網頁瀏覽器中可使用相同的域名來訪問 到,好比a.test.com,b.test.com,c.test.com等等的.test.com域的網站。這些網站所在的計算機或者說服務器不非得 真的處在這樣的一個域或者網站有這樣的一個域名什麼的,只要能讓瀏覽器所在的計算機經過這些域名訪問到這些服務器就行,我本身的測試環境就是家裏的兩臺計 算機的一個小內網,也沒有域,只是修改了使用瀏覽器作測試的機器上的C:\WINDOWS\system32\drivers\etc\host,讓瀏覽 器可使用那些域名訪問到另外的服務器便可。這個也說明在客戶端的瀏覽器使用cookie的一個機制,只要是地址欄裏輸入的頂級域同樣,好比訪問 a.test.com,b.test.com什麼的,只要都是test.com的,它就會使用那些cookie了。因此說要確保這些服務器都在同一個域名 下面,這樣這些服務器才能獲得同樣的sessionid,由於asp.net會把sessionid存儲在叫ASP.NET_SessionId的 cookie中,只有訪問一樣的域名的網站纔會發送這個COOKIE,否則訪問其餘域名的網站,好比a.test1.com,瀏覽器不會發送這個 cookie,即便使用一樣的stateserver,不能獲得相同的sessionid,也沒法共享session。
先說一下stateserver的web.config等的配置,這些不管是上面說的哪兩種狀況都是同樣的,在web.config中須要添加如下兩個節點:瀏覽器
1
2
3
4
|
<!--web.config中sessionState節點的配置方案-->
<sessionState cookieName=
"DotNesession"
mode=
"StateServer"
stateConnectionString=
"tcpip=127.0.0.1:42424"
cookieless=
"false"
timeout=
"30"
/>
<httpRuntime targetFramework=
"4.5"
/>
<machineKey validationKey=
"D4033EDA0C4490B94331CAD18C43A72146BC0083FBB0D5ABE876A43EE271904EB2DAE181096561A451F7ADF61ED9EDBE40C0B920ED45682F96A8EA2788B96913"
decryptionKey=
"DDE7A8EF5E6B8C31DA73F8D942FF28B0790BF0F2446202BBA0F80F39A5375BAA"
validation=
"SHA1"
decryption=
"AES"
/>
|
這個machineKey的值能夠是隨意的,但必定要配成同樣的,由於須要用它來給session進行解密。另外要說的就是若是stateserver配爲遠程的服務器的話,則須要修改stateserver服務器的註冊表的[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters]中的AllowRemoteConnection,把它改爲1,默認是不容許遠程鏈接的,值爲0,另外在這裏也能夠改Port 端口號,默認的stateserver須要用的asp.net狀態服務須要監聽42424端口。而後在stateserver上啓動asp.net狀態服務,另外要是先啓動了這個服務,再修改的註冊表,則須要再重啓一下這個服務。
最後說一下以上兩種狀況分別須要寫不一樣的處理程序,其實處理第二種狀況的程序也能處理第一種狀況,不過第一種狀況的程序比第二種的簡單。在這裏先說一下分 類的標準,這是根據,在若是沒有寫處理程序的狀況下,stateserver會把這些要共享session的網站產生的session放置到 appdomin的狀況來區分的。
第一種狀況,由於網站的標識ID是相同的,固然除了默認網站那個特例,由於aps.net狀態服務應該是根據網站的標識id來決定放置session的 appdomain的id, 因此放置這些網站的appdomain的id也是相同的,所以這些網站產生的session會被放在同一個appdomain中,因此處理程序很簡單,只 要確保ASP.NET_SessionId這個cookie能在這些網站中共享就好了。網上給出來的通用作 法,就是在自定義實現IHttpModule的自定義類中,在request結束的事件中,改寫ASP.NET_SessionId的cookie的 domain爲這些網站的主域名便可,按上例來講,即.test.com。代碼以下:服務器
public class Test:IHttpModule { #region IHttpModule 成員 void IHttpModule.Dispose() { //throw new Exception("The method or operation is not implemented."); } void IHttpModule.Init(HttpApplication context) { context.EndRequest += new EventHandler(this.EndRequest); } #endregion private void EndRequest(object sender, EventArgs args) { HttpApplication application = sender as HttpApplication; for (int i = 0; i < application.Response.Cookies.Count; i++ ) { if( application.Response.Cookies[i].Name == "ASP.NET_SessionId") application.Response.Cookies[i].Domain = ".test.com"; } } }
在EndRequest當中,或在此類中的其餘相應方法中,切記不要沒有for循環而使用如下代碼來賦值
application.Response.Cookies["ASP.NET_SessionId"].domain = ".test.com"
甚至是不用循環而直接在方法中直接使用像string str = application.Response.Cookies["ASP.NET_SessionId"].value這樣的代碼。否則這樣會產生一種這樣 的效果,每刷新兩次瀏覽器,session就會發生更新,這樣就致使程序出現問題。內部的緣由我也不甚瞭解,但從表面來看,是由於 application.Response.Cookies這個集合只有在網站最開始打開的時候,能夠經過循環訪問到ASP.NET_SessionId 這個cookie,而後再刷新網頁,這個循環就不會訪問到ASP.NET_SessionId這個cookie了,可是使用上面的代碼,則仍能夠直接訪問 到ASP.NET_SessionId這個cookie。應該是就由於在這種狀況下修改了這個cookie,致使cookie或什麼發生了變化等緣由,最 終讓下一個提交請求被認爲是新的請求,而產生了新的session。
第二種狀況,根據第一種狀況中的描述,如今由於網站的標識id不一樣(默認網站狀況除外),因此每一個網站放置session的appdomain的id也不 同,所以這些session被放在了不一樣的appdomain中,因此像上面那樣的代碼,雖然能夠確保將想共享的sessionid傳至服務器,可是因爲 session被放在了不一樣的appdomain中,因此其實是產生了兩個相同sessionid的session被分別放在了屬於不一樣網站的兩個 appdomain中, 這樣確定也是不能共享session的了,這個解決起來就麻煩點,須要經過反射來調用一個asp.net沒有公開的類 OutOfProcSessionStateStore,看名字也知道它表明的是進程外session存儲了,來修改它一個靜態成員s_uribase, 此成員表明state外部存儲須要訪問的appdomain的一個內部id,只要在建立session前,設置一個相同的appdomain的id(固然 看程序這個id其實好像能夠隨意設了,應該不侷限於只使用網站的根域名),這樣就能確保取session和放置session都到同一個 appdomain中。大體的代碼以下:cookie
public class CookieTest:IHttpModule { #region IHttpModule 成員 void IHttpModule.Dispose() { //throw new Exception("The method or operation is not implemented."); } void IHttpModule.Init(HttpApplication context) { //throw new Exception("The method or operation is not implemented."); Type stateServerSessionProvider = typeof(HttpSessionState).Assembly.GetType("System.Web.SessionState.OutOfProcSessionStateStore"); FieldInfo uriField = stateServerSessionProvider.GetField("s_uribase", BindingFlags.Static | BindingFlags.NonPublic); if (uriField == null) throw new ArgumentException("UriField was not found"); uriField.SetValue(null, ".test.com"); context.EndRequest += new EventHandler(this.EndRequest); } private void EndRequest(object sender, EventArgs args) { HttpApplication application = sender as HttpApplication; for (int i = 0; i < application.Response.Cookies.Count; i++) { application.Response.Cookies[i].Domain = ".test.com"; } } }
最後在web.config中註冊這個http模塊:
<httpModules>
<add name="test" type="WebApplication5.Test,WebApplication5"/>
</httpModules>session
這樣就能夠在相同頂級域名的網站間共享session狀態了.app
還能夠在Global.asax.cs中加入下面代碼負載均衡
public override void Init() { base.Init(); foreach (string moduleName in this.Modules) { string appName = "APPNAME"; IHttpModule module = this.Modules[moduleName]; SessionStateModule ssm = module as SessionStateModule; if (ssm != null) { FieldInfo storeInfo = typeof(SessionStateModule).GetField("_store", BindingFlags.Instance | BindingFlags.NonPublic); SessionStateStoreProviderBase store = (SessionStateStoreProviderBase)storeInfo.GetValue(ssm); if (store == null)//In IIS7 Integrated mode, module.Init() is called later { FieldInfo runtimeInfo = typeof(HttpRuntime).GetField("_theRuntime", BindingFlags.Static | BindingFlags.NonPublic); HttpRuntime theRuntime = (HttpRuntime)runtimeInfo.GetValue(null); FieldInfo appNameInfo = typeof(HttpRuntime).GetField("_appDomainAppId", BindingFlags.Instance | BindingFlags.NonPublic); appNameInfo.SetValue(theRuntime, appName); } else { Type storeType = store.GetType(); if (storeType.Name.Equals("OutOfProcSessionStateStore")) { FieldInfo uribaseInfo = storeType.GetField("s_uribase", BindingFlags.Static | BindingFlags.NonPublic); uribaseInfo.SetValue(storeType, appName); } } } } }