上篇文章( http://blog.csdn.net/u010533180/article/details/52805792)中使用asp.net 自带的服务存储sesion 来解决sesion一致性的问题。那么这篇文章使用redis 来解决session一致性的问题。
1. 首先下载windows版本的redis。
Windows下Redis项目: https://github.com/MSOpenTech/redis
源代码: Redis官网下载页面: http://redis.io/download
我这里下载的是 Redis-x64-3.2.100.zip 这个版本的redis.
解压后找到放到一台机器上,我这里放到了192.168.164.133 这台服务器上面。我这里放到了C里面,如下图所示。
其中redis-server.exe 是运行到服务器上面。
redis-cli.exe是客户端,用来连接服务器的。
其余的可以不进行考虑。
2.修改redis.windows.conf文件
指定访问密码
requirepass foobared
requirepass redistest
设置最大堆内存限制,两者设置一个即可
maxheap
maxheap 512000000
设置最大内存限制, 两者设置一个即可
maxmemory
maxmemory 512000000
修改配置文件redis.windows.conf,如果有中文,请另存为UTF-8编码.
手工运行redis-server.exe ,出现类似如下的界面,表示启动成功,而且这个界面不能进行关闭,如果注册为服务,则可以进行关闭。
强烈建议把redis-server.exe注册为服务启动,这样启动机器时,把这个服务也启动,不用每次收到启动redis服务器了。注册命令如下,:
注册服务,可以保存为 service-install.bat 文件,运行这个文件:
redis-server.exe --service-install redis.windows.conf --loglevel verbose
redis-server --service-start
对应的卸载命令如下:
卸载服务, 可以保存为 uninstall-service.bat
redis-server –service-stop
redis-server –service-uninstall
上面的bat文件存放在redis-server.exe同一目录下面,如下图:
在服务列表中查看redis服务。其中启动类型为自动。如下图
服务注册成功后,redis-server.exe文件夹下面可能会有一个redis.windows-server.confi文件,如果需要修改redis服务器的配置,则修改这个文件即可。不需要修改redis.windows.config文件。
如果需要自行定义config文件或者需要知道config文件里面节点的含义,请读者自行查阅。
redis服务器所在的ip为192.168.164.133 端口号为6379. 我在192.168.164.130服务器上运行redis-cli.exe测试连接,需要指定IP 端口 和密码。 如下图:
这里建议写成bat文件,下次直接运行 便连接上redis服务器,不用再进行切换命令。bat文件代码如下:
start c:\redis\redis-cli.exe -h 192.168.164.133 -p 6379 -a redistest
3.接着修改项目中的web.config文件,使session存储在自定义的redis中。
.
文件中的关键代码下:
<httpRuntime/>
<!--<sessionState mode="StateServer" stateConnectionString="tcpip=192.168.164.129:42424" timeout="60" stateNetworkTimeout="20"/>-->
<sessionState timeout="60" mode="Custom" customProvider="RedisSessionStateProvider" cookieless="false">
<providers>
<add name="RedisSessionStateProvider" writeexceptionstoeventlog="false" type="RedisProvider.SessionProvider.CustomServiceProvider"
server="192.168.164.133" port="6379" password="redistest"/>
</providers>
其中redis 部署在192.168.164.133 服务器上面,端口号是默认的端口号 6379 密码设置的为redistest.
4.在测试的项目中添加RedisProvider.SessionProvider类库, 因为这个是自定义session里面存储的那个节点名义。如上面的sesionstate里面的节点里面的节点里面的type属性的值。
在这个类库里面添加CustomServiceProvider类,自定义Session保存的位置必须实现System.Web.SessionState.SessionStateStoreProviderBase这个基类,并重新里面的方法,代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections.Specialized;
using System.Web.SessionState;
using ServiceStack.Redis;
using System.Configuration;
using System.Configuration.Provider;
using System.Web.Configuration;
using System.IO;
using System.Xml;
namespace RedisProvider.SessionProvider
{
#region Session Item Model
[Serializable]
public class SessionItem
{
#region Properties
public DateTime CreatedAt { get; set; }
public DateTime LockDate { get; set; }
public int LockID { get; set; }
public int Timeout { get; set; }
public bool Locked { get; set; }
public string SessionItems { get; set; }
public int Flags { get; set; }
#endregion Properties
}
#endregion Session Item Model
public class CustomServiceProvider : System.Web.SessionState.SessionStateStoreProviderBase, IDisposable
{
/// <summary>
/// 相关Redis的配置
/// </summary>
private CustomReaderRedisWebConfig redisCfg = CustomReaderRedisWebConfig.GetCustomerReaderRedisWebConfig;
#region Properties
private string ApplicationName
{
get
{
if (ConfigurationManager.AppSettings.AllKeys.Contains("Application.Name"))
{
return ConfigurationManager.AppSettings["Application.Name"];
}
return string.Empty;
}
}
private RedisClient RedisSessionClient
{
get
{
if (!string.IsNullOrEmpty(redisCfg.RedisPassword))
{
return new RedisClient(redisCfg.RedisServer, redisCfg.RedisPort, redisCfg.RedisPassword);
}
return new RedisClient(redisCfg.RedisServer, redisCfg.RedisPort);
}
}
#endregion Properties
#region Private Methods
/// <summary>
/// Prepends the application name to the redis key if one exists. Querying by application name is recommended for session
/// </summary>
/// <param name="id">The session id</param>
/// <returns>Concatenated string applicationname:sessionkey</returns>
private string RedisKey(string id)
{
return string.Format("{0}{1}", !string.IsNullOrEmpty(this.ApplicationName) ? this.ApplicationName + ":" : "", id);
}
#endregion Private Methods
#region Constructor
public CustomServiceProvider()
{
}
#endregion Constructor
#region Overrides
public override void Dispose()
{
}
public override void Initialize(string name, NameValueCollection config)
{
// Initialize values from web.config.
if (config == null)
{
throw new ArgumentNullException("config");
}
if (name == null || name.Length == 0)
{
name = "RedisSessionStateStore";
}
if (String.IsNullOrEmpty(config["description"]))
{
config.Remove("description");
config.Add("description", "Redis Session State Provider");
}
// Initialize the abstract base class.
base.Initialize(name, config);
}
public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
{
return true;
}
public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
{
using (RedisClient client = this.RedisSessionClient)
{
// Serialize the SessionStateItemCollection as a string.
string sessionItems = Serialize((SessionStateItemCollection)item.Items);
try
{
if (newItem)
{
SessionItem sessionItem = new SessionItem();
sessionItem.CreatedAt = DateTime.UtcNow;
sessionItem.LockDate = DateTime.UtcNow;
sessionItem.LockID = 0;
sessionItem.Timeout = item.Timeout;
sessionItem.Locked = false;
sessionItem.SessionItems = sessionItems;
sessionItem.Flags = 0;
client.Set<SessionItem>(this.RedisKey(id), sessionItem, DateTime.UtcNow.AddMinutes(item.Timeout));
}
else
{
SessionItem currentSessionItem = client.Get<SessionItem>(this.RedisKey(id));
if (currentSessionItem != null && currentSessionItem.LockID == (int?)lockId)
{
currentSessionItem.Locked = false;
currentSessionItem.SessionItems = sessionItems;
client.Set<SessionItem>(this.RedisKey(id), currentSessionItem, DateTime.UtcNow.AddMinutes(item.Timeout));
}
}
}
catch (Exception e)
{
throw e;
}
}
}
public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
{
return GetSessionStoreItem(true, context, id, out locked, out lockAge, out lockId, out actions);
}
public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags)
{
return GetSessionStoreItem(false, context, id, out locked, out lockAge, out lockId, out actionFlags);
}
private SessionStateStoreData GetSessionStoreItem(bool lockRecord,
HttpContext context,
string id,
out bool locked,
out TimeSpan lockAge,
out object lockId,
out SessionStateActions actionFlags)
{
// Initial values for return value and out parameters.
SessionStateStoreData item = null;
lockAge = TimeSpan.Zero;
lockId = null;
locked = false;
actionFlags = 0;
// String to hold serialized SessionStateItemCollection.
string serializedItems = "";
// Timeout value from the data store.
int timeout = 0;
using (RedisClient client = this.RedisSessionClient)
{
try
{
if (lockRecord)
{
locked = false;
SessionItem currentItem = client.Get<SessionItem>(this.RedisKey(id));
if (currentItem != null)
{
// If the item is locked then do not attempt to update it
if (!currentItem.Locked)
{
currentItem.Locked = true;
currentItem.LockDate = DateTime.UtcNow;
client.Set<SessionItem>(this.RedisKey(id), currentItem, DateTime.UtcNow.AddMinutes(redisCfg.SessionTimeOut));
}
else
{
locked = true;
}
}
}
SessionItem currentSessionItem = client.Get<SessionItem>(this.RedisKey(id));
if (currentSessionItem != null)
{
serializedItems = currentSessionItem.SessionItems;
lockId = currentSessionItem.LockID;
lockAge = DateTime.UtcNow.Subtract(currentSessionItem.LockDate);
actionFlags = (SessionStateActions)currentSessionItem.Flags;
timeout = currentSessionItem.Timeout;
}
else
{
locked = false;
}
if (currentSessionItem != null && !locked)
{
// Delete the old item before inserting the new one
client.Remove(this.RedisKey(id));
lockId = (int?)lockId + 1;
currentSessionItem.LockID = lockId != null ? (int)lockId : 0;
currentSessionItem.Flags = 0;
client.Set<SessionItem>(this.RedisKey(id), currentSessionItem, DateTime.UtcNow.AddMinutes(redisCfg.SessionTimeOut));
// If the actionFlags parameter is not InitializeItem,
// deserialize the stored SessionStateItemCollection.
if (actionFlags == SessionStateActions.InitializeItem)
{
item = CreateNewStoreData(context, 30);
}
else
{
item = Deserialize(context, serializedItems, timeout);
}
}
}
catch (Exception e)
{
throw e;
}
}
return item;
}
public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
{
using (RedisClient client = this.RedisSessionClient)
{
SessionItem currentSessionItem = client.Get<SessionItem>(this.RedisKey(id));
if (currentSessionItem != null && (int?)lockId == currentSessionItem.LockID)
{
currentSessionItem.Locked = false;
client.Set<SessionItem>(this.RedisKey(id), currentSessionItem, DateTime.UtcNow.AddMinutes(redisCfg.SessionTimeOut));
}
}
}
public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
{
using (RedisClient client = this.RedisSessionClient)
{
// Delete the old item before inserting the new one
client.Remove(this.RedisKey(id));
}
}
public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
{
using (RedisClient client = this.RedisSessionClient)
{
SessionItem sessionItem = new SessionItem();
sessionItem.CreatedAt = DateTime.Now.ToUniversalTime();
sessionItem.LockDate = DateTime.Now.ToUniversalTime();
sessionItem.LockID = 0;
sessionItem.Timeout = timeout;
sessionItem.Locked = false;
sessionItem.SessionItems = string.Empty;
sessionItem.Flags = 0;
client.Set<SessionItem>(this.RedisKey(id), sessionItem, DateTime.UtcNow.AddMinutes(timeout));
}
}
public override SessionStateStoreData CreateNewStoreData(System.Web.HttpContext context, int timeout)
{
return new SessionStateStoreData(new SessionStateItemCollection(),
SessionStateUtility.GetSessionStaticObjects(context),
timeout);
}
public override void ResetItemTimeout(HttpContext context, string id)
{
using (RedisClient client = this.RedisSessionClient)
{
try
{
// TODO :: GET THIS VALUE FROM THE CONFIG
client.ExpireEntryAt(id, DateTime.UtcNow.AddMinutes(redisCfg.SessionTimeOut));
}
catch (Exception e)
{
throw e;
}
}
}
public override void InitializeRequest(HttpContext context)
{
// Was going to open the redis connection here but sometimes I had 5 connections open at one time which was strange
}
public override void EndRequest(HttpContext context)
{
this.Dispose();
}
#endregion Overrides
#region Serialization
/// <summary>
/// Serialize is called by the SetAndReleaseItemExclusive method to
/// convert the SessionStateItemCollection into a Base64 string to
/// be stored in MongoDB.
/// </summary>
private string Serialize(SessionStateItemCollection items)
{
using (MemoryStream ms = new MemoryStream())
using (BinaryWriter writer = new BinaryWriter(ms))
{
if (items != null)
items.Serialize(writer);
writer.Close();
return Convert.ToBase64String(ms.ToArray());
}
}
private SessionStateStoreData Deserialize(HttpContext context, string serializedItems, int timeout)
{
using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(serializedItems)))
{
SessionStateItemCollection sessionItems = new SessionStateItemCollection();
if (ms.Length > 0)
{
using (BinaryReader reader = new BinaryReader(ms))
{
sessionItems = SessionStateItemCollection.Deserialize(reader);
}
}
return new SessionStateStoreData(sessionItems,
SessionStateUtility.GetSessionStaticObjects(context),
timeout);
}
}
#endregion Serialization
}
}
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Configuration;
using System.Xml;
namespace RedisProvider.SessionProvider
{
public class CustomReaderRedisWebConfig
{
private static CustomReaderRedisWebConfig customerReaderWebConfig = null;
// 定义一个标识确保线程同步
private static readonly object locker = new object();
static ConfigXmlDocument doc = new ConfigXmlDocument();
public static CustomReaderRedisWebConfig GetCustomerReaderRedisWebConfig
{
get
{
if (customerReaderWebConfig == null)
{
lock (locker)
{
customerReaderWebConfig = new CustomReaderRedisWebConfig();
doc.Load(HttpContext.Current.Server.MapPath("web.config"));
}
}
return customerReaderWebConfig;
}
}
/// <summary>
/// Sessio过期时间,默认是20分钟
/// </summary>
public int SessionTimeOut
{
get
{
int sessionTimeOut = 20;
XmlNode node = doc.SelectSingleNode("configuration/system.web / sessionState");
if (node == null || node.Attributes["timeout"] == null)
return sessionTimeOut;
if (!int.TryParse(node.Attributes["timeout"].Value, out sessionTimeOut))
{
sessionTimeOut = 20;
}
return sessionTimeOut;
}
}
public bool CookieLess
{
get
{
XmlNode node = doc.SelectSingleNode("configuration/system.web / sessionState");
if (node == null || node.Attributes["cookieless"] == null)
return false;
return node.Attributes["cookieless"].Value.ToUpper().Equals("TRUE");
}
}
/// <summary>
/// Redis连接的名称
/// </summary>
public string CustomProvider
{
get
{
XmlNode node = doc.SelectSingleNode("configuration/system.web / sessionState");
if (node == null || node.Attributes["customProvider"] == null)
{
throw new ConfigurationErrorsException("customProvider node not found in sessionstate ");
}
return node.Attributes["customProvider"].Value;
}
}
/// <summary>
/// 是否向Redis服务器书写错误日志
/// </summary>
public bool WriteExceptionsToEventLog
{
get
{
XmlNode node = doc.SelectSingleNode("configuration/system.web/ sessionState/providers/add[@name='" + CustomProvider + "']");
if (node == null || node.Attributes["writeExceptionsToEventLog"] == null)
{
return false;
}
return node.Attributes["writeExceptionsToEventLog"].Value.ToUpper().Equals("TRUE");
}
}
/// <summary>
/// 获取Redis服务器地址
/// </summary>
public string RedisServer
{
get
{
XmlNode node = doc.SelectSingleNode("configuration/system.web/ sessionState/providers/add[@name='" + CustomProvider + "']");
if (node == null || node.Attributes["server"] == null)
{
return "localhost";
}
return node.Attributes["server"].Value;
}
}
/// <summary>
/// Redis端口号
/// </summary>
public int RedisPort
{
get
{
int port = 6379;
XmlNode node = doc.SelectSingleNode("configuration/system.web/ sessionState/providers/add[@name='" + CustomProvider + "']");
if (node == null || node.Attributes["port"] == null)
{
return port;
}
if (!int.TryParse(node.Attributes["port"].Value, out port))
{
port = 6379;
}
return port;
}
}
/// <summary>
/// Redis端口号
/// </summary>
public string RedisPassword
{
get
{
XmlNode node = doc.SelectSingleNode("configuration/system.web/ sessionState/providers/add[@name='" + CustomProvider + "']");
if (node == null || node.Attributes["password"] == null)
{
return "";
}
return node.Attributes["password"].Value;
}
}
private CustomReaderRedisWebConfig()
{
}
}
}
把这个类库引用到测试的web项目中。其中using ServiceStack.Redis; 这个dll需要进行下载,我这里使用vs自带的下载。按照下面的操作进行下载即可。(要求项目的Framework 为4.5以上,因为它们官方提供的是4.5,以前的版本我没有找到)。
在搜索框中输入redis,如下图:
在你的项目中根文件下下面会有packages的文件里面有如下下载的文件:
找到对应的dll引入即可。
其中用到的default.aspx页面代码如下:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using RedisProvider.SessionProvider;
namespace NginxWebProject
{
public partial class _default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
int port = Request.Url.Port;
if (port == 8081)
{
Response.Write("第一个页面<br/>");
}
else if (port == 8082)
{
Response.Write("第二个页面<br/>");
}
else
{
Response.Write(port.ToString() + "<br/>");
}
Response.Write("请求开始时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "<br/>");
Response.Write("服务器名称:" + Server.MachineName + "<br/>"); //服务器名称
Response.Write("服务器IP地址:" + Request.ServerVariables["LOCAL_ADDR"] + "<br/>"); //服务器IP地址
Response.Write("HTTP访问端口:" + Request.ServerVariables["SERVER_PORT"]);//HTTP访问端口"
Response.Write(".NET解释引擎版本:" + ".NET CLR" + Environment.Version.Major + "." + Environment.Version.Minor + "quot;." + Environment.Version.Build + "." + Environment.Version.Revision + "<br/>"); //.NET解释引擎版本
Response.Write("服务器操作系统版本:" + Environment.OSVersion.ToString() + "<br/>");//服务器操作系统版本
Response.Write("服务器IIS版本:" + Request.ServerVariables["SERVER_SOFTWARE"] + "<br/>");//服务器IIS版本
Response.Write("服务器域名:" + Request.ServerVariables["SERVER_NAME"] + "<br/>");//服务器域名
Response.Write("虚拟目录的绝对路径:" + Request.ServerVariables["APPL_RHYSICAL_PATH"] + "<br/>");//虚拟目录的绝对路径
Response.Write("执行文件的绝对路径:" + Request.ServerVariables["PATH_TRANSLATED"] + "<br/>");//执行文件的绝对路径
Response.Write("虚拟目录Session总数:" + Session.Contents.Count.ToString() + "<br/>"); //虚拟目录Session总数
Response.Write("虚拟目录Application总数:" + Application.Contents.Count.ToString() + "<br/>");//虚拟目录Application总数
Response.Write("域名主机:" + Request.ServerVariables["HTTP_HOST"] + "<br/>");//域名主机
Response.Write("服务器区域语言:" + Request.ServerVariables["HTTP_ACCEPT_LANGUAGE"] + "<br/>");//服务器区域语言
Response.Write("用户信息:" + Request.ServerVariables["HTTP_USER_AGENT"] + "<br/>");
Response.Write("CPU个数:" + Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS") + "<br/>");//CPU个数
Response.Write("CPU类型:" + Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER") + "<br/>");//CPU类型
Response.Write("请求来源地址:" + Request.Headers["X-Real-IP"] + "<br/>");
if (Session["port"] == null)
{
Response.Write("Session为null<br/>");
SessionItem si = new SessionItem();
si.CreatedAt = DateTime.Now;
si.SessionItems = port.ToString();
//LoginSession loginSession = new LoginSession();
//loginSession.LoginPort = port;
//loginSession.LoginTime = DateTime.Now;
Session["port"] = si;
}
else
{
CustomServiceProvider custom = new CustomServiceProvider();
SessionItem loginSession = Session["port"] as SessionItem;
if (loginSession != null)
{
Response.Write("port:" + loginSession.SessionItems + ";createTime:" + loginSession.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss") + "</br>");
if (loginSession.Locked)
{
Response.Write("lockID:" + loginSession.LockID + ";" + loginSession.LockDate.ToString("yyyy-MM-dd HH:mm:ss") + "</br>");
}
Response.Write("flags:" + loginSession.Flags + ";timeout" + loginSession.Timeout + "</br>");
Response.Write("SessionId:" +Session.SessionID+ "</br>");
custom.RemoveItem(this.Context, Session.SessionID, null, null);
}
else
{
Response.Write("不知道何种原因从Session里面取到的LoginSession 为null");
}
}
}
}
}
最终的运行界面如下:
至于nginx的搭建,详见:http://blog.csdn.net/u010533180/article/details/52784696
如果想要删除Session 则调用如下的方法:
CustomServiceProvider custom = new CustomServiceProvider();
custom.RemoveItem(this.Context, Session.SessionID, null, null);
删除Session之后的界面如下:
本次测试的代码如下:
链接: http://pan.baidu.com/s/1pKR3VrP 密码: 3anx
参考文献:
http://www.codeceo.com/article/nginx-iis-load-balance.html
http://www.cnblogs.com/yanweidie/archive/2015/08/17/4678095.html
https://github.com/MSOpenTech/redis/releases
http://huangliangbao.iteye.com/blog/2217717
http://www.cnblogs.com/Drama/p/4159364.html
http://blog.sina.com.cn/s/blog_60fb0b3f0100szkd.html
备注:以上项目使用的是redis 4.0以上的版本,这个版本有个问题就是商业化后,每小时6000次的访问量。如下图的错误:
解决方案在一篇文中进行详细解决。