2019獨角獸企業重金招聘Python工程師標準>>>
應用部署結構(精簡):
站點部署在
Nginx
后面,以Nginx
作為反向代理,不希望在Nginx
上設置ip_hash
,實現比較真實的負載均衡效果。
這時考慮到需要讓site1
和site2
同時共享會話信息,進行如下的配置:
默認情況下asp.net站點的會話模式是采用Inproc模式,這種模式在站點因IIS重啟會導致丟失用戶會話(已經登陸的用戶會自動重定向到登陸頁面),在本次實踐中使用SqlServer模式
創建會話數據庫(獨立于應用):
aspnet_regsql.exe -S server_ip -E -ssadd -sstype c -d dbname
更多參數可以參考命令的幫助aspnet_regsql.exe /?,通常路徑在**C:\Windows\Microsoft.NET\Framework64\{.net_version}**下
配置如下:
<sessionState mode="SQLServer" sqlConnectionString="server=server_ip;database=dbname;uid=user;pwd=pwd;" allowCustomSqlDatabase="True" cookieless="false" timeout="20" />
會話數據庫的表ASPStateTempApplications中存放應用的信息(appId和AppName),每一個站點一條記錄,我兩個站點分別使用該數據庫所以有兩條記錄,如圖:
通過以上的配置, 貌似完成了會話的共享,這時候通過Nginx
的IP
地址發送兩個請求到服務器(一個登陸,一個獲取用戶考試列表)。通過Nginx
的日志發現兩次請求被分發到兩臺不同的后端服務器:
從圖中可以看出第一次請求是192.168.2.2:80803
這個站點處理,第二個請求被192.168.2.5:8083
這個站點處理。明明已經設置了會話共享為什么第二次請求被提示為未登錄。檢查是否登陸的代碼如下:
public class WebApiCheckLoginFilterAttribute : ActionFilterAttribute{public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext){LoginService service = new LoginService();string controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName;string actionName = actionContext.ActionDescriptor.ActionName;bool isAuthAction = controllerName.Equals("ExamAPI") && actionName.Equals("Login");if (!isAuthAction){HttpSessionState Session = System.Web.HttpContext.Current.Session;if (Session["user"] == null){throw new HttpException((int)HttpStatusCode.NonAuthoritativeInformation, "未登錄用戶或已在其他設備中登錄");}if (!service.IsOnline(Model.DeviceType.Phone, Session.SessionID)){throw new HttpException((int)HttpStatusCode.NonAuthoritativeInformation, "未登錄用戶或已在其他設備中登錄");}}base.OnActionExecuting(actionContext);}}
上述代碼是為了檢查如果session['user']==null,就會提示是未登陸。已經配置了會話共享為什么session['user']還是獲取不到用戶對象?通過各種嘗試發現asp.net在處理session的時候會根據站點的AppId加上瀏覽器上的SessionId在數據庫表([ASPStateTempSessions])中創建會話記錄,SessionId的值如下:
0kf0zoq4to3h0z0b1eztw43s28d8c075
- 28d8c075 是
AppId
685293685的16進制(對應的AppId) - 0kf0zoq4to3h0z0b1eztw43s是客戶端
cookie
中存放的sessionid
通過查看存儲過程[dbo].[TempGetAppID]
的代碼,發現asp.net會通過每個站點的AppName(見表ASPStateTempApplications
)去獲取AppId
,然后將SessionId
+AppId
(16進制)作為主鍵插入到表ASPStateTempSessions
中,這樣的處理邏輯是保證一個會話數據庫會被N個站點共同使用,不用每個站點創建自己的會話數據庫。隱藏的問題是如果站點的AppName
不一樣會導致獲取的AppId
不一樣達不到會話共享的目的。
驗證過程如下:向Nginx
發送兩個請求(一個登陸,一個獲取考試列表)會在ASPStateTempSessions表中產生兩條記錄,正常情況應該兩個請求也應該只產生一條會話記錄。
為了保證只產生一條會話記錄可以通過讓site1
和site2
的AppName保持一致,每次通過存儲過程[dbo].[TempGetAppID]
獲取的AppId
都是同一個。 修改方式為:
修改完成后,再次發送兩個請求(一個登陸,一個獲取考試列表),兩個請求經過Nginx
分發到兩個站點后在ASPStateTempSessions
只產生一條會話記錄,這樣才真正的實現會話的一致性。
配置machineKey
,在做asp.net站點集群的時候如果站點中使用到cookie
或viewstate
的話需要配置machineKey
來確保多臺機器共享驗證和ViewState
.每個節點上的machineKey
配置必須一致
按照MSDN的標準說法:“對密鑰進行配置,以便將其用于對 Forms 身份驗證 Cookie 數據和視圖狀態數據進行加密和解密,并將其用于對進程外會話狀態標識進行驗證.加密和解密使用的就是
machineKey
生成machineKey的代碼
public static string CreateMachineKey(int length){byte[] random = new byte[length / 2];RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();rng.GetBytes(random);StringBuilder builder = new StringBuilder();for (int i = 0; i < random.Length; i++){builder.Append(string.Format("{0:X2}", random[i]));}return builder.ToString();}string decryptionKey= CreateMachineKey(48);string validationKey = CreateMachineKey(128);
web.config
配置內容
<system.web><compilation targetFramework="4.0" /><machineKey validationKey="validationKey" decryptionKey="decryptionKey"/>
...other...</system.web>
小結
asp.net 站點的會話模式如果選用
SqlServer
的話,需要保證站點群(可能有N個站點)通過[dbo].[TempGetAppID]
存儲過程獲取AppId
后的結果是相同的。可以通過修改存儲過程,也可以通過修改AppName。