nginx+iis+redis+Task.MainForm构建分布式架构 之 (redis存储分布式共享的session及共享session运作流程)
本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,上一篇分享文章制作是在windows上使用的nginx,一般正式发布的时候是在linux来配置nginx,我这里测试分享内容只是起引导作用;下面将先给出整个架构的核心节点简介,希望各位多多点赞:
. 架构设计图展示
. redis存储分布式共享的session及共享session运作流程
. redis主从配置及Sentinel管理多个Redis集群
. 定时框架Task.MainForm提供数据给redis集群储存
以上是整个架构的我认为核心的部分,其中没有包含有数据库方面的设计(请忽略),下面来正式分享今天的文章吧(redis存储分布式共享的session及共享session运作流程):
. 理解分布式的session(个人理解)
. 分析分布式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中,下面先来贴出方法内容:
- /// <summary>
- /// Login扩展类
- /// </summary>
- public class UserLoginExtend
- {
- public static string HashSessionKey = "Hash_SessionIds";
- public static string CookieName = "Sid";
- public static T BaseSession<T>(HttpContextBase context) where T : class,new()
- {
- //获取cookie中的token
- var cookie = context.Request.Cookies.Get(CookieName);
- if (cookie == null) { return default(T); }
- //使用toke去查询缓存工厂是否有对应的session信息,如果有自动把缓存工厂的时间往后延nAddCookieExpires分钟
- //return CacheRepository.Current(CacheType.RedisCache).GetHashValue<T>(HashSessionKey, cookie.Value);
- return CacheRepository.Current(CacheType.RedisCache).GetCache<T>(cookie.Value);
- }
- public static RedirectResult BaseCheckLogin<T>(
- HttpContextBase context,
- out T t,
- int nAddCookieExpires = ,
- string loginUrl = "/User/Login") where T : class,new()
- {
- var returnUrl = context.Request.Path;
- var result = new RedirectResult(string.Format("{0}?returnUrl={1}", loginUrl, returnUrl));
- t = default(T);
- try
- {
- //获取cookie中的token
- var cookie = context.Request.Cookies.Get(CookieName);
- if (cookie == null) { return result; }
- //使用toke去查询缓存工厂是否有对应的session信息,如果有自动把缓存工厂的时间往后延nAddCookieExpires分钟
- //t = CacheRepository.Current(CacheType.RedisCache).GetHashValue<T>(HashSessionKey, cookie.Value);
- t = CacheRepository.Current(CacheType.RedisCache).GetCache<T>(cookie.Value, true);
- if (t == null)
- {
- //清空cookie
- cookie.Expires = DateTime.Now.AddDays(-);
- context.Response.SetCookie(cookie);
- return result;
- }
- //登陆验证都成功后,需要重新设置cookie中的toke失效时间
- cookie.Expires = DateTime.Now.AddMinutes(nAddCookieExpires);
- context.Response.SetCookie(cookie);
- //设置session失效时间
- CacheRepository.Current(CacheType.RedisCache).AddExpire(cookie.Value, nAddCookieExpires);
- }
- catch (Exception ex)
- {
- return result;
- }
- return null;
- }
- public static RedirectResult BaseLoginOut(HttpContextBase context, string redirectUrl = "/")
- {
- var result = new RedirectResult(string.IsNullOrEmpty(redirectUrl) ? "/" : redirectUrl);
- try
- {
- //获取cookie中的token
- var cookie = context.Request.Cookies.Get(CookieName);
- if (cookie == null) { return result; }
- var key = cookie.Value;
- //设置过期cookie(先过期cookie)
- cookie.Expires = DateTime.Now.AddDays(-);
- context.Response.SetCookie(cookie);
- //移除session
- //var isRemove = CacheRepository.Current(CacheType.RedisCache).RemoveHashByKey(HashSessionKey, key);
- var isRemove = CacheRepository.Current(CacheType.RedisCache).Remove(key);
- }
- catch (Exception ex)
- {
- throw new Exception(ex.Message);
- }
- //跳转到指定地址
- return result;
- }
- }
BaseCheckLogin方法主要用来验证是否登录,没有登录跳转到重定向地址中;如果验证是登录状态,会自动重新设置redis存储的session有效期,并重新设置cookie有效期;看代码的话其实就那点重要的地方都有备注说明;
BaseLoginOut方法主要用来清空用户注销后的session数据和cookie数据;这两个方法都是通常登陆验证需要的内容,两方法返回的是RedirectResult,适用于.net的mvc版本;
. Redis存储分布式session的登录实例(这里是.net mvc代码操作)
首先,看下登陆的action代码:
- [HttpPost]
- //[ValidateAntiForgeryToken]
- public ActionResult Login([Bind(Include = "UserName,UserPwd", Exclude = "Email")]MoUserInfo model, string returnUrl)
- {
- if (ModelState.IsValid)
- {
- //初始化数据库读取数据 nginx+iis+redis+Task.MainForm组建分布式架构 - (nginx+iis构建服务集群)
- model.Email = "841202396@qq.com";
- model.Id = ;
- model.Introduce = "专注web开发二十年";
- model.Sex = false;
- model.Tel = "183012787xx";
- model.Photo = "/Content/ace-master/assets/images/avatars/profile-pic.jpg";
- model.NickName = "神牛步行3";
- model.Addr = "北京-亦庄";
- model.Birthday = "1991-05-31";
- model.Blog = "http://www.cnblogs.com/wangrudong003/";
- var role = new StageModel.MoRole();
- role.Name = "系统管理员";
- role.Des = "管理整个系统";
- //根据角色Id获取对应菜单Id,这里构造成List<int>形式
- var menus = new List<StageModel.MoMenu>{
- new StageModel.MoMenu{
- Id = ,
- Link="/User/UserCenter"
- },
- new StageModel.MoMenu{
- Id = ,
- Link="/User/ChangeUser1"
- },
- new StageModel.MoMenu{
- Id = ,
- Link=""
- },
- new StageModel.MoMenu{
- Id = ,
- Link=""
- },
- new StageModel.MoMenu{
- Id = ,
- Link=""
- }
- };
- //赋值个人信息
- var userData = new StageModel.MoUserData();
- userData.Email = model.Email;
- userData.Id = model.Id;
- userData.Introduce = model.Introduce;
- userData.Sex = model.Sex;
- userData.Tel = model.Tel;
- userData.Photo = model.Photo;
- userData.UserName = model.UserName;
- userData.NickName = model.NickName;
- userData.Addr = model.Addr;
- userData.Birthday = model.Birthday;
- userData.Blog = model.Blog;
- //能访问菜单的Ids
- userData.Menus = menus;
- //获取唯一token
- var token = CacheRepository.Current(CacheType.BaseCache).GetSessionId(userData.UserName);
- var timeOut = ; //分钟
- //if (CacheRepository.Current(CacheType.RedisCache).SetHashCache<StageModel.MoUserData>("Hash_SessionIds", token, userData,2))
- if (CacheRepository.Current(CacheType.RedisCache).SetCache<StageModel.MoUserData>(token, userData, , true))
- {
- var cookie = new HttpCookie(UserLoginExtend.CookieName, token);
- cookie.Expires = DateTime.Now.AddMinutes(timeOut);
- HttpContext.Response.AppendCookie(cookie);
- return new RedirectResult(returnUrl);
- }
- }
- return View(model);
- }
里面用到了 var token = CacheRepository.Current(CacheType.BaseCache).GetSessionId(userData.UserName) 方法,这个方法主要是用来获取上面说的分布式唯一的key,参数只需要传递用户登陆的唯一账号就行了(底层用的是Md5hash值算法,文字结尾给出所以代码);获取key后使用 CacheRepository.Current(CacheType.RedisCache).SetCache<StageModel.MoUserData>(token, userData, , true) 方法来设置登陆的基本信息到redis服务中,如果保存redis数据成功再通过 HttpContext.Response.AppendCookie(cookie); 吧key输出到用户的cookie中保存;
然后,登陆后通常会跳转到用户后台,用户后台的一些页面需要登录验证,我这里是使用后台几个Controller来继承同一个父级BaseController,父级里面重写Initialize方法来验证登陆信息;代码如下:
- public class BaseController : Controller
- {
- protected StageModel.MoUserData userData;
- protected override void Initialize(System.Web.Routing.RequestContext requestContext)
- {
- //使用登录扩展,验证登陆,获取登陆信息
- var redirectResult = UserLoginExtend.BaseCheckLogin(requestContext.HttpContext, out userData,);
- //验证失败,跳转到loginUrl
- if (redirectResult != null)
- {
- requestContext.HttpContext.Response.Redirect(redirectResult.Url, true);
- return;
- }
- //验证成功,添加视图访问登陆信息数据
- ViewBag.UserData = userData;
- base.Initialize(requestContext);
- }
- }
BaseCheckLogin方法就是我们上面分享的公共验证登陆的方法,具体参数可以看下参数描述说明;代码写好后,来看下运行的页面效果(我这里使用的是前一章大家的nginx集群来演示):
红色框里面的就是咋们自己生产的Sid也就是上面说的key,接着咋们在打开一个浏览器tab,来看下系统02的Sid,如图:
通过上图可以看到系统01和系统02,对应的sid都是一样的值,每次这样分布式站点的session使用和制作就成功了,好那咋们通过redis-cli.exe客户端看下我们登陆后保存在redis服务中的数据图如:
看到的redis里的key和我们浏览器截图中的key是一样的,所以本章要将的内容大致就要结束了,如果觉得文章让您有所收获,请多多点"赞",谢谢。
nginx+iis+redis+Task.MainForm构建分布式架构 之 (redis存储分布式共享的session及共享session运作流程)的更多相关文章
- windows+nginx+iis+redis+Task.MainForm构建分布式架构 之 (nginx+iis构建服务集群)
本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,由标题就能看出此内容不是一篇分享文章能说完的,所以我打算分几篇分享文章来讲解,一步一步实现分 ...
- Zookeeper系列二:分布式架构详解、分布式技术详解、分布式事务
一.分布式架构详解 1.分布式发展历程 1.1 单点集中式 特点:App.DB.FileServer都部署在一台机器上.并且访问请求量较少 1.2 应用服务和数据服务拆分 特点:App.DB.Fi ...
- 基于SpringCloud分布式架构
基于SpringCloud分布式架构 为什么要使用分布式架构 Spring Cloud 专注于提供良好的开箱即用经验的典型用例和可扩展性机制覆盖 分布式/版本化配置 服务注册和发现 路由 Servic ...
- Elasticsearch由浅入深(二)ES基础分布式架构、横向扩容、容错机制
Elasticsearch的基础分布式架构 Elasticsearch对复杂分布式机制的透明隐藏特性 Elasticsearch是一套分布式系统,分布式是为了应对大数据量. Elasticsearch ...
- 深入理解java:5. Java分布式架构
什么是分布式架构 分布式系统(distributed system)是建立在网络之上的软件系统. 内聚性是指每一个数据库分布节点高度自治,有本地的数据库管理系统. 透明性是指每一个数据库分布节点对用户 ...
- Nginx+IIS+Redis 处理Session共享问题 1
最近遇到一个棘手的问题,微信公众平台的前端站点session老是丢失,我们是走的微信网页授权,授权后获取用户openid,丢失后没有openid后续的操作全白搭了,因为没了openid只能判断为客户不 ...
- 分布式架构真正适用于大型互联网项目的架构! dubbo+zookeeper+springmvc+mybatis+shiro+redis
分类: 分布式技术(3) 目录(?)[+] 平台简介 Jeesz是一个分布式的框架,提供项目模块化.服务化.热插拔的思想,高度封装安全性的Java EE快速开发平台. Jeesz本身集成D ...
- j2ee分布式架构 dubbo + springmvc + mybatis + ehcache + redis 分布式架构
介绍 <modules> <!-- jeesz 工具jar --> <module>jeesz-utils</module> ...
- .Net分布式架构(一):Nginx实现负载均衡
一:负载均衡简介 负载均衡,英文名称为Load Balance,其意思就是分摊到多个操作单元上进行执行,例如Web服务器.FTP服务器.企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务. ...
随机推荐
- 多线程的通信和同步(Java并发编程的艺术--笔记)
1. 线程间的通信机制 线程之间通信机制有两种: 共享内存.消息传递. 2. Java并发 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式执行,通信的过程对于程序员来说是完全透 ...
- PHP之GD函数的使用
本文讲解常用GD函数的应用 1.一个简单的图像 我们先看一个例子: <?php $w = 200; $h = 200; $img = imagecreatetruecolor($w,$h); $ ...
- Java 经典入门(一)
一.什么是 Java 技术?为何需要 Java? Java 是由 Sun Microsystems 在 1995 年首先发布的编程语言和计算平台.有许多应用程序和 Web 站点只有在安装 Java 后 ...
- MySQL 系列(二) 你不知道的数据库操作
第一篇:MySQL 系列(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:MySQL 系列(二) 你不知道的数据库操作 本章内容: 查看\创建\使用\删除 数据库 用户管理及授权实战 局域网 ...
- VS2015在创建项目时的一些注意事项
一.下面是在创建一个新的项目是我最常用的,现在对他们一一做一个详细的介绍: 1.Win32控制台应用程序我平时编写小的C/C++程序都用它,它应该是用的最多的. 2.名称和解决方案名称的区别:名称是项 ...
- AEAI DP V3.7.0 发布,开源综合应用开发平台
1 升级说明 AEAI DP 3.7版本是AEAI DP一个里程碑版本,基于JDK1.7开发,在本版本中新增支持Rest服务开发机制(默认支持WebService服务开发机制),且支持WS服务.RS ...
- Android中开发工具Android Studio修改created用户(windows环境)
最近经常有朋友反馈说我的安卓项目中,在一些类中会出现Created by panchengjia on 2016/12/30的字样,是如何自动实现的(默认一般为Administrator),如下图: ...
- Mysql - 函数
Mysql提供的函数是在是太多了, 很多我都见过, 别说用了. 园子里面, 有人弄了一个比较全的. MYSQL函数 我这里会将他写的完全拷贝下来, 中间会插入一些自己项目中使用过的心得 一.数学函数 ...
- CodingLife主题更新
收到反馈说CodingLife主题某些地方显示有问题,于是进行了更新,并且已提交.官方那边正在进行测试,我自己这边测完应该是没问题的,但不知道官方啥时候会进行更新,所以把CSS代码贴出来,有需要的可以 ...
- Android AppBar
AppBar官方文档摘记 2016-6-12 本文摘自Android官方文档,为方便自己及其他开发者朋友阅读. 章节目录为"Develop > Training > Best P ...