本次要分享的是利用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 = "[email protected]"; 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是一樣的,所以本章要將的內容大致就要結束了,如果覺得文章讓您有所收獲,請多多點"贊",謝謝。