nginx+iis+redis+Task.MainForm构建分布式架构 之 (redis存储分布式共享的session及共享session运作流程)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,上一篇分享文章制作是在windows上使用的nginx,一般正式发布的时候是在linux来配置nginx,我这里测试分享内容只是起引导作用;下面将先给出整个架构的核心节点简介,希望各位多多点赞:   .

本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,上一篇分享文章制作是在windows上使用的nginx,一般正式发布的时候是在linux来配置nginx,我这里测试分享内容只是起引导作用;下面将先给出整个架构的核心节点简介,希望各位多多点赞:

 

. 架构设计图展示

nginx+iis构建服务集群

. 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中,下面先来贴出方法内容:

 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         }
View Code

里面用到了 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是一样的,所以本章要将的内容大致就要结束了,如果觉得文章让您有所收获,请多多点"",谢谢。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
18天前
|
缓存 NoSQL 搜索推荐
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
本文介绍了如何通过Lua脚本在Redis中实现分布式锁的原子性操作,避免并发问题。首先讲解了Lua脚本的基本概念及其在Redis中的使用方法,包括通过`eval`指令执行Lua脚本和通过`script load`指令缓存脚本。接着详细展示了如何用Lua脚本实现加锁、解锁及可重入锁的功能,确保同一线程可以多次获取锁而不发生死锁。最后,通过代码示例演示了如何在实际业务中调用这些Lua脚本,确保锁操作的原子性和安全性。
48 6
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
|
19天前
|
NoSQL Java 中间件
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
本文介绍了从单机锁到分布式锁的演变,重点探讨了使用Redis实现分布式锁的方法。分布式锁用于控制分布式系统中多个实例对共享资源的同步访问,需满足互斥性、可重入性、锁超时防死锁和锁释放正确防误删等特性。文章通过具体示例展示了如何利用Redis的`setnx`命令实现加锁,并分析了简化版分布式锁存在的问题,如锁超时和误删。为了解决这些问题,文中提出了设置锁过期时间和在解锁前验证持有锁的线程身份的优化方案。最后指出,尽管当前设计已解决部分问题,但仍存在进一步优化的空间,将在后续章节继续探讨。
459 131
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
|
22天前
|
NoSQL Java Redis
Springboot使用Redis实现分布式锁
通过这些步骤和示例,您可以系统地了解如何在Spring Boot中使用Redis实现分布式锁,并在实际项目中应用。希望这些内容对您的学习和工作有所帮助。
152 83
|
1月前
|
缓存 NoSQL 中间件
Redis,分布式缓存演化之路
本文介绍了基于Redis的分布式缓存演化,探讨了分布式锁和缓存一致性问题及其解决方案。首先分析了本地缓存和分布式缓存的区别与优劣,接着深入讲解了分布式远程缓存带来的并发、缓存失效(穿透、雪崩、击穿)等问题及应对策略。文章还详细描述了如何使用Redis实现分布式锁,确保高并发场景下的数据一致性和系统稳定性。最后,通过双写模式和失效模式讨论了缓存一致性问题,并提出了多种解决方案,如引入Canal中间件等。希望这些内容能为读者在设计分布式缓存系统时提供有价值的参考。感谢您的阅读!
130 6
Redis,分布式缓存演化之路
|
2月前
|
存储 分布式计算 Hadoop
基于Java的Hadoop文件处理系统:高效分布式数据解析与存储
本文介绍了如何借鉴Hadoop的设计思想,使用Java实现其核心功能MapReduce,解决海量数据处理问题。通过类比图书馆管理系统,详细解释了Hadoop的两大组件:HDFS(分布式文件系统)和MapReduce(分布式计算模型)。具体实现了单词统计任务,并扩展支持CSV和JSON格式的数据解析。为了提升性能,引入了Combiner减少中间数据传输,以及自定义Partitioner解决数据倾斜问题。最后总结了Hadoop在大数据处理中的重要性,鼓励Java开发者学习Hadoop以拓展技术边界。
72 7
|
3月前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
250 5
|
1月前
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
|
3月前
|
Cloud Native API 持续交付
云原生架构下的微服务治理策略与实践####
本文旨在探讨云原生环境下微服务架构的治理策略,通过分析当前面临的挑战,提出一系列实用的解决方案。我们将深入讨论如何利用容器化、服务网格(Service Mesh)等先进技术手段,提升微服务系统的可管理性、可扩展性和容错能力。此外,还将分享一些来自一线项目的经验教训,帮助读者更好地理解和应用这些理论到实际工作中去。 ####
82 0
|
3月前
|
Java 开发者 微服务
从单体到微服务:如何借助 Spring Cloud 实现架构转型
**Spring Cloud** 是一套基于 Spring 框架的**微服务架构解决方案**,它提供了一系列的工具和组件,帮助开发者快速构建分布式系统,尤其是微服务架构。
350 69
从单体到微服务:如何借助 Spring Cloud 实现架构转型
|
3月前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。

热门文章

最新文章