多站点会话共享
现在以应对各种需求.需要将一个站点分离成N个子站点,这比想像中多了些阻碍.在我做过一个多站点分布后.就发现了会话不能共享的问题.下面谈谈解决方案:
一.
会话数据保存在指定的SQLServer数据库中,会话数据通过SessionID进行查找,理论上只需要使多个站点共享同一会话数据库即可共享会话数据,确定了思路便好办。多个站点共享统一会话数据库在.NET下是很容易的事情,只需要在WEB.CONFIG里配置sessionState节点指定会话存储模式以及相同的会话数据库即可。事情没有这么简单,不同的站点其SessionID是不同的,就算SessionID相同了,保存在会话数据库中的实际SessionID的值也不只是网站生成的SessionID,而是在SessionID后加上了站点的AppName,因此即使各个站点共享了会话数据库也不能共享会话数据。打开会话数据表ASPStateTempSessions中的记录,可以发现如下几个数据
ASPStateTempSessions各个字段的意义如下:
表1 ASPStateTempSessions表
列 |
类 型 |
描 述 |
SessionId |
char(88) |
索引字段,它表示会话ID |
Created |
DateTime |
指出会话被创建的时间。默认值为当前时间 |
Expires |
DateTime |
指出会话将到期的时间。该值一般等于会话状态的创建时间加上Timeout中指定的分钟数。注意,Created指会话的创建时间,而Expires把分钟数加到第一个数据项被添加到会话状态的时间 |
LockDate |
DateTime |
指出会话被锁定以添加最后一个数据项的时间。该值表示为当前的UTC(Universal Time Coordinate)时间 |
LockDateLocal |
DateTime |
与LockDate一样,但是它只表示系统的本地时间。ASP.NET 1.x不支持该列 |
LockCookie |
int |
指出该会话被锁定的次数——即,访问次数 |
Timeout |
int |
指出会话的超时时间(以分为单位) |
Locked |
bit |
指出会话当前没有被锁定 |
SessionItemShort |
VarBinary(7000) |
可以取null的字段。它表示指定会话中的值。这些字节的布局等同于StateServer提供程序所述的布局。如果对字典进行序列化需要7000多字节,则使用SessionItemLong |
SessionItemLong |
Image |
可以取null的字段,表示一个超过7000字节的会话状态的序列化版本 |
Flags |
Int |
指示SessionStateActions枚举类型的行动标记(初始化数据项)。ASP.NET 1.x不支持该列 |
其中的SessionId包括两个部分:网站生成的24位SessionID及8位AppName(这个AppName是怎么来的呢?)对于不同的站点,其AppName不同,在能够在不同站点下使24位SessionID相同的情况下,要保证经过组合加上AppName后的SessionID相同,可以通过修改存储过程TempGetAppID,使其得到的SessionID与AppName无关,修改TempGetAppID如下:
2 CREATE PROCEDURE dbo.TempGetAppID
3 @appName tAppName,
4 @appId int OUTPUT
5 AS
6 SET @appName = LOWER(@appName)
7 SET @appId = NULL
8
9 SELECT @appId = AppId
10 FROM [JSEC_SessionDB].dbo.ASPStateTempApplications
11 -- WHERE AppName = @appName //屏蔽该行
12
13 IF @appId IS NULL BEGIN
14 BEGIN TRAN
15
16 SELECT @appId = AppId
17 FROM [JSEC_SessionDB].dbo.ASPStateTempApplications WITH (TABLOCKX)
18 WHERE AppName = @appName
19
20 IF @appId IS NULL
21 BEGIN
22 EXEC GetHashCode @appName, @appId OUTPUT
23
24 INSERT [JSEC_SessionDB].dbo.ASPStateTempApplications
25 VALUES
26 (@appId, @appName)
27
28 IF @@ERROR = 2627
29 BEGIN
30 DECLARE @dupApp tAppName
31
32 SELECT @dupApp = RTRIM(AppName)
33 FROM [JSEC_SessionDB].dbo.ASPStateTempApplications
34 WHERE AppId = @appId
35
36 RAISERROR('SQL session state fatal error: hash-code collision between applications ''%s'' and ''%s''. Please rename the 1st application to resolve the problem.',
37 18, 1, @appName, @dupApp)
38 END
39 END
40
41 COMMIT
42 END
43
44 RETURN 0
45GO
46
经过以上修改之后,下面要实现多个站点共用同一个SessionID,对ASP.NET的会话模型System.Web.SessionState.SessionIDManager.GetSessionID(HttpContext context)方法进行反编译,分析其源代码:
2 {
3 string id = null;
4 this.CheckInitializeRequestCalled(context);
5 if (this.UseCookieless(context))
6 {
7 return (string) context.Items["AspCookielessSession"];
8 }
9 HttpCookie cookie = context.Request.Cookies[Config.CookieName];
10 if ((cookie != null) && (cookie.Value != null))
11 {
12 id = this.Decode(cookie.Value);
13 if ((id != null) && !this.ValidateInternal(id, false))
14 {
15 id = null;
16 }
17 }
18 return id;
19 }
20
21
22
可以看到,SessionID是通过客户端的Cookie进行保存的,因此,只要使各个站点共用同一个Cookie文件保存SessionID即可达到目的。如何使不同的站点共用同一个Cookie呢?只要设置cookie.Domain为相同的域即可。在页面的PageLoad事件中加上以下代码:
2 cookie.Domain = ".websitename.com";
3 cookie.Expires = DateTime.Now.AddDays(365); //设置Cookie保存的天数,不可少于1天
4 HttpContext.Current.Response.Cookies.Add(cookie);
经过以上步骤之后,终于真正的实现了多站点会话共享。
二.
和上面前面一样.在WEB.CONFIG里配置sessionState节点指定会话存储模式以及相同的会话数据库.由上面第一种方法分析可以知道.AppName不一样.上面是让它忽略AppName.下面就是让他AppName一样.这样也能达到同样效果.具体做法是:
在每个子站点必须设置相同路径主目录(比如.有三台服务器.WEB程序IIS主目录都应指向同一位置.如都在D:\WebRoot下)
这样.AppName会得一个相同的值.
注意:如果你是在已经建立好的IIS站点上.采用这种模式.建议选删除原来的WEB站点.再新建成每台都一样的物理路径的IIS站点.我的理解是.一但建立就生成了AppName.以后修改它是不会变的(不知道是不是这个原因,但我就是这样才解决的.删除其它已建立好的.重新建立的),所以保证真正的AppName一样.得严格按照上面的做法.