本次要分享的是利用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的方法有多中,這裡要分享的是使用redis來存儲session;随着登陸使用者增多,存儲在伺服器上的session也越來越多,如果某台伺服器上使用記憶體儲存的sesssion那伺服器的記憶體占有會對比的提升,最終可能直接奔潰,嚴重的由于長時間100%記憶體占用率可能導緻硬碟燒壞,由此産生了多種存儲方式如:使用同步session到不同伺服器方式做讀寫分離,資料庫存儲session等方式;其實我們要用到的redis叢集存儲操作session的方式也主要是分攤讀寫;
其次,session的讀寫分離通常都對應站點有很大的通路量了,如果通路量如此之大那麼站點的釋出對應的應該也是叢集的方式(名為分布式架構),分布式架構和單站點模式對比最明顯的在于分布式對應的多個站點隻需要使用其中某一個登陸入口登陸後,其他站點共享此session,無需再做登陸操作,其實這裡就可以看做是單點登陸,隻是分布式叢集一般通路的都是同一個域名或同一ip段而已;而單站點模式通常就是我這裡登陸了,就隻能我本系統能使用,另外的系統無法使用(正常來講);
最後,既然要滿足共享session,那麼session要麼就是儲存在同一個地方,讀取的時候也在同一個地方讀取;要麼就redis叢集這種方式實作即時同步到不同伺服器上或不同端口實作資料讀寫分離;這樣就能保證統一資料源;
首先,上面的内容也基本介紹了下分布式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就會驗證使用者需要登入才能通路需要權限的頁面
首先,将要發出來的兩個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是一樣的,是以本章要将的内容大緻就要結束了,如果覺得文章讓您有所收獲,請多多點"贊",謝謝。
git位址:
https://github.com/shenniubuxing3nuget釋出包:
https://www.nuget.org/profiles/shenniubuxing3