詳解Session分佈式共享(.NET CORE版)

1、前言&回顧html


       在上篇文章Session分佈式共享 = Session + Redis + Nginx中,好多同窗留言問了我好多問題,其中印象深入的有:nginx掛了怎麼辦?採用Redis的Session方案與微軟Session方案相比,有什麼優點呢?Cookie也能夠取代Session的,採用Redis的Session方案優點在哪裏?Nginx的iphash方式究竟是什麼?MachineKey有啥用?Net Core怎樣實現?linux

       那會兒看到你們的提問,個人回答也只是從應用層面回答,基本上的回答能夠總結爲:「別人這麼作了,解決了這個問題,我用這個方法也解決了這個問題,原理請看連接。」很慚愧的說,那時的我並無徹底理解他真正的優點在哪裏,只是憑着直覺和經驗知道這樣作比較好,知道當一部分東西不可控時候,將其解耦、可視化、集羣就可讓一個系統更加健壯,但沒有一個理論支撐。通過最近一段時間的查閱資料和閱讀書籍,對此有了深入理解,本文將從網站架構的可用性角度對這種Session共享進行分析和講解,並用.net core再次實現這種架構模式。(Session分佈式共享的net core版,由於工做沒有機會應用到生產環境,過往經驗就更別提了,因此只是研究性的,請你們注意,但園子裏早有大牛寫出了相關文章,本文結束會將相關文章貼出)nginx

2、網站可用性--Session管理web


     可用性是網站架構中很是重要的一環,什麼是可用性,說的簡單些,就是用戶隨時隨地打開這個網站,這個網站都能打開,而且裏面的功能都能用。若是可用性不高會出現什麼狀況?你們想象一下春節在12306搶票的情景,網站各類崩潰,你們保準會想:要是有別的方式能買到票,我纔不用12306這個破網站呢。這個例子有點極端,由於業務場景比較極端,固然,這種現象也不光是網站可用性這一環出了問題。可是一個網站三天兩頭打不開,要麼是點開了裏面的頁面處處是報錯頁面和操做無反應,你還會用這個網站麼?我相信咱們在瀏覽網站時候,只要不像12306這種壟斷業務的網站,出現不可用的狀況,咱們必定會離開尋找其餘相似的網站。redis

     Session管理是網站可用性的內容之一,你們都知道Http是無狀態請求,即沒法追蹤上次Http請求的相關信息,可是業務中大量須要將Http變爲有狀態請求,Session就隨之產生了,但是在分佈式網站設計中,無狀態請求才能實現網站的橫向拓展(增減應用服務器),所以又與Session相矛盾,由於Session信息若是存儲在網站應用服務器的緩存中,加臺服務器就不能用了,所以將Session解耦是解決此問題的關鍵,下面介紹網站常見的Session管理手段。算法

一、Session複製數據庫

     Session複製是最先企業應用系統使用較多的一種服務集羣Session管理機制,開啓Session複製功能,便是在集羣中的幾臺服務器之間同步Session對象,Java中好像JBoss有這個功能,.Net暫不知道。json

     優點:Session信息讀取快,實現簡單。ubuntu

     缺點:集羣規模較大時,服務器之間Session複製會佔用服務器資源和網絡資源,最後系統會不堪重負。windows

image

二、Session綁定 

     Session綁定的方式,通常軟/硬均衡負載服務器都會提供此功能,例如:上篇文章Nginx的IPhash方式,均衡負載服務器利用Hash算法將同一IP分配到同一臺服務器上,即Session綁定在某臺特定服務器上,保證Session總能在這臺服務器上得到,又稱做爲會話黏滯。

     缺點:若是某臺服務器宕機,那麼這臺服務器上面的Session也就不存在了,用戶請求切換到其餘服務器上由於沒有Session而出錯。

image

 

三、利用Cookie記錄Session

     經過Cookie記錄Session信息是大部分網站採用的方法,這種方式只要Cookie不濫用,也是很是好很是成熟的方案。Cookie記錄Session就是把一些狀態信息放到了客戶端,每次請求都要傳輸到服務器。

     優點:這種方法簡單易實現,可用性高,支持服務器橫向拓展,方案成熟

     缺點:安全性問題,Cookie有大小限制,並且每次請求傳輸Cookie會影響性能

image

 

四、Session服務器

     Session服務器的方式管理Session,是一種很是好的解決方案,由於Session是爲了業務須要Http狀態而產生,而分佈式網站設計中提倡Http無狀態,爲了知足這一設計,Session服務器是將有狀態的Session信息與無狀態的應用服務器相分離,再針對不一樣服務器的不一樣特性進行設計。例如:咱們將Session信息存入到Redis中,那麼Redis的集羣配置、穩定性設置都有不少好的解決方案,若是將Session存入到Memcache,那麼Memcache的集羣配置、穩定性設置也會有不少成熟案例。這樣咱們就將一些問題簡單化,若是咱們單獨應用.Net的Session,咱們須要瞭解更多.Net深層次的東西並加以改造來保證其可用和穩定,越深層的東西越須要時間和閱歷,而若是將Session存儲介質轉移到Redis中,Redis集羣方案、管理工具都很是成熟,只須要配置配置就解決了Session的問題,何樂而不爲呢。

     優點:可用性高、安全性高、伸縮性好、性能高、信息大小無限制

image

 

3、.Net Core+Redis+Nginx實現Session分佈式共享


一、前期準備&環境

      (1)Vs2017    (2).Net Core 1.1  (3) Win 7  (4)ubuntu 16.04

二、.Net Core簡介

       隨着互聯網的發展,在當今中國市場(外國不大清楚)開源、跨平臺是衡量一門語言、技術好壞的重要指標之一,微軟爲了推進.Net開源及跨平臺,.Net Core隨之誕生。

       詳見大牛的文章:.NET Core與.NET Framework、Mono之間的關係

       下面說說.Net Core給個人初步的感覺:

         1).Net Core並無顛覆以前C#語法

          通俗講就是以前說中國話(C#),如今仍是說中國話,只是說話的環境變了。

         2).Net Core由於剛起步,API變了或者少了不少

          通俗講就是說話環境變了,並且裏面有好多你沒見過的東西,你不知道用什麼官方詞語來描述,由於官方正在找相關詞來描述這些新東西。

         3)脫離IIS,跨平臺

          通俗講就是微軟老媽爲了避免讓咱們到了新環境餓着,怕離開如今這個環境(Windows+IIS)以後不知道怎麼生存。因而,教會了咱們語言(C#),給了咱們掙錢的工具(.Net Core+Kestrel),說了一句「去吧孩子,本身奮鬥去吧,稍等,別忘了把這張Visa卡帶上(.Net Core SDK),我會按期給你打錢的。」

         4)NuGet愈來愈重要

          NuGet通過幾年的發展,愈來愈成熟,.Net Core開源組件獲取的主要方法,經過NuGet能夠下載各類中間件和組件,並且方便快捷(除了有時候斷網,可是可使用國內鏡像),NuGet就像微軟老媽給我們的一個通信錄,並告訴我們,若是你在某些方面須要幫助的時候,能夠經過NuGet找到你的七大姑八大姨來幫忙。

三、拓撲圖

image

          根據以前文章中成功的經驗,簡單改造一下,中間一個Windows系統和一個Ubuntu系統承載着.Net Core程序,有人會問Windows那個咋不來個IIS啊,我要說的是.Net Core實行走出去的原則,基本脫離IIS,若是IIS上面想部署.Net Core程序的話,須要安裝一樣的應用程序,而且站點配置的應用程序池也要變成「無託管代碼」。

四、開發.Net Core程序使用Session

4-一、建立一個Web程序

          用Vs2017建立一個.Net Core的Web應用程序,且這個應用程序不包含身份驗證信息

image

image

         建立完以下

image

4-二、.Net Core調用Session

        .Net Core使用Session,須要引用相關Session的NuGet包,網上一查,發現.Net Core的官方Session組件相似一箇中間件,而且官方支持Redis。

         注意:.Net Core的Mvc不能直接使用Session,若是你在程序裏面寫了個HttpContext.Session就會出現以下錯誤:Session has not been configured for this application or request.

image

4-2-一、Microsoft.AspNetCore.Session

         .Net Core使用Session必須安裝Microsoft.AspNetCore.Session,他的NuGet包安裝以下圖:

image

4-2-二、修改Startup.cs讓Session可用

          在相應位置加入高亮代碼services.AddSession(); app.UseSession();

public void ConfigureServices(IServiceCollection services)
 {
     // Add framework services.
     services.AddMvc();
   services.AddSession();
 }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
     loggerFactory.AddConsole(Configuration.GetSection("Logging"));
     loggerFactory.AddDebug();

     if (env.IsDevelopment())
     {
          app.UseDeveloperExceptionPage();
          app.UseBrowserLink();
     }
     else
     {
          app.UseExceptionHandler("/Home/Error");
     }

     app.UseStaticFiles();
 app.UseSession();
     app.UseMvc(routes =>
     {
          routes.MapRoute(
          name: "default",
          template: "{controller=Home}/{action=Index}/{id?}");
     });
}

4-2-三、Session寫入和讀取

          Session的讀取方式,與.Net有所不一樣,寫法以下,而且Session的HttpContext.Session.SetString或者HttpContext.Session.Set方法分別支持字符串和Byte數組,因此複雜實體須要轉化成Json存入Session中。

【Session 寫入方法】

HttpContext.Session.SetString("key", "strValue");

【Session 讀取方法】

HttpContext.Session.GetString("key")

五、Session存儲介質更換爲Redis

5-一、首先配置Redis

詳細配置方式見:Session分佈式共享 = Session + Redis + Nginx

redis-server redis.windows.conf

詳細配置方式見:Session分佈式共享 = Session + Redis + Nginx

5-二、安裝Microsoft.Extensions.Caching.Redis.Core

       NuGet中搜索Microsoft.Extensions.Caching.Redis.Core並安裝,此NuGet包是對Caching的拓展,便可以更換Caching存儲介質

image

5-三、appsettings.json配置Redis鏈接字符串

       appsettings.json配置Redis鏈接字符串(至關於web.config裏面配置appsetting節點),注意:添加位置要在Logging上面,不然讀不到,添加代碼爲下面的高亮部分

{

"Data": "RedisConnection",
"ConnectionStrings"
: {
"RedisConnection": "192.168.8.138:6379"
},
"Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}

5-四、Startup.cs的ConfigureServices方法中添加引用

public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();
 services.AddDistributedRedisCache(option =>
{
                   //redis 數據庫鏈接字符串
                   option.Configuration = Configuration.GetConnectionString("RedisConnection" );
                   // redis 實例名
                   
option.InstanceName = "master" ;
             
} );
              
services.AddSession();
 
           } 

         頁面運行HttpContext.Session.GetString("key"),而後用Redis管理工具RedisDesktopManager查詢Session是否入庫。

4

5-五、發佈前指定IP和端口(重要) 

         若是你沒有看這個步驟,繼續下面發佈步驟,等你發佈時候,你會發現一個尷尬的問題,就是你用IP訪問不了你的網站,用localhost能夠訪問,.Net Core默認是5000端口,端口占用也會讓你的網站訪問不了。

         只須要在Program.cs中添加高亮代碼便可,細心地人已經看到.UseUrls(new string[] { }) 傳入的是個數組,那麼這裏定義多個網站,當你執行時候dotnet命令時候,多個網站都會啓動。

image

public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                  //增長處,*號表示ip
                  .UseUrls(new string[] { "http://*:7201
})
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .UseApplicationInsights()
                .Build();

            host.Run();
        }

六、.Net Core 發佈

6-一、Windows安裝.Net Core發佈環境[10.2.107.100]

         1)安裝Windows Server Hosting (x64 & x86)至關於IIS,注意安裝時候請聯網(好像是自動下載sdk,具體沒仔細研究)。

image

        2)輸入dotnet命令驗證,若是「報’dotnet’不是內部或者外部命令」請找到「C:\Program Files\dotnet」文件夾中的dotnet.exe,用cmd來調用dotnet.exe來運行,或者添加系統環境變量(window中cmd命令能夠節省在編寫命令時候能夠.exe,即命令dotnet就是dotnet.exe)

image

       坑1】

         在win7下提示一下錯誤:Failed to load the dll from [C:\Program Files\dotnet\host\fxr\1.0.1\hostfxr.dll], HRESULT: 0x80070057

image

         解決方法:

         須要安裝補丁:KB2533623

        下載地址以下:

         https://support.microsoft.com/en-us/kb/2533623 

      【坑2】

         注意.net Core版本,本文主要是用的.net Core 1.1.1開發的,下面兩個截圖是版本按錯了出的錯誤信息

image

6-二、Ubuntu安裝.Net Core發佈環境[10.2.107.46]

         Ubuntu安裝.Net Core官方寫的很詳細了,照着作便可,千萬別抵觸Linux系統,抵觸的話那就別用.Net Core了,若是不知道Ubuntu和Linux的關係的話請百度。

image

         最後驗證dotnet命令是否可使用。

image

6-三、發佈網站

       在項目上右鍵->發佈…

image

image

       點擊發布按鈕,生成的文件以下(SessionTest爲應用程序名)

image

        好了,有了這些文件,咱們只須要把這些文件扔到服務器上就成了,可是怎麼啓動呢?經過查詢,網上說只要用dotnet命令就成。繼續實踐…

        說明:個人項目叫作image生成了image這個爲主要的dll,也是程序的入口。

        你們都知道.Net Core是跨平臺的,不一樣系統的服務器環境配置好了,網上查詢說是使用dotnet命令啓動網站,那麼能夠推斷出幾個平臺的dotnet命令是同樣的。

6-3-一、Windows啓動.Net Core網站[10.2.107.100:7201]

         啓動.Net Core網站的命令很簡單,安裝好發佈環境的應用程序,C:\Program Files\dotnet目錄以下(若是dotnet命令不能用,能夠直接調用dotnet.exe這個應用程序。)

image

         將生成好的網站複製到服務器上

image

 cmd命令找到PublishOutput

cd C:\PublishOutput

image

dotnet運行網站命令

dotnet SessionTest.dll

成功之後(以後再編譯運行,會提示下面截圖)

image

訪問http://10.2.107.100:7201/(若是一臺機子有多個網卡多個IP,其餘IP的7201端口也是個獨立網站)

image

 

6-3-二、Ubuntu啓動.Net Core網站[10.2.107.46:7201]

想辦法將發佈的程序複製到Ubuntu上面去,我測試使用的VBox虛擬機。

具體方法傳送門:virtualbox中ubuntu和windows共享文件夾設置

dotnet SessionTest.dll

image

訪問http://10.2.107.46:7201/

image

七、Nginx配置

7-一、網站端口修改

        nginx.conf配置修改

image

        listen   80; 改爲 listen   81; 由於通常都被80都被使用。

server {
        listen       81;
        ……
}

7-二、增長負載均衡

  nginx.conf中添加upstream節點

upstream Jq_one {
      server
10.2.107.100:7201
;
       server
10.2.107.46:7201;
} 
server {
.....
}

 

7-三、location節點修改

location / {
            root   html;
            index  index.aspx index.html index.htm;
 #其中jq_one 對應着upstream設置的集羣名稱 proxy_pass http://Jq_one; 
            #設置主機頭和客戶端真實地址,以便服務器獲取客戶端真實IP
            proxy_set_header   Host             $host; 
            proxy_set_header   X-Real-IP        $remote_addr; 
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
}

7-四、Nginx啓動命令

        C:\server\nginx-1.0.2>start nginx

        或

        C:\server\nginx-1.0.2>nginx.exe

7-五、Nginx從新載入命令

      C:\server\nginx-1.0.2>nginx.exe -s reload

 

4、黎明前的黑暗-MachineKey


 

      本覺得作了上述準備和相關代碼編寫,就可以實現Session共享了,結果我想的太簡單了,應用程序發佈後並不能實現Session共享,難道分佈式共享下Session須要特殊處理?.Net我是怎麼實現的,它們的方法應該方法相似。我忽然想到了MachineKey這個東西,以前在.Net版本分佈式共享時候須要添加這個東西,評論也有人問我什麼要加MachineKey。後來只能搜索.Net Core Machinekey關鍵詞,找到了如下幾篇文章作參考。

      搭建分佈式 ASP.NET Core Web

      ASP.NET Core 數據保護(Data Protection)

      坎坷路:ASP.NET Core 1.0 Identity 身份驗證(中集)

      net core 1.0 實現負載多服務器單點登陸

      此問題屬於數據安全問題,微軟在開發.Net Core中延續了以前的設計,採用數據保護(Data Protection)方式對一些內部數據進行加密解密設計,如:Session、Cookie等(遠不止這些)。這樣能夠保證數據的真實性、完整性、機密性、隔離性。數據安全必然離不開加解密算法,你們想一下以前.Net的WebFrom中的ViewState,它最終解析到Html頁面是個hidden標籤裏面有一串很複雜的字符串,這個字符串是被數據保護(Data Protection)機制加密過的。Session也同樣,你們能夠看看Session存到Redis中啥樣,見下圖:

image

       數據保護(Data Protection)有個特性是隔離性,你們能夠想象一下,數據保護核心是加密解密,常見的加密方式有對稱加密和非對稱加密,上一篇作分佈式共享時候,兩臺機子拷貝了一樣的MahcineKey,那麼他的內部加密猜想好像是對稱加密,MachineKey直譯中文爲「機器鑰匙」在聯想隔離性,那麼能夠推斷出來不一樣機子密鑰是不一樣的,那麼MachineKey的做用是統一不一樣機子的密鑰。(吐血中…….這個只是個猜想,詳細原理請參考專業文章)

一、提取.Net Core的MachineKey

        .Net Core的MachineKey存儲是以key-xxxx-xxxx-xxxx-xxxx.xml的形式存儲的,那如何提取這個xml信息呢?

       Startup.cs的ConfigureServices添加下圖高亮代碼

public void ConfigureServices(IServiceCollection services)
        {
              //抽取key-xxxxx.xml
            services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(@"D:\XML"));
            services.AddSession();
            services.AddDistributedRedisCache(option =>
            {
                //redis 數據庫鏈接字符串
                option.Configuration = Configuration.GetConnectionString("RedisConnection");

                //redis 實例名
                option.InstanceName = "master";
            });
            services.AddMvc();
        }

       查看D:\Xml裏的xml文件

imageimage

 

二、重寫IXmlRepository接口固定Key

       在項目中添加CustomXmlRepository.cs類,其中keyContent中填寫key.xml內容,注意:裏面的幾個時間(如今還不能肯定expirationDate對項目是否有影響),有人問我KeyContent可否從文件裏讀,回答是能夠,可是ubuntu的文件路徑保準不是Windows的d:\之類的,須要使用Linux的寫法,因此乾脆字符串來的快。

using Microsoft.AspNetCore.DataProtection.Repositories;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace SessionTest
{
    public class CustomXmlRepository : IXmlRepository
    {
        private readonly string keyContent =
@"<?xml version='1.0' encoding='utf-8'?>
<key id='9108538d-9ea4-45fb-a690-438c8d788619' version='1'>
  <creationDate>2017-04-27T06:15:07.2194692Z</creationDate>
  <activationDate>2017-04-27T06:15:07.1844647Z</activationDate>
  <expirationDate>2017-07-26T06:15:07.1844647Z</expirationDate>
  <descriptor deserializerType='Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'>
    <descriptor>
      <encryption algorithm='AES_256_CBC' />
      <validation algorithm='HMACSHA256' />
      <masterKey p4:requiresEncryption='true' xmlns:p4='http://schemas.asp.net/2015/03/dataProtection'>
        <!-- Warning: the key below is in an unencrypted form. -->
      <value>HOz58FE6STtDHlMo2ZONoPgPTOOjRPikRWXmHOwNDS5o6NPb4hlgl/DxXUhat66soovBUFy1APXCQ4z30DDPyw==</value>
      </masterKey>
    </descriptor>
  </descriptor>
</key>";
         
        public virtual IReadOnlyCollection<XElement> GetAllElements()
        {
            return GetAllElementsCore().ToList().AsReadOnly();
        }

        private IEnumerable<XElement> GetAllElementsCore()
        {
            yield return XElement.Parse(keyContent);
        }
        public virtual void StoreElement(XElement element, string friendlyName)
        {
            if (element == null)
            {
                throw new ArgumentNullException(nameof(element));
            }
            StoreElementCore(element, friendlyName);
        }

        private void StoreElementCore(XElement element, string filename)
        {
        }
    }
}

修改Startup.cs文件中的ConfigureServices方法加載自定義的CustomXmlRepository類

public void ConfigureServices(IServiceCollection services)
        {
            ////抽取key-xxxxx.xml
            //services.AddDataProtection()
            //        .PersistKeysToFileSystem(new DirectoryInfo(@"D:\XML"));

 services.AddSingleton<IXmlRepository, CustomXmlRepository>(); 
              services.AddDataProtection(configure =>
 { configure.ApplicationDiscriminator = "newP.Web";
 });

            services.AddSession();
            services.AddDistributedRedisCache(option =>
            {
                //redis 數據庫鏈接字符串
                option.Configuration = Configuration.GetConnectionString("RedisConnection");

                //redis 實例名
                option.InstanceName = "master";
            });

            services.AddMvc();

        }

5、實現效果演示


         演示效果說明

         本機127.0.0.1也爲10.2.107.100,由於電腦性能有限,沒有弄windows虛擬機,只弄了10.2.107.46這臺Linux虛擬機。

         MachineKey的這個實現思路也能夠用到.Net Core的身份驗證上。

         UNC文件也能夠實現Session共享方式

         原理就是Windows和Linux經過文件共享和掛載的方式Key.xml共享一個文件,可是總以爲有點怪怪的,共享文件會不會被別人惡意篡改,因此最後採用重寫的方式實現。

         對UNC方式感興趣的請看:搭建分佈式 ASP.NET Core Web

 

 

5 6、後記&感悟


         但願經過本文,讓你們對網站的可用性中有個簡單認識,並瞭解到Session存入Redis中的優點。

        我的觀點,有可能由於知識和閱歷的緣由,分析片面,請多諒解。

 

7、參考文章


         ASP.NET Core 使用 Redis 和 Protobuf 進行 Session 緩存

        .Net Core Session使用

        Asp.net Core 使用Redis存儲Session

        Using Sessions and HttpContext in ASP.NET Core and MVC Core

        .NET Core與.NET Framework、Mono之間的關係

        virtualbox中ubuntu和windows共享文件夾設置  

        搭建分佈式 ASP.NET Core Web

        ASP.NET Core 數據保護(Data Protection)

        坎坷路:ASP.NET Core 1.0 Identity 身份驗證(中集)

        net core 1.0 實現負載多服務器單點登陸

相關文章
相關標籤/搜索