.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