1. 程式人生 > >.Net Core Web Api實踐(二).net core+Redis+IIS+nginx實現Session共享

.Net Core Web Api實踐(二).net core+Redis+IIS+nginx實現Session共享

前言:雖說公司app後端使用的是.net core+Redis+docker+k8s部署的,但是微信公眾號後端使用的是IIS部署的,雖說公眾號併發量不大,但領導還是使用了負載均衡,所以在介紹docker+k8s實現分散式Session共享之前,就先介紹一下IIS+nginx實現Session共享的方案,兩者其實區別不大,所以這篇著重介紹方案,下篇介紹測試的區別以及填坑的方式。

1、環境準備

作業系統:Windows10

IIS:需要安裝模組

VS2019、本地Redis資料庫、ngnix(windows版)

2、Session共享的簡易說明

下圖簡要說明了負載均衡通過輪詢方式,將同一個客戶端請求傳送到不同的站點下,操作的Session應該是同一個。

3、新增測試專案

雖然個人認為本來WebApi中使用Session本身就是一種不合理的設計,但這是舊專案遷移需要保留的歷史邏輯,所以只能硬著頭皮尋找對應的解決方案了。

在VS2019中新增一個.net core 的WebApi專案,使用Session的話需要新增以下配置。

Startup.cs類中,ConfigureServices方法新增services.AddSession();  Configure方法中新增app.UseSession();  注意要放到UseMVC方法前面。

測試程式碼如下,新增testController類,在Get1方法中設定Session,記錄當前時間,Get2方法中讀取Session

[Route("[action]")]
    [ApiController]
    public class testController : ControllerBase
    {
        // GET: api/test
        [HttpGet]
        public IEnumerable<string> Get()
        {            
            return new string[] { "value1", "value2", HttpContext.Connection.LocalIpAddress.ToString(), HttpContext.Connection.LocalPort.ToString()};
        }

        // GET: api/test/5
        [HttpGet]
        public string Get1(int temp1)
        {
            if (string.IsNullOrEmpty(HttpContext.Session.GetString("qqq")))
            {
                HttpContext.Session.SetString("qqq", DateTime.Now.ToString());
            }
            return HttpContext.Connection.LocalIpAddress.ToString() + "|" + HttpContext.Connection.LocalPort.ToString();
        }

        [HttpGet]
        public string Get2(int temp1, int temp2)
        {            
            return HttpContext.Connection.LocalIpAddress.ToString() + "|" + HttpContext.Connection.LocalPort.ToString() + "|" + HttpContext.Session.GetString("qqq");
        }
    }

4、釋出.net core專案到IIS  

(1)右鍵專案點擊發布

 

 

 (2)記錄下釋出路徑,並在IIS中新增兩個站點,指向該路徑,並設定不同的埠號

 

 

 

 

記得把應用程式池中改為無託管程式碼

 

 

 

 

 

5、nginx配置負載均衡

下載地址:http://nginx.org/

配置方式:

(1)找到nginx的安裝路徑,開啟nginx.conf檔案

(2)新增upstream配置,配置用於負載均衡輪詢的站點,即上一步驟中新增的兩個站點

 

(3)配置location節點,注意proxy_pass與upstream中配置的名稱保持一致。

 

(4)啟動ngnix,用cmd命令指定nginx的安裝目錄,然後start nginx

 

 

 

 

6、在沒有做Session共享方案的情況下進行測試

 

瀏覽器分別輸入http://localhost:7665/Get1與http://localhost:7665/Get2,由於ip是一樣的,所以沒有參考必要,不停重新整理http://localhost:7665/Get1,最後看到的埠號在7666與7667之間不停的來回切換,說明nginx的輪詢是成功的。當然這裡只是為了實現session共享做的負載均衡,所以把負載均衡放在了同一臺伺服器上進行配置,感興趣的同學可以使用不同伺服器配置負載均衡,並用壓力測試工具測試站點在配置負載均衡的吞吐能力,後面有機會我可以單獨介紹這部分內容。

 

 

 

 

 

 

 

 

測試結果:

1、過程:  請求http://localhost:7665/Get1,請求分發到站點7667,設定了Session;

請求http://localhost:7665/Get2,請求分發到站點7666,讀取不到該Session;

再次請求Get2,請求分發到站點7667,可以讀取到Session。

結論:說明負載均衡的兩個站點之間不會讀取同一個Session,也就是說Session不會共享。

2、 過程: 再次請求Get1,請求分發到站點7666;

再次請求Get2,請求分發到站點7667,讀取不到該Session;

再次請求Get2,請求分發到站點7666,可以讀取到Session,且session值被重新整理。

結論:因為nginx每次都將請求分發到另外一站點,且session沒有共享,所以string.IsNullOrEmpty(HttpContext.Session.GetString("qqq"))總是true,然後session都會被重新整理,另一個站點的session就丟失了(這裡的丟失應該是重新整理session後產生了新的cookie值,導致原來的session無法讀取,感興趣的同學還可以用Fiddler跟蹤記錄下Get1產生cookie值,然後在Get2請求時帶上cookie進行驗證)。

 

7、使用Redis將Session存放在Redis伺服器

(1)在Startup.cs檔案中,ConfigureServices方法加入以下程式碼

services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => false; //這裡要改為false,預設是true,true的時候session無效
                options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None;
            });
            
            services.AddDataProtection(configure =>
            {
                configure.ApplicationDiscriminator = "wxweb";
            })
            .SetApplicationName("wxweb")
            .AddKeyManagementOptions(options =>
            {
                //配置自定義XmlRepository
                options.XmlRepository = new SessionShare();
            });

            #region 使用Redis儲存Session
            // 這裡取連線字串 
            services.AddDistributedRedisCache(option =>
            {
                //redis 連線字串
                option.Configuration = Configuration.GetValue<string>("RedisConnectionStrings");
                //redis 例項名
                option.InstanceName = "Wx_Session";
            });

            //新增session 設定過期時長分鐘  
            //var sessionOutTime = con.ConnectionConfig.ConnectionRedis.SessionTimeOut;
            services.AddSession(options =>
            {
                options.IdleTimeout = TimeSpan.FromSeconds(Convert.ToDouble(8 * 60 * 60)); //session活期時間
                options.Cookie.HttpOnly = true;//設為httponly
            });
            #endregion

 

簡要說明:

services.Configure<CookiePolicyOptions>是為了可以使用cookie

SetApplicationName("wxweb")是為了保證不同站點下的應用程式名稱是一致的。

options.XmlRepository = new SessionShare();是為了保證不同站點下應用程式使用的machinekey是一樣的,詳情見https://www.cnblogs.com/newP/p/6518918.html

AddDistributedRedisCache是一個官方的拓展元件,使用者將session儲存在redis中。

RedisConnectionStrings是Redis連線字串

 

(2)SessionShare的實現如下

public class SessionShare : IXmlRepository
    {
        private readonly string keyContent =
@"自己的machinekey";

        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)
        {
        }
    }

(3)再次進行釋出

 

8、新增Session共享方案以後進行測試

測試結果:無論Get1重新整理多少次,Get2都能拿到Session值,且Session沒有被重新整理(當前時間沒有變化),即Session共享成功。

 

 

 

總結:以前總是看別人的文章瞭解Session共享,這次自己從配置負載均衡到解決Session共享從頭到尾走了一遍,所以記錄了下來。下篇文章我會介紹AddDistributedRedisCached在併發量較高時timeout的解決方案。

 

參考文章(同時感謝這些大佬的文章提供的幫助)

1、【nginx】配置Nginx實現負載均衡

2、Session分散式共享 = Session + Redis + N