前言:上篇文章介紹了.net core+Redis+IIS+nginx實現Session共享,本來打算直接說明后續填坑過程,但畢竟好多坑是用docker部署后出現的,原計划簡單提一下.net core+Redis+docker實現Session共享,但是發現篇幅也不小,所以還是單獨起草一篇,除了k8s部署docker,其它部分都有基本介紹。
1、環境准備
操作系統:Windows10
VS2019、本地Redis數據庫、Windows docker
2、背景介紹
由於項目從asp.net MVC向.net core webapi遷移,一方面是技術方面的遷移,另一方面是從業務方面切割,向微服務模式轉型,項目最后完成部署的結構大致如下:
總體上說,大家各自的項目有各自的部署方式,一旦做成分布式的,實現Session共享往往就不可避免了。
3、.net core+Redis+docker實現Session共享
如果你的項目是用IIS或其它方式部署,那么這部分你可以直接跳過了,因為代碼部分跟上篇文章是一樣的。無非是使用windows docker 命令進行部署。
(1)用VS2019新建一個Web Api項目(RedisSessionTest)
在Startup.cs文件中添加以下代碼
public void ConfigureServices(IServiceCollection services) { 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 = "commonweb1"; }) .SetApplicationName("commonweb1") .AddKeyManagementOptions(options => { //配置自定義XmlRepository options.XmlRepository = new SessionShare(); }); //services.AddSession(); #region 使用Redis保存Session // 這里取連接字符串 services.AddDistributedRedisCache(option => { //redis 連接字符串 option.Configuration = ""; //redis 實例名 option.InstanceName = "Test_Session"; }); //添加session 設置過期時長分鍾 //var sessionOutTime = con.ConnectionConfig.ConnectionRedis.SessionTimeOut; services.AddSession(options => { options.IdleTimeout = TimeSpan.FromSeconds(Convert.ToDouble(3 * 60 * 60)); //session活期時間 options.Cookie.HttpOnly = true;//設為httponly }); #endregion services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseSession(); app.UseMiddleware<RequestMiddleware>(); app.UseCookiePolicy(); app.UseMvc(); }
為什么這么加請參考上篇文章,這里我只做一個簡單的介紹:
services.Configure<CookiePolicyOptions>:配置可以讀取cookie信息。
app.UseCookiePolicy():表示使用ConfigureServices中配置cookie策略
services.AddDataProtection:配置應用程序名稱,自定義MachineKey,用於不同站點服務可以讀取同一Session。
services.AddDistributedRedisCache:將Session保存到Redis數據庫。
services.AddSession:配置Sesion策略。
app.UseMiddleware<RequestMiddleware>():使用自定義中間件。
(2)添加自定義中間件RequestMiddleware
public class RequestMiddleware { private readonly RequestDelegate _next; public RequestMiddleware(RequestDelegate next) { this._next = next; } public Task Invoke(HttpContext context) { context.Request.EnableRewind(); //支持context.Request.Body重復讀取,內部調用了EnableBuffering方法,否則在使用部分方法或屬性時會報錯誤System.NotSupportedException: Specified method is not supported,例如context.Request.Body.Position if (context.Request.ContentLength == null) { return this._next(context); } string sessionPhone = context.Session.GetString("phone"); if (string.IsNullOrEmpty(sessionPhone)) { context.Session.SetString("phone", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); } return this._next(context); } }
在中間件中保存當前時間到Session中。
(3)在ValuesController中添加測試接口
[HttpPost] public string PostTest(dynamic @param) { string phone = HttpContext.Session.GetString("phone"); return JsonConvert.SerializeObject(@param) + phone; }
為了方便我把路由從api/[Controller]改成了[action]:
(4)添加dockerfile文件如下(如果用別的方式部署,后續步驟可直接跳過,如果想了解windows docker的安裝和部署,可以點擊;如果想深入了解docker,這里我也幫不了多少,自己還在進一步學習中):
(5)使用docker命令(windows版)部署測試項目
打開cmd命令,cd定位到項目路徑
生成鏡像(最后面的.不能去掉): docker build -f /Redis使用測試/RedisSessionTest/RedisSessionTest/Dockerfile -t testcore .
映射容器端口:docker run --name testweb -p 7001:80 -d testcore
利用fiddler模擬請求,調用步驟3中創建的PostTest接口,驗證是否部署成功:
點擊composer->輸入接口地址->設置contentype頭信息->添加參數為{"qqq":147},最后得到結果是: {"qqq":147}2020-01-13 09:16:58
特別留意下這個cookie信息,它將作為另外一個站點下,同http://xxxx:7001/PostTest接口共享Session的接口的請求頭信息。
可以發現Session緩存的時間是2020-01-13 09:16:58,這里注意一下,docker容器所在linux系統中的時間比windows當前時間早了8個小時,也就是說我實際做測試的時間是2020-01-13 17:16:58,如果要解決這個問題,在dockerfile文件中加入時區設置:
#設置時區
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
(6)重復上面幾個步驟,新增一個新的web api項目(RedisSessionTestNew)
在第(3)步的時候,將新增項目的接口action改為PostTestNew,用於區別RedisSessionTest項目,即代碼如下:
[HttpPost] public string PostTestNew(dynamic @param) { string phone = HttpContext.Session.GetString("phone"); return JsonConvert.SerializeObject(@param) + phone; }
在第(5)步的時候,將新的項目映射為7002端口,我的測試項目部署如下:
生成鏡像(最后面的.不能去掉): docker build -f /Redis使用測試/RedisSessionTest/RedisSessionTestNew/Dockerfile -t testnewcore .
映射容器端口:docker run --name testnewweb -p 7002:80 -d testnewcore
接下來再使用fiddler去調用7002站點下的PostTestNew接口,注意帶上7001PostTest測試結果中的cookie信息,參數為{"qqq":258},結果如下:{"qqq":258}2020-01-13 09:16:58
4、分析測試結果
這里對比下兩次請求結果:
http://XXXX:7001/PostTest:{"qqq":147}2020-01-13 09:16:58
http://XXXX:7002/PostTestNew:{"qqq":258}2020-01-13 09:16:58
7002/PostTestNew的結果中輸出的請求參數值發生了變化,但是從Session中讀取到的時間是7001/PostTest設置的Session值,而且訪問Redis數據庫,確實只保存了一個Session值,說明實現了Session共享。
最后尤其要注意,這里采用了cookie值作為id尋找Session值的方式,所以項目中需要保存第一次緩存Session產生的cookie值,在后面http請求的頭中帶上該cookie值;若是session值發生了變化,則將新的cookie值覆蓋到原來的cookie值。