本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,上一篇分享文章制做是在windows上使用的nginx,通常正式发布的时候是在linux来配置nginx,我这里测试分享内容只是起引导做用;下面将先给出整个架构的核心节点简介,但愿各位多多点赞:html
. 架构设计图展现linux
. nginx+iis构建服务集群nginx
. redis存储分布式共享的session及共享session运做流程web
. redis主从配置及Sentinel管理多个Redis集群redis
. 定时框架Task.MainForm提供数据给redis集群储存算法
以上是整个架构的我认为核心的部分,其中没有包含有数据库方面的设计(请忽略),下面来正式分享今天的文章吧(redis存储分布式共享的session及共享session运做流程):数据库
. 理解分布式的session(我的理解)windows
. 分析分布式session的流转过程跨域
. 封装session登陆验证和退出的公共方法浏览器
. Redis存储分布式session的登陆实例
下面一步一个脚印的来分享:
. 理解分布式的session(我的理解)
首先,操做session方式,这里要说的是登陆的session,理解是基于我的观点来的,而且这里的理解可能不是那么深入哈;一般咱们建设的网站或者管理系统都有用户登录,登录后会有一个存储用户基本信息的session保存在服务器,保存session的方法有多中,这里要分享的是使用redis来存储session;随着登录用户增多,存储在服务器上的session也愈来愈多,若是某台服务器上使用内存保存的sesssion那服务器的内存占有会对比的提高,最终可能直接奔溃,严重的因为长时间100%内存占用率可能致使硬盘烧坏,由此产生了多种存储方式如:使用同步session到不一样服务器方式作读写分离,数据库存储session等方式;其实咱们要用到的redis集群存储操做session的方式也主要是分摊读写;
其次,session的读写分离一般都对应站点有很大的访问量了,若是访问量如此之大那么站点的发布对应的应该也是集群的方式(名为分布式架构),分布式架构和单站点模式对比最明显的在于分布式对应的多个站点只须要使用其中某一个登录入口登录后,其余站点共享此session,无需再作登录操做,其实这里就能够看作是单点登录,只是分布式集群通常访问的都是同一个域名或同一ip段而已;而单站点模式一般就是我这里登录了,就只能我本系统能使用,另外的系统没法使用(常规来说);
最后,既然要知足共享session,那么session要么就是保存在同一个地方,读取的时候也在同一个地方读取;要么就redis集群这种方式实现即时同步到不一样服务器上或不一样端口实现数据读写分离;这样就能保证统一数据源;
. 分析分布式session的流转过程
首先,上面的内容也基本介绍了下分布式session(session数据源统一),这里要说的是分布式登陆时几个疑问:
. 系统怎么产生共享session
. 用户根据何种数据取得相同的session
. 共享session生存的周期
下面来给出对应上面问题的回答及说明:
. 产生session其实就是保存session数据,在用户使用分布式站点第一次登录时,从数据库检查此帐号运行登录,在返回登陆成功信息给用户前,会先生成一个分布式系统中惟一的一个key,这个key一般使用的规则是分布式站点Id(每一个分布式子站点对应的Id)+时间戳+用户登录惟一的帐号+加密串+Guid组合而成(可能有其余不一样的保证惟一key的方法吧),而后用md5或hash等加密,再把用户的基础信息和key一块儿保存到指定的Redis服务(姑且用redis存session,一般是键值对的关系)中,而且会返回key到用户的cookie中
. 用户要去的对应的session,就是经过cookie存储的key传递给每台分布式的站点,站点获取cookie再去指定的session读取的地方获取是否有对应的key并获取session保存的数据;只要用户有有效的cookie就能登陆分布式系统;假如我用ie浏览器登录系统后,再使用google浏览器访问系统,这样使用google浏览器时候登录不成功的,由于cookie没有跨域,可是若是您手动或者经过其余人为方式利用ie登录成功后返回的cookie的key加入到google中,那么一样登录也是没问题的,能够试试按照原理分析是没问题的
. session的生命周期你们应该都很关注,一般一个session不可能设置成无线久的生命周期,这个时候就须要按照每次用户触发验证登录的时候,自动重新设置session失效时间(通常就当前时间日后推您session定义的过时时间);因为分布式用到了cookie因此此时还须要从新更新设置下cookie的key过时时间,这样使用cookie+seesion来保存用户的登录有效性,直到用户超过了session有效期尚未触发过登录验证或者特殊方法清除了cookie,那这个时候过时的cookie或session就会验证用户须要登陆才能访问须要权限的页面
. 封装session登陆验证和退出的公共方法
首先,将要发出来的两个C#方法都进过测试了,你们能够直接拿来使用,固然此方法用到了前面分享的CacheRepository缓存工厂,由于我保存session是在redis中,下面先来贴出方法内容:
1 /// <summary> 2 /// Login扩展类 3 /// </summary> 4 public class UserLoginExtend 5 { 6 7 public static string HashSessionKey = "Hash_SessionIds"; 8 public static string CookieName = "Sid"; 9 10 public static T BaseSession<T>(HttpContextBase context) where T : class,new() 11 { 12 //获取cookie中的token 13 var cookie = context.Request.Cookies.Get(CookieName); 14 if (cookie == null) { return default(T); } 15 16 //使用toke去查询缓存工厂是否有对应的session信息,若是有自动把缓存工厂的时间日后延nAddCookieExpires分钟 17 //return CacheRepository.Current(CacheType.RedisCache).GetHashValue<T>(HashSessionKey, cookie.Value); 18 return CacheRepository.Current(CacheType.RedisCache).GetCache<T>(cookie.Value); 19 } 20 21 public static RedirectResult BaseCheckLogin<T>( 22 HttpContextBase context, 23 out T t, 24 int nAddCookieExpires = 30, 25 string loginUrl = "/User/Login") where T : class,new() 26 { 27 var returnUrl = context.Request.Path; 28 var result = new RedirectResult(string.Format("{0}?returnUrl={1}", loginUrl, returnUrl)); 29 t = default(T); 30 try 31 { 32 33 //获取cookie中的token 34 var cookie = context.Request.Cookies.Get(CookieName); 35 if (cookie == null) { return result; } 36 37 //使用toke去查询缓存工厂是否有对应的session信息,若是有自动把缓存工厂的时间日后延nAddCookieExpires分钟 38 //t = CacheRepository.Current(CacheType.RedisCache).GetHashValue<T>(HashSessionKey, cookie.Value); 39 t = CacheRepository.Current(CacheType.RedisCache).GetCache<T>(cookie.Value, true); 40 if (t == null) 41 { 42 //清空cookie 43 cookie.Expires = DateTime.Now.AddDays(-1); 44 context.Response.SetCookie(cookie); 45 return result; 46 } 47 48 //登录验证都成功后,须要从新设置cookie中的toke失效时间 49 cookie.Expires = DateTime.Now.AddMinutes(nAddCookieExpires); 50 context.Response.SetCookie(cookie); 51 52 //设置session失效时间 53 CacheRepository.Current(CacheType.RedisCache).AddExpire(cookie.Value, nAddCookieExpires); 54 } 55 catch (Exception ex) 56 { 57 return result; 58 } 59 return null; 60 } 61 62 public static RedirectResult BaseLoginOut(HttpContextBase context, string redirectUrl = "/") 63 { 64 var result = new RedirectResult(string.IsNullOrEmpty(redirectUrl) ? "/" : redirectUrl); 65 try 66 { 67 //获取cookie中的token 68 var cookie = context.Request.Cookies.Get(CookieName); 69 if (cookie == null) { return result; } 70 71 var key = cookie.Value; 72 73 //设置过时cookie(先过时cookie) 74 cookie.Expires = DateTime.Now.AddDays(-1); 75 context.Response.SetCookie(cookie); 76 77 //移除session 78 //var isRemove = CacheRepository.Current(CacheType.RedisCache).RemoveHashByKey(HashSessionKey, key); 79 var isRemove = CacheRepository.Current(CacheType.RedisCache).Remove(key); 80 } 81 catch (Exception ex) 82 { 83 84 throw new Exception(ex.Message); 85 } 86 //跳转到指定地址 87 return result; 88 } 89 }
BaseCheckLogin方法主要用来验证是否登陆,没有登陆跳转到重定向地址中;若是验证是登陆状态,会自动从新设置redis存储的session有效期,并从新设置cookie有效期;看代码的话其实就那点重要的地方都有备注说明;
BaseLoginOut方法主要用来清空用户注销后的session数据和cookie数据;这两个方法都是一般登录验证须要的内容,两方法返回的是RedirectResult,适用于.net的mvc版本;
. Redis存储分布式session的登陆实例(这里是.net mvc代码操做)
首先,看下登录的action代码:
1 [HttpPost] 2 //[ValidateAntiForgeryToken] 3 public ActionResult Login([Bind(Include = "UserName,UserPwd", Exclude = "Email")]MoUserInfo model, string returnUrl) 4 { 5 6 if (ModelState.IsValid) 7 { 8 //初始化数据库读取数据 nginx+iis+redis+Task.MainForm组建分布式架构 - (nginx+iis构建服务集群) 9 model.Email = "841202396@qq.com"; 10 model.Id = 1; 11 model.Introduce = "专一web开发二十年"; 12 model.Sex = false; 13 model.Tel = "183012787xx"; 14 model.Photo = "/Content/ace-master/assets/images/avatars/profile-pic.jpg"; 15 16 model.NickName = "神牛步行3"; 17 model.Addr = "北京-亦庄"; 18 model.Birthday = "1991-05-31"; 19 model.Blog = "http://www.cnblogs.com/wangrudong003/"; 20 21 var role = new StageModel.MoRole(); 22 role.Name = "系统管理员"; 23 role.Des = "管理整个系统"; 24 25 //根据角色Id获取对应菜单Id,这里构形成List<int>形式 26 var menus = new List<StageModel.MoMenu>{ 27 new StageModel.MoMenu{ 28 Id = 1001, 29 Link="/User/UserCenter" 30 }, 31 new StageModel.MoMenu{ 32 Id = 1002, 33 Link="/User/ChangeUser1" 34 }, 35 new StageModel.MoMenu{ 36 Id = 1003, 37 Link="" 38 }, 39 40 new StageModel.MoMenu{ 41 Id = 2001001, 42 Link="" 43 }, 44 new StageModel.MoMenu{ 45 Id = 2001002, 46 Link="" 47 } 48 }; 49 50 //赋值我的信息 51 var userData = new StageModel.MoUserData(); 52 userData.Email = model.Email; 53 userData.Id = model.Id; 54 userData.Introduce = model.Introduce; 55 userData.Sex = model.Sex; 56 userData.Tel = model.Tel; 57 userData.Photo = model.Photo; 58 59 userData.UserName = model.UserName; 60 userData.NickName = model.NickName; 61 userData.Addr = model.Addr; 62 userData.Birthday = model.Birthday; 63 userData.Blog = model.Blog; 64 65 //能访问菜单的Ids 66 userData.Menus = menus; 67 68 //获取惟一token 69 var token = CacheRepository.Current(CacheType.BaseCache).GetSessionId(userData.UserName); 70 var timeOut = 2; //分钟 71 //if (CacheRepository.Current(CacheType.RedisCache).SetHashCache<StageModel.MoUserData>("Hash_SessionIds", token, userData,2)) 72 if (CacheRepository.Current(CacheType.RedisCache).SetCache<StageModel.MoUserData>(token, userData, 2, true)) 73 { 74 var cookie = new HttpCookie(UserLoginExtend.CookieName, token); 75 cookie.Expires = DateTime.Now.AddMinutes(timeOut); 76 HttpContext.Response.AppendCookie(cookie); 77 78 return new RedirectResult(returnUrl); 79 } 80 } 81 82 return View(model); 83 }
里面用到了 var token = CacheRepository.Current(CacheType.BaseCache).GetSessionId(userData.UserName) 方法,这个方法主要是用来获取上面说的分布式惟一的key,参数只须要传递用户登录的惟一帐号就好了(底层用的是Md5hash值算法,文字结尾给出因此代码);获取key后使用 CacheRepository.Current(CacheType.RedisCache).SetCache<StageModel.MoUserData>(token, userData, 2, true) 方法来设置登录的基本信息到redis服务中,若是保存redis数据成功再经过 HttpContext.Response.AppendCookie(cookie); 吧key输出到用户的cookie中保存;
而后,登录后一般会跳转到用户后台,用户后台的一些页面须要登陆验证,我这里是使用后台几个Controller来继承同一个父级BaseController,父级里面重写Initialize方法来验证登录信息;代码以下:
1 public class BaseController : Controller 2 { 3 4 protected StageModel.MoUserData userData; 5 6 protected override void Initialize(System.Web.Routing.RequestContext requestContext) 7 { 8 9 //使用登陆扩展,验证登录,获取登录信息 10 var redirectResult = UserLoginExtend.BaseCheckLogin(requestContext.HttpContext, out userData,2); 11 //验证失败,跳转到loginUrl 12 if (redirectResult != null) 13 { 14 requestContext.HttpContext.Response.Redirect(redirectResult.Url, true); 15 return; 16 } 17 18 //验证成功,添加视图访问登录信息数据 19 ViewBag.UserData = userData; 20 base.Initialize(requestContext); 21 } 22 }
BaseCheckLogin方法就是咱们上面分享的公共验证登录的方法,具体参数能够看下参数描述说明;代码写好后,来看下运行的页面效果(我这里使用的是前一章你们的nginx集群来演示):
红色框里面的就是咋们本身生产的Sid也就是上面说的key,接着咋们在打开一个浏览器tab,来看下系统02的Sid,如图:
经过上图能够看到系统01和系统02,对应的sid都是同样的值,每次这样分布式站点的session使用和制做就成功了,好那咋们经过redis-cli.exe客户端看下咱们登录后保存在redis服务中的数据图如:
看到的redis里的key和咱们浏览器截图中的key是同样的,因此本章要将的内容大体就要结束了,若是以为文章让您有所收获,请多多点"赞",谢谢。