1. 程式人生 > >詳解Session分散式共享(.NET CORE版)

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

一、前言&回顧

       在上篇文章Session分散式共享 = Session + Redis + Nginx中,好多同學留言問了我好多問題,其中印象深刻的有:nginx掛了怎麼辦?採用Redis的Session方案與微軟Session方案相比,有什麼優勢呢?Cookie也可以取代Session的,採用Redis的Session方案優勢在哪裡?Nginx的iphash方式到底是什麼?MachineKey有啥用?Net Core怎樣實現?

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

二、網站可用性--Session管理

  可用性是網站架構中非常重要的一環,什麼是可用性,說的簡單些,就是使用者隨時隨地開啟這個網站,這個網站都能開啟,並且裡面的功能都能用。如果可用性不高會出現什麼情況?大家想象一下春節在12306搶票的情景,網站各種崩潰,大家保準會想:要是有別的方式能買到票,我才不用12306這個破網站呢。這個例子有點極端,因為業務場景比較極端,當然,這種現象也不光是網站可用性這一環出了問題。但是一個網站三天兩頭打不開,要麼是點開了裡面的頁面到處是報錯頁面和操作無反應,你還會用這個網站麼?我相信我們在瀏覽網站時候,只要不像12306這種壟斷業務的網站,出現不可用的情況,我們一定會離開尋找其他類似的網站。

  Session管理是網站可用性的內容之一,大家都知道Http是無狀態請求,即無法追蹤上次Http請求的相關資訊,但是業務中大量需要將Http變為有狀態請求,Session就隨之產生了,可是在分散式網站設計中,無狀態請求才能實現網站的橫向拓展(增減應用伺服器),因此又與Session相矛盾,因為Session資訊如果儲存在網站應用伺服器的快取中,加臺伺服器就不能用了,因此將Session解耦是解決此問題的關鍵,下面介紹網站常見的Session管理手段。

1、Session複製

  Session複製是最早企業應用系統使用較多的一種服務叢集Session管理機制,開啟Session複製功能,即是在叢集中的幾臺伺服器之間同步Session物件,Java中好像JBoss有這個功能,.Net暫不知道。

  優勢:Session資訊讀取快,實現簡單。

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

image

2、Session繫結 

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

  缺點:如果某臺伺服器宕機,那麼這臺伺服器上面的Session也就不存在了,使用者請求切換到其他伺服器上因為沒有Session而出錯。

image

3、利用Cookie記錄Session

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

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

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

image

4、Session伺服器

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

  優勢:可用性高、安全性高、伸縮性好、效能高、資訊大小無限制

image

三、.Net Core+Redis+Nginx實現Session分散式共享

1、前期準備&環境

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

2、.Net Core簡介

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

       下面說說.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找到你的七大姑八大姨來幫忙。

3、拓撲圖

image

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

4、開發.Net Core程式使用Session

4-1、建立一個Web程式

          用Vs2017建立一個.Net Core的Web應用程式,且這個應用程式不包含身份驗證資訊

image

image

         建立完如下

image

4-2、.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-1、Microsoft.AspNetCore.Session

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

image

4-2-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-3、Session寫入和讀取

        Session的讀取方式,與.Net有所不同,寫法如下,並且Session的HttpContext.Session.SetString或者HttpContext.Session.Set方法分別支援字串和Byte陣列,所以複雜實體需要轉化成Json存入Session中。

【Session 寫入方法】

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

【Session 讀取方法】

HttpContext.Session.GetString("key")

5、Session儲存介質更換為Redis

5-1、首先配置Redis

redis-server redis.windows.conf

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

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

image

5-3、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-4、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-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();
        }

複製程式碼

6、.Net Core 釋出

6-1、Windows安裝.Net Core釋出環境[10.2.107.100]

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

        下載地址如下:

      【坑2】

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

image
 

6-2、Ubuntu安裝.Net Core釋出環境[10.2.107.46]

         Ubuntu安裝.Net Core官方寫的很詳細了,照著做即可,千萬別抵觸Linux系統,抵觸的話那就別用.Net Core了,如果不知道Ubuntu和Linux的關係的話請百度。

image

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

image

6-3、釋出網站

       在專案上右鍵->釋出…

image

image

       點擊發布按鈕,生成的檔案如下(SessionTest為應用程式名)

image

       好了,有了這些檔案,我們只需要把這些檔案扔到伺服器上就成了,但是怎麼啟動呢?通過查詢,網上說只要用dotnet命令就成。繼續實踐…

       說明:我的專案叫做image生成了image這個為主要的dll,也是程式的入口。

       大家都知道.Net Core是跨平臺的,不同系統的伺服器環境配置好了,網上查詢說是使用dotnet命令啟動網站,那麼可以推斷出幾個平臺的dotnet命令是一樣的。

6-3-1、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-2、Ubuntu啟動.Net Core網站[10.2.107.46:7201]

想辦法將釋出的程式複製到Ubuntu上面去,我測試使用的VBox虛擬機器。

dotnet SessionTest.dll

image

image

7、Nginx配置

7-1、網站埠修改

       nginx.conf配置修改

image

       listen   80; 改成 listen   81; 因為一般都被80都被使用。

server {
        listen       81;
        ……
}

7-2、增加負載均衡

  nginx.conf中新增upstream節點

複製程式碼

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

} 
server {
.....
}

複製程式碼

7-3、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-4、Nginx啟動命令

        C:\server\nginx-1.0.2>start nginx

        或

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

7-5、Nginx重新載入命令

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

四、黎明前的黑暗-MachineKey

      本以為做了上述準備和相關程式碼編寫,就能夠實現Session共享了,結果我想的太簡單了,應用程式釋出後並不能實現Session共享,難道分散式共享下Session需要特殊處理?.Net我是怎麼實現的,它們的方法應該方法類似。我突然想到了MachineKey這個東西,之前在.Net版本分散式共享時候需要新增這個東西,評論也有人問我什麼要加MachineKey。後來只能搜尋.Net Core Machinekey關鍵詞,找到了以下幾篇文章做參考。

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

image

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

1、提取.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

2、重寫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();

        }

複製程式碼

五、實現效果演示

   演示效果說明

   本機127.0.0.1也為10.2.107.100,因為電腦效能有限,沒有弄windows虛擬機器,只弄了10.2.107.46這臺Linux虛擬機器。

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

   UNC檔案也可以實現Session共享方式

   原理就是Windows和Linux通過檔案共享和掛載的方式Key.xml共享一個檔案,但是總覺得有點怪怪的,共享檔案會不會被別人惡意篡改,所以最後採用重寫的方式實現。

5 六、後記&感悟

   希望通過本文,讓大家對網站的可用性中有個簡單認識,並瞭解到Session存入Redis中的優勢。本文介紹的網站可用性內容中的冰山一角,還有許多知識需要我們去學習和積累。

        .Net Core版本的Session分散式共享,讓我們對.Net Core有了初步瞭解,.Net Core的高效能、跨平臺、開源,讓許多人改變了對.Net的看法,但是.Net Core在中國市場的路還有很長要走,我認為.Net Core並不是扭轉.Net語言在中國市場佔有率的銀彈。真正的銀彈也許是我們這些天天寫程式的.Neter,即使是微軟大量宣傳.Net Core、成功案例漫天飛,我們不去學習、不去了解新知識,我們最終會被淘汰。語言只是工具,只有通過不斷學習和努力,將知識消化、吸收並最終分享給別人才會有最大的收穫,我們在十字路口迷茫之時,為何不去學習新的知識和方法提升自身的經驗和閱歷。我經常會跟別人說,工作前幾年最重要的不是知識,而是你做事的風格和為目標持之以恆的信念,俗話說“江山易改,本性難移”,如果不好的工作態度和方法變成了你的工作習慣,即使換了語言、換了工作甚至轉了行,都會對你的職業發展有很大影響。好的習慣一定要堅持,有些事堅持一天可以、堅持兩天可以、但是堅持三個月以上,卻變成了無法完成的任務,更別提幾年了,“不積跬步,無以至千里”,只有堅持每天去磨練自己才能有所成長,因為我知道我不是天才,需要後天的努力才能成長。

      “踏踏實實做人,認認真真做事”我堅信自己的努力,一定會有回報的,只是現在還沒有抓住機遇。最後,向那些奮鬥在一線使用.Net Core開發的人員致敬。

        以上總結是我熬的味道濃郁的心靈雞湯01DAAF7A,可話說啥時候能改掉我工作外的拖延症啊01D99C38,這篇文章一直拖拖拖,論文一直拖拖拖,學英語拖拖拖,還有好多事要做可一直也是拖拖拖,悲劇啊01D9102F。。。突然發現鴨梨山大啊,壞習慣不好改啊!請大家引以為戒!當然別做工作狂,身體健康更重要,有時間多陪陪家裡人。

      個人觀點,有可能因為知識和閱歷的原因,分析片面,請多諒解。

七、參考文章