前言:上篇文章介紹了.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值。
