Asp.net 实现Session分布式储存(Redis,Mongodb,Mysql等) sessionState Custom
对于asp.net 程序员来说,Session的存储方式有InProc、StateServer、SQLServer和Custom,但是Custom确很少有人提及。但Custom确实最好用,目前最实用和最灵活一种方式,因为Custom可以实现各种情况的Session存储,特别对于大型网站尤为重要,解决Session丢失和Session效率的最佳实现方式,也是实现单点登录的最佳途径。 对于InProc、StateServer和SQLServer各自优缺点,网上一大堆,这里就不做详解。
重要的两点
1、首先是关于Session存储,Session的存储并不是我们想象的,当设置Session时立即往数据容器里插入或者修改数据,当获取Session的值时就是立即去数据容器里获取值,这种理解是错误的(我之前就是这样理解的)。后来想想完全没必要这样操作,而且会大大影响效率。Asp.net的Session实现方式是每次请求前获取数据,当请求逻辑代码结束的时候在设置Session的值,所以说Session对数据容器的修改简单的意义只有两次,这个可能就要牵扯到管道流了。
2、第二个就是关于Asp.net网站异步的问题,当我们不设置Session的状态为只读时,我们每个用户的请求其实都是同步的,也就说每个用户请求网站同时只能有一个请求响应。理解这个就可以理解后面实现方法中需要锁的概念了。
SessionStateStoreProviderBase
SessionStateStoreProviderBase是asp.net框架为我们提供的一个用来存储Session提供程序所需的成员(就是实现这个类),我们知道的InProc、SQLServer、StateServer都是实现了这个抽象类。继承这个类需要有多个抽象方法需要实现。关于这多个方法没必要每个都实现,我们只关注我们需要实现的即可。说简单点就是对Session数据的增删改查(CRUD)。关于SessionStateStoreProviderBase的详情可以参考https://msdn.microsoft.com/zh-cn/library/system.web.sessionstate.sessionstatestoreproviderbase(v=vs.100).aspx
成员 |
说明 |
---|---|
采用当前请求的 HttpContext 实例作为输入,并执行会话状态存储提供程序必需的所有初始化操作。 |
|
采用当前请求的 HttpContext 实例作为输入,并执行会话状态存储提供程序必需的所有清理操作。 |
|
Dispose 方法 |
释放会话状态存储提供程序不再使用的所有资源。 |
采用当前请求的 HttpContext 实例和当前请求的 SessionID 值作为输入。从会话数据存储区中检索会话的值和信息,并在请求持续期间锁定数据存储区中的会话项数据。GetItemExclusive 方法设置几个输出参数值,这些参数值将数据存储区中当前会话状态项的状态通知给执行调用的 SessionStateModule。 如果数据存储区中未找到任何会话项数据,则 GetItemExclusive 方法将 locked 输出参数设置为 false,并返回 null。这将导致SessionStateModule 调用 CreateNewStoreData 方法来为请求创建一个新的 SessionStateStoreData 对象。 如果在数据存储区中找到会话项数据但该数据已锁定,则 GetItemExclusive 方法将 locked 输出参数设置为 true,将 lockAge输出参数设置为当前日期和时间与该项锁定日期和时间的差,将 lockId 输出参数设置为从数据存储区中检索的锁定标识符,并返回 null。这将导致 SessionStateModule 隔半秒后再次调用 GetItemExclusive 方法,以尝试检索会话项信息和获取对数据的锁定。如果 lockAge 输出参数的设置值超过 ExecutionTimeout 值,SessionStateModule 将调用 ReleaseItemExclusive 方法以清除对会话项数据的锁定,然后再次调用 GetItemExclusive 方法。 如果 regenerateExpiredSessionId 属性设置为 true,则 actionFlags 参数用于其 Cookieless 属性为 true 的会话。actionFlags 值设置为 InitializeItem (1) 则指示会话数据存储区中的项是需要初始化的新会话。通过调用CreateUninitializedItem 方法可以创建会话数据存储区中未初始化的项。如果会话数据存储区中的项已经初始化,则actionFlags 参数设置为零。 如果提供程序支持无 Cookie 会话,请将 actionFlags 输出参数设置为当前项从会话数据存储区中返回的值。如果被请求的会话存储项的 actionFlags 参数值等于 InitializeItem 枚举值 (1),则 GetItemExclusive 方法在设置 actionFlags out 参数之后应将数据存储区中的值设置为零。 |
|
GetItem 方法 |
除了不尝试锁定数据存储区中的会话项以外,此方法与 GetItemExclusive 方法执行的操作相同。GetItem 方法在EnableSessionState 属性设置为 ReadOnly 时调用。 |
采用当前请求的 HttpContext 实例、当前请求的 SessionID 值、包含要存储的当前会话值的 SessionStateStoreData 对象、当前请求的锁定标识符以及指示要存储的数据是属于新会话还是现有会话的值作为输入。 如果 newItem 参数为 true,则 SetAndReleaseItemExclusive 方法使用提供的值将一个新项插入到数据存储区中。否则,数据存储区中的现有项使用提供的值进行更新,并释放对数据的任何锁定。请注意,只有与提供的 SessionID 值和锁定标识符值匹配的当前应用程序的会话数据才会更新。 调用 SetAndReleaseItemExclusive 方法后,SessionStateModule 调用 ResetItemTimeout 方法来更新会话项数据的过期日期和时间。 |
|
采用当前请求的 HttpContext 实例、当前请求的 SessionID 值以及当前请求的锁定标识符作为输入,并释放对会话数据存储区中的项的锁定。在调用 GetItem 或 GetItemExclusive 方法,并且数据存储区指定被请求项已锁定,但锁定时间已超过ExecutionTimeout 值时会调用此方法。此方法清除锁定,释放该被请求项以供其他请求使用。 |
|
采用当前请求的 HttpContext 实例、当前请求的 SessionID 值以及当前请求的锁定标识符作为输入,并删除数据存储区中与提供的 SessionID 值、当前应用程序和提供的锁定标识符相匹配的数据存储项的会话信息。此方法在 Abandon 方法被调用时调用。 |
|
采用当前请求的 HttpContext 实例、当前请求的 SessionID 值以及当前请求的锁定标识符作为输入,并向会话数据存储区添加一个 actionFlags 值为 InitializeItem 的未初始化项。 如果 regenerateExpiredSessionId 属性设置为 true,则 CreateUninitializedItem 方法用于无 Cookie 会话,这将导致遇到过期会话 ID 时,SessionStateModule 会生成一个新的 SessionID 值。 生成新的 SessionID 值的过程需要浏览器重定向到包含新生成的会话 ID 的 URL。在包含过期的会话 ID 的初始请求期间,会调用CreateUninitializedItem 方法。SessionStateModule 获取一个新的 SessionID 值来替换过期的会话 ID 之后,它会调用CreateUninitializedItem 方法以将一个未初始化项添加到会话状态数据存储区中。然后,浏览器重定向到包含新生成的SessionID 值的 URL。如果会话数据存储区中存在未初始化项,则可以确保包含新生成的 SessionID 值的重定向请求被视为新的会话,而不会被误认为是对过期会话的请求。 会话数据存储区中未初始化的项与新生成的 SessionID 值关联,并且仅包含默认值,其中包括到期日期和时间以及与 GetItem 和GetItemExclusive 方法的 actionFlags 参数相对应的值。会话状态存储区中的未初始化项应包含一个与 InitializeItem 枚举值 (1) 相等的 actionFlags 值。此值由 GetItem 和 GetItemExclusive 方法传递给 SessionStateModule,并针对SessionStateModule 指定当前会话是新会话。然后,SessionStateModule 将初始化该新会话,并引发 Session_OnStart 事件。 |
|
采用当前请求的 HttpContext 实例和当前会话的 Timeout 值作为输入,并返回带有空 ISessionStateItemCollection 对象的新的 SessionStateStoreData 对象、一个 HttpStaticObjectsCollection 集合和指定的 Timeout 值。使用GetSessionStaticObjects 方法可以检索 ASP.NET 应用程序的 HttpStaticObjectsCollection 实例。 |
|
采用引用 Global.asax 文件中定义的 Session_OnEnd 事件的委托作为输入。如果会话状态存储提供程序支持 Session_OnEnd事件,则设置对 SessionStateItemExpireCallback 参数的局部引用,并且此方法返回 true;否则,此方法返回 false。 |
由于本机只有SQLServer作为数据存储,所有就用SqlServer作为代码演示,其原理都一样的,直接改成Redis或者其他的就可以。
表结构
CREATE TABLE [dbo].[ASPStateTempSessions](
[SessionId] [nvarchar](88) NOT NULL,--唯一的SessionId
[Created] [datetime] NOT NULL,--Session的创建时间
[Expires] [datetime] NOT NULL,--过期的时间
[LockDate] [datetime] NOT NULL,--锁定的时间
[LockId] [int] NOT NULL,--锁定的Id
[Timeout] [int] NOT NULL,--过期时间(分钟)
[Locked] [bit] NOT NULL,--是否锁定
[SessionItem] [nvarchar](max) NULL,--Session数据
[Flags] [int] NOT NULL,--标记是否是初始化数据
CONSTRAINT [PK_ASPStateTempSessions] PRIMARY KEY CLUSTERED
(
[SessionId] ASC
)
)
代码实现
/// <summary>
/// 自定义Session实现方式
/// </summary>
public class MyCustomSessionStateStoreProvider : SessionStateStoreProviderBase
{
/// <summary>
/// 获取配置文件的设置的默认超时时间
/// </summary>
private static TimeSpan _expiresTime; /// <summary>
/// 获取Web.config 在sessionState设置的超时时间
/// </summary>
static MyCustomSessionStateStoreProvider()
{
System.Web.Configuration.SessionStateSection sessionStateSection = (System.Web.Configuration.SessionStateSection)System.Configuration.ConfigurationManager.GetSection("system.web/sessionState");
_expiresTime = sessionStateSection.Timeout;
} /// <summary>
/// 创建新的存储数据
/// </summary>
/// <param name="context"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
{
return new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), timeout);
} /// <summary>
/// 创建未初始化的项,就是初始化Session数据
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
/// <param name="timeout"></param>
public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
{
using (SessionStateEF db = new SessionStateEF())
{
var session = new ASPStateTempSessions
{
Created = DateTime.Now,
Expires = DateTime.Now.AddMinutes(timeout),
Flags = (int)SessionStateActions.InitializeItem,
LockDate = DateTime.Now,
Locked = false,
SessionId = id,
LockId = ,
Timeout = timeout
};
db.ASPStateTempSessions.Add(session);
db.SaveChanges();
}
} /// <summary>
/// 释放锁定的项,就是把锁定的Session的锁的状态清除掉
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
/// <param name="lockId"></param>
public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
{
using (SessionStateEF db = new SessionStateEF())
{
var session = db.ASPStateTempSessions.Find(id);
if (session == null)
{
return;
} // 把locked设置为false
session.Locked = false;
session.Expires = DateTime.Now + _expiresTime;
db.SaveChanges();
} } /// <summary>
/// 删除Session,会在Session.Abandon()的时候调用
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
/// <param name="lockId"></param>
/// <param name="item"></param>
public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
{
using (SessionStateEF db = new SessionStateEF())
{
var session = db.ASPStateTempSessions.Find(id);
if (session == null)
{
return;
} db.ASPStateTempSessions.Remove(session);
db.SaveChanges();
}
} /// <summary>
/// 设置超时时间
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
public override void ResetItemTimeout(HttpContext context, string id)
{
using (SessionStateEF db = new SessionStateEF())
{
var session = db.ASPStateTempSessions.Find(id);
if (session == null)
{
return;
}
session.Expires = DateTime.Now + _expiresTime;
db.SaveChanges();
}
} /// <summary>
/// 新建或者释放锁定的项并设置Session的值
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
/// <param name="item"></param>
/// <param name="lockId"></param>
/// <param name="newItem"></param>
public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
{
using (SessionStateEF db = new SessionStateEF())
{
// 判断是否是新建,如果是新建则和CreateUninitializedItem不同在于Timeout和有初始值。
if (newItem)
{
var session = new ASPStateTempSessions
{
Created = DateTime.Now,
Expires = DateTime.Now.AddMinutes(item.Timeout),
Flags = (int)SessionStateActions.None,
LockDate = DateTime.Now,
Locked = false,
SessionId = id,
LockId = ,
Timeout = item.Timeout,
SessionItem = Serialize((SessionStateItemCollection)item.Items)
};
db.ASPStateTempSessions.Add(session);
db.SaveChanges();
}
else// 释放锁定的项并设置Session的值
{
var session = db.ASPStateTempSessions.FirstOrDefault(i => i.SessionId == id);
if (session == null)
{
return;
} session.Expires = DateTime.Now.AddMinutes(item.Timeout);
session.Locked = false;
session.LockId = Convert.ToInt32(lockId);
session.SessionItem = Serialize((SessionStateItemCollection)item.Items);
db.SaveChanges();
}
}
} /// <summary>
/// 获取项,这个方式主要是把Session状态设置为只读状态时调用。
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
/// <param name="locked"></param>
/// <param name="lockAge"></param>
/// <param name="lockId"></param>
/// <param name="actions"></param>
/// <returns></returns>
public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
{
return DoGet(false, context, id, out locked, out lockAge, out lockId, out actions);
} /// <summary>
/// 独占获取项,除了Session状态为只读时调用
/// </summary>
/// <param name="context"></param>
/// <param name="id"></param>
/// <param name="locked"></param>
/// <param name="lockAge"></param>
/// <param name="lockId"></param>
/// <param name="actions"></param>
/// <returns></returns>
public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
{
return DoGet(true, context, id, out locked, out lockAge, out lockId, out actions);
} /// <summary>
/// 获取Session的值
/// </summary>
/// <param name="isExclusive"></param>
/// <param name="context"></param>
/// <param name="id"></param>
/// <param name="locked"></param>
/// <param name="lockAge"></param>
/// <param name="lockId"></param>
/// <param name="actions"></param>
/// <returns></returns>
public SessionStateStoreData DoGet(bool isExclusive, HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
{
using (SessionStateEF db = new SessionStateEF())
{
// 设置初始值
var item = default(SessionStateStoreData);
lockAge = TimeSpan.Zero;
lockId = null;
locked = false;
actions = ; // 如果数据存储区中未找到任何会话项数据,则GetItemExclusive 方法将 locked 输出参数设置为false,并返回 null。
// 这将导致 SessionStateModule调用 CreateNewStoreData 方法来为请求创建一个新的SessionStateStoreData 对象。
var session = db.ASPStateTempSessions.Find(id);
if (session == null)
{
return null;
} // 判断session是否是ReadOnly 模式,不是readonly模式得判断是否锁住
if (isExclusive)
{
// 如果在数据存储区中找到会话项数据但该数据已锁定,则GetItemExclusive 方法将 locked 输出参数设置为true,
// 将 lockAge 输出参数设置为当前日期和时间与该项锁定日期和时间的差,将 lockId 输出参数设置为从数据存储区中检索的锁定标识符,并返回 nul
if (session.Locked)
{
locked = true;
lockAge = session.LockDate - DateTime.Now;
lockId = session.LockId;
return null;
}
} // 判断是否过期
if (session.Expires < DateTime.Now)
{
db.ASPStateTempSessions.Remove(session);
return null;
} // 处理值
lockId = lockId == null ? : (int)lockId + ;
session.Flags = (int)SessionStateActions.None;
session.LockId = Convert.ToInt32(lockId); // 获取timeout
var timeout = actions == SessionStateActions.InitializeItem ? _expiresTime.TotalMinutes : session.Timeout; // 获取SessionStateItemCollection
SessionStateItemCollection sessionStateItemCollection = null; // 获取Session的值
if (actions == SessionStateActions.None && !string.IsNullOrEmpty(session.SessionItem))
{
sessionStateItemCollection = Deserialize((session.SessionItem));
} item = new SessionStateStoreData(sessionStateItemCollection ?? new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), (int)timeout); return item; } } #region 序列化反序列化Session的值
/// <summary>
/// 反序列化Session的数据
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public SessionStateItemCollection Deserialize(string item)
{
MemoryStream stream = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(item));
SessionStateItemCollection collection = new SessionStateItemCollection();
if (stream.Length > )
{
BinaryReader reader = new BinaryReader(stream);
collection = SessionStateItemCollection.Deserialize(reader);
}
return collection;
} /// <summary>
/// 序列化Session的数据
/// </summary>
/// <param name="items"></param>
/// <returns></returns>
public string Serialize(SessionStateItemCollection items)
{
MemoryStream ms = new MemoryStream();
BinaryWriter writer = new BinaryWriter(ms);
if (items != null)
items.Serialize(writer);
writer.Close();
return System.Text.Encoding.ASCII.GetString(ms.ToArray());
} #endregion public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
{
return true;
}
public override void InitializeRequest(HttpContext context)
{
}
public override void EndRequest(HttpContext context)
{
}
public override void Dispose()
{
} }
最后配置web.config system.web/sessionState
<sessionState mode="Custom" customProvider="mySessionProvider">
<providers>
<add name="mySessionProvider" type="CustomSessionState.MyCustomSessionStateStoreProvider,CustomSessionState"/>
</providers>
</sessionState>
Github地址
https://github.com/Emrys5/Asp.net-CustomSessionState
最后的最后,求推荐
Asp.net 实现Session分布式储存(Redis,Mongodb,Mysql等) sessionState Custom的更多相关文章
- ASP.Net Core使用分布式缓存Redis从入门到实战演练
一.课程介绍 人生苦短,我用.NET Core!缓存在很多情况下需要用到,合理利用缓存可以一方面可以提高程序的响应速度,同时可以减少对特定资源访问的压力. 所以经常要用到且不会频繁改变且被用户共享的 ...
- redis mongodb mysql 三大数据库的更简单的批量操作。批量任务自动聚合器。
1.redis mongodb mysql的python包都提供了批量插入操作,但需要自己在外部对一个例如1000 001个任务进行分解成每1000个为1个小批次,还要处理整除批次数量后的余数,如果做 ...
- DB-Engines Ranking : Redis, MongoDB, MySQL
DB-Engines Ranking http://db-engines.com/en/ranking The DB-Engines Ranking ranks database management ...
- 【数据库】Redis/MongoDB/MySQL/Oracle随笔索引
数据库体系 [思维导图]数据库体系 密码: a8ni Redis JPA
- mongodb,redis,mysql 简要对比
本篇内容大部分不是原创,转载的会贴有链接. 准备学习下数据库,想对目前的主流数据库做一个简单的了解分析,就搜集了资料整理到了一块. 当下主流的要数NoSql数据库了,拥有强大的高并发能力. mongo ...
- Session分布式共享 = Session + Redis + Nginx
一.Session 1.Session 介绍 我相信,搞Web开发的对Session一定再熟悉不过了,所以我就简单的介绍一下. Session:在计算机中,尤其是在网络应用中,称为"会话控制 ...
- ASP.NET WebApi 基于分布式Session方式实现Token签名认证
一.课程介绍 明人不说暗话,跟着阿笨一起学玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NETWebSer ...
- ASP.NET WebApi 基于分布式Session方式实现Token签名认证(发布版)
一.课程介绍 明人不说暗话,跟着阿笨一起学玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NETWebSer ...
- Session分布式共享 = Session + Redis + Nginx(转)
出处:http://www.cnblogs.com/newP/p/6518918.html 一.Session 1.Session 介绍 我相信,搞Web开发的对Session一定再熟悉不过了,所以我 ...
随机推荐
- gulp/grunt和browserify/webpack的区别
Gulp应该和Grunt比较,他们的区别我就不说了,说说用处吧.Gulp / Grunt 是一种工具,能够优化前端工作流程.比如自动刷新页面.combo.压缩css.js.编译less等等.简单来说, ...
- word-wrap: break-word; break-word: break-all;区别
word-break:break-all和word-wrap:break-word都是能使其容器如DIV的内容自动换行. 它们的区别就在于: 1,word-break:break-all 例如div宽 ...
- poj 3352Road Construction(无向双连通分量的分解)
/* 题意:给定一个连通的无向图G,至少要添加几条边,才能使其变为强连通图(指的是边强联通). 思路:利用tarjan算法找出所有的双联通分量!然后根据low[]值的不同将双联通分量 进行缩点,最后图 ...
- SQL*Loader之CASE2
CASE2 1. 控制文件 [oracle@node3 ulcase]$ cat ulcase2.ctl -- NAME -- ulcase2.ctl - SQL*Loader Case Study ...
- [OpenCV] Samples 01: drawing
基本的几何图形,标注功能. commondLineParser的使用参见:http://blog.csdn.net/u010305560/article/details/8941365 #includ ...
- 【深入BFC】 关于CSS中float布局,清除浮动,和margin合并的原理解析,解开你心中的那些困惑!
BFC的通俗理解: Block Formatting Context(块级格式化上下文)是W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用. ...
- Android抓包方法(三)之Win7笔记本Wifi热点+WireShark工具
Android抓包方法(三) 之Win7笔记本Wifi热点+WireShark工具 前言 做前端测试,基本要求会抓包,会分析请求数据包,查看接口是否调用正确,数据返回是否正确,问题产生是定位根本原因等 ...
- [Java 基础]字符串
String类 实例化String对象 String 对象初始化方式有多种. 如下代码中,各种初始化方式的效果是一样的,初始化后,String 对象的内容为 "hello" . p ...
- [C/C++]函数指针和函数分发表
// console.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> using nam ...
- hadoop的概念
hadoop的概念 网上会经常遇到各种hadoop的概念,Hive,HBase,Hdfs都各是什么呢? 首先从hdfs说起,hdfs是分布式文件系统,它把集群当作单机一样做文件操作,文件可能存在于多个 ...