分站点部署的小总结——多站点会话共享
随着IP访问量的增长,公司考虑对站点按频道进行分离。
将一个站点分离成N个子站点有诸多好处:
1. Web服务器的分压:来自客户端的请求将分散到各个站点分别处理
2. 有利于多服务器部署:每个站点都可以部署到独立的服务器上
3. 降低系统的耦合度:分离站点后,每个站点可以进行独立的升级管理,而不必顾虑是否影响其他频道
可能还有其他的一些好处,但我还没有想到
将原来的一个站点分离成N个子站点,我是第一次做这事,其中存在的许多困难一开始根本想不到。首先要考虑的就是多站点单点登录的问题,用户从任何一个子站
点登录之后,进入其他的子站点,都能自动进行身份验证而不需要再登录。这是多站点部署所要解决的最首要的问题,由于公司现有系统验证登录用户身份的方式是
通过保存在会话数据中的用户信息,用户在登录之后,实例化一个自定义的会话对象,在这个对象里保存了经过加密的用户身份、权限等信息,要求必须在各个子站
点都能访问这个会话对象。因此必须实现各个站点间的会话数据共享。网上可以找到很多这方面的资料,但都不是我想要实现的,例如:
1、采用IIS虚拟目录的方式,把多个站点放在同一个站点中,这种方法虽然能解决会话共享,但并不是真正的站点分离。
2、采用SSO的方法,虽然能够实现多站点用户身份验证,但是由于现有系统的特殊性,也不能满足要求
会话数据保存在指定的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.TempGetAppID3
@appName tAppName,4
@appId int OUTPUT5
AS6
SET @appName = LOWER(@appName)7
SET @appId = NULL8

9
SELECT @appId = AppId10
FROM [JSEC_SessionDB].dbo.ASPStateTempApplications11
-- WHERE AppName = @appName //屏蔽该行12

13
IF @appId IS NULL BEGIN14
BEGIN TRAN 15

16
SELECT @appId = AppId17
FROM [JSEC_SessionDB].dbo.ASPStateTempApplications WITH (TABLOCKX)18
WHERE AppName = @appName19
20
IF @appId IS NULL21
BEGIN22
EXEC GetHashCode @appName, @appId OUTPUT23
24
INSERT [JSEC_SessionDB].dbo.ASPStateTempApplications25
VALUES26
(@appId, @appName)27
28
IF @@ERROR = 2627 29
BEGIN30
DECLARE @dupApp tAppName31
32
SELECT @dupApp = RTRIM(AppName)33
FROM [JSEC_SessionDB].dbo.ASPStateTempApplications 34
WHERE AppId = @appId35
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
END39
END40

41
COMMIT42
END43

44
RETURN 045
GO46

经过以上修改之后,下面要实现多个站点共用同一个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);
经过以上步骤之后,终于真正的实现了多站点会话共享。
到目前为止,还有一些问题没有想明白:
数据库中保存的SessionID的后8位代表什么?与表ASPStateTempApplications中的AppName有什么关系?
浙公网安备 33010602011771号