.Net Core中实现分布式 Session
集群
我们的项目如果跑在一台机器上,如果这台机器出现故障的话,或者用户请求量比较高,一台机器支撑不住的话。我们的网站可能就访问不了。那怎么解决呢?就需要使用多台机器,部署一样的程序,让几个机器同时的运行我们的网站。那怎么怎么分发请求的我们的所有机器上。所以负载均衡的概念就出现了。
负载均衡
负载均衡是指基于反向代理能将现在所有的请求根据指定的策略算法,分发到不同的服务器上。常用实现负载均衡的可以用nginx,lvs。但是现在也有个问题,如果负载均衡服务器出现问题了怎么办?所有冗余的概念就出现了。
如果我们只是想实现身份认证(如是否登录、会话是否超时),使用session管理即可满足。
session共享
大型的网站平台,都是分布式结构,分布式的好处是通过nginx分发请求,让多个服务器各自处理请求,来减少单一服务器的压力,并且提高执行效率。
在分布式结构下,如果不用共享session的话,就会出现问题。当一个客户端发送一个请求(无session),通过nginx将第一次请求分发给服务器1,服务器判断无session,就让那个客户进行登录操作,并得到响应,此时客户端会存储一个来自服务器1响应的session,并存储在客户端。
当客户端发送第二次请求的时候,此时本次请求已经携带了session(跳过登录),nginx却将请求分发给服务器2,因为服务器2中没有session,所以无法与客户端session进行对应。所以程序会出现异常或是报错,无法正常响应。
解决方法: session+redis 实现session 共享
当然现在还有jwt-token的分布式方案,有兴趣童鞋可以了解。
.NET Core中使用session
笔者这里以 .NET CORE 2.2 为例实现,最新版本为3.1,应该类似
第一步,添加Session的Nuget包
NuGet添加:Microsoft.AspNetCore.Session
第二步,在startup.cs 中开启session中间件
- 在ConfigureServices中增加AddSession
- 在Configure中增加UseSession
代码如下:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
//启用session
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.AddControllersWithViews();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
//启用session
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapRazorPages();
});
}
}
在Controllers中使用Session
using Microsoft.AspNetCore.Http; //添加程序集引用
public class HomeController:Controller
{
public IActionResult Index()
{
HttpContext.Session.SetString("Name","God");
return View();
}
public IActionResult About()
{
ViewBag.Name=HttpContext.Session.GetString("Name");
return View();
}
}
Session的基础配置情况,介绍完成,但是在实际项目中,使用情况挺复杂的,不可能只是在Controller中使用Session,遇到这种情况怎么办呢!
在类库中使用Session
首先,注入IHttpContextAccessor
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
简单做个封装,类似如下:
public static class CurrentUser
{
#region Initialize
private static IHttpContextAccessor _httpContextAccessor;
private static ISession _session => _httpContextAccessor.HttpContext.Session;
public static void Configure(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
#endregion
#region Attribute
/// <summary>
/// 用户主键
/// </summary>
public static AdminUser User
{
get =>
_session == null ? new AdminUser() : _session.Get<AdminUser>(AccountHashKeys.CurrentAdminUser);
set => _session.Set<AdminUser>(AccountHashKeys.CurrentAdminUser, value);
}
/// <summary>
/// 用户角色
/// </summary>
public static int RoleId
{
//get => _session == null ?0 : _session.Get<int>(AccountHashKeys.CurrentAdminRole);
//set => _session.Set<int>(AccountHashKeys.CurrentAdminRole, value);
get
{
if (_session == null)
{
var t = 1;
}
return _session == null ? 0 : _session.Get<int>(AccountHashKeys.CurrentAdminRole);
}
set => _session.Set<int>(AccountHashKeys.CurrentAdminRole, value);
}
public static string RoleName
{
get => _session == null ? "游客" : _session.Get<string>(AccountHashKeys.CurrentAdminRoleName);
set => _session.Set<string>(AccountHashKeys.CurrentAdminRoleName, value);
}
#endregion
}
在View视图中使用Session
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor _httpContextAccessor
@{
CurrentUser.Configure(_httpContextAccessor);
}
使用Redis实现分布式Session
将Session保存到Redis中,但是大家不清楚这个值是否是真的保存到Redis里面去了还是在项目内存中;所以这里就实现在两个不的应用程序(或两台不同的机器)中共享Session,也就是实现分布式Session,分布式即代表了不同的机器不同的应用程序,但往往有下面的一种尴尬的情况,就算是每个HTTP请求时都携带了相同的cookie值。
<section>
<a href="http://www.gitblogs.com/Files/BlogImg/20180917/6367281552034993356325842.png" data-lightbox=“example-set”><img style=“display: block; margin-left: auto; margin-right: auto;” src="http://www.gitblogs.com/Files/BlogImg/20180917/6367281552034993356325842.png" alt=""></a>
</section>
<br>
造成这个的问题的原因是每个机器上面的 ASP .NET Core的应用程序的密钥是不一样的,所以没有办法得到前一个应用程序保存的Session数据;为了解决这个问题,.NET Core团队为提供了Microsoft.AspNetCore.DataProtection.AzureStorage和Microsoft.AspNetCore.DataProtection.Redis包将密钥保存到Azure或Redis中。这里选择将密钥保存到Redis。
<section>
<a href="http://www.gitblogs.com/Files/BlogImg/20180917/6367281552047560963351469.png" data-lightbox=“example-set”><img style=“display: block; margin-left: auto; margin-right: auto;” src="http://www.gitblogs.com/Files/BlogImg/20180917/6367281552047560963351469.png" alt=""></a>
</section>
第一步,安装Redis包
- Microsoft.Extensions.Caching.StackExchangeRedis
- Microsoft.AspNetCore.DataProtection.StackExchangeRedis
第二步,在ConfigureServices中添加如下代码:
var redis = ConnectionMultiplexer.Connect("redids服务器IP:6379");
services.AddDataProtection() //将key存储在redis中,实现分布式session,如果不带有key操作,仅仅是将session存储在redis中,无法实现共享
.SetApplicationName("您的应用名")
.PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys");
services.AddStackExchangeRedisCache(options =>
{ //将session存储在redis中,不存储key的情况下,也可以使用此代码
options.Configuration = "redids服务器IP";
options.InstanceName = "db0";
});