详解Session分布式共享(.NET CORE版)
一、前言&回顾
在上篇文章Session分布式共享 = Session + Redis + Nginx中,好多同学留言问了我好多问题,其中印象深刻的有:nginx挂了怎么办?采用Redis的Session方案与微软Session方案相比,有什么优势呢?Cookie也可以取代Session的,采用Redis的Session方案优势在哪里?Nginx的iphash方式到底是什么?MachineKey有啥用?Net Core怎样实现?
那会儿看到大家的提问,我的回答也只是从应用层面回答,基本上的回答可以总结为:“别人这么做了,解决了这个问题,我用这个方法也解决了这个问题,原理请看链接。”很惭愧的说,那时的我并没有完全理解他真正的优势在哪里,只是凭着直觉和经验知道这样做比较好,知道当一部分东西不可控时候,将其解耦、可视化、集群就可以让一个系统更加健壮,但没有一个理论支撑。经过最近一段时间的查阅资料和阅读书籍,对此有了深刻理解,本文将从网站架构的可用性角度对这种Session共享进行分析和讲解,并用.net core再次实现这种架构模式。(Session分布式共享的net core版,因为工作没有机会应用到生产环境,过往经验就更别提了,所以只是研究性的,请大家注意,但园子里早有大牛写出了相关文章,本文结束会将相关文章贴出)
二、网站可用性--Session管理
可用性是网站架构中非常重要的一环,什么是可用性,说的简单些,就是用户随时随地打开这个网站,这个网站都能打开,并且里面的功能都能用。如果可用性不高会出现什么情况?大家想象一下春节在12306抢票的情景,网站各种崩溃,大家保准会想:要是有别的方式能买到票,我才不用12306这个破网站呢。这个例子有点极端,因为业务场景比较极端,当然,这种现象也不光是网站可用性这一环出了问题。但是一个网站三天两头打不开,要么是点开了里面的页面到处是报错页面和操作无反应,你还会用这个网站么?我相信我们在浏览网站时候,只要不像12306这种垄断业务的网站,出现不可用的情况,我们一定会离开寻找其他类似的网站。
Session管理是网站可用性的内容之一,大家都知道Http是无状态请求,即无法追踪上次Http请求的相关信息,但是业务中大量需要将Http变为有状态请求,Session就随之产生了,可是在分布式网站设计中,无状态请求才能实现网站的横向拓展(增减应用服务器),因此又与Session相矛盾,因为Session信息如果存储在网站应用服务器的缓存中,加台服务器就不能用了,因此将Session解耦是解决此问题的关键,下面介绍网站常见的Session管理手段。
1、Session复制
Session复制是最早企业应用系统使用较多的一种服务集群Session管理机制,开启Session复制功能,即是在集群中的几台服务器之间同步Session对象,Java中好像JBoss有这个功能,.Net暂不知道。
优势:Session信息读取快,实现简单。
缺点:集群规模较大时,服务器之间Session复制会占用服务器资源和网络资源,最后系统会不堪重负。
2、Session绑定
Session绑定的方式,一般软/硬均衡负载服务器都会提供此功能,例如:上篇文章Nginx的IPhash方式,均衡负载服务器利用Hash算法将同一IP分配到同一台服务器上,即Session绑定在某台特定服务器上,保证Session总能在这台服务器上获得,又称作为会话黏滞。
缺点:如果某台服务器宕机,那么这台服务器上面的Session也就不存在了,用户请求切换到其他服务器上因为没有Session而出错。
3、利用Cookie记录Session
通过Cookie记录Session信息是大部分网站采用的方法,这种方式只要Cookie不滥用,也是非常好非常成熟的方案。Cookie记录Session就是把一些状态信息放到了客户端,每次请求都要传输到服务器。
优势:这种方法简单易实现,可用性高,支持服务器横向拓展,方案成熟
缺点:安全性问题,Cookie有大小限制,而且每次请求传输Cookie会影响性能
4、Session服务器
Session服务器的方式管理Session,是一种非常好的解决方案,因为Session是为了业务需要Http状态而产生,而分布式网站设计中提倡Http无状态,为了满足这一设计,Session服务器是将有状态的Session信息与无状态的应用服务器相分离,再针对不同服务器的不同特性进行设计。例如:我们将Session信息存入到Redis中,那么Redis的集群配置、稳定性设置都有很多好的解决方案,如果将Session存入到Memcache,那么Memcache的集群配置、稳定性设置也会有很多成熟案例。这样我们就将一些问题简单化,如果我们单独应用.Net的Session,我们需要了解更多.Net深层次的东西并加以改造来保证其可用和稳定,越深层的东西越需要时间和阅历,而如果将Session存储介质转移到Redis中,Redis集群方案、管理工具都非常成熟,只需要配置配置就解决了Session的问题,何乐而不为呢。
优势:可用性高、安全性高、伸缩性好、性能高、信息大小无限制
三、.Net Core+Redis+Nginx实现Session分布式共享
1、前期准备&环境
(1)Vs2017 (2).Net Core 1.1 (3) Win 7 (4)ubuntu 16.04
2、.Net Core简介
随着互联网的发展,在当今中国市场(外国不大清楚)开源、跨平台是衡量一门语言、技术好坏的重要指标之一,微软为了推动.Net开源及跨平台,.Net Core随之诞生。
详见大牛的文章:.NET Core与.NET Framework、Mono之间的关系
下面说说.Net Core给我的初步的感受:
1).Net Core并没有颠覆之前C#语法
通俗讲就是之前说中国话(C#),现在还是说中国话,只是说话的环境变了。
2).Net Core因为刚起步,API变了或者少了很多
通俗讲就是说话环境变了,而且里面有好多你没见过的东西,你不知道用什么官方词语来描述,因为官方正在找相关词来描述这些新东西。
3)脱离IIS,跨平台
通俗讲就是微软老妈为了不让我们到了新环境饿着,怕离开现在这个环境(Windows+IIS)之后不知道怎么生存。于是,教会了我们语言(C#),给了我们挣钱的工具(.Net Core+Kestrel),说了一句“去吧孩子,自己奋斗去吧,稍等,别忘了把这张Visa卡带上(.Net Core SDK),我会定期给你打钱的。”
4)NuGet越来越重要
NuGet经过几年的发展,越来越成熟,.Net Core开源组件获取的主要方法,通过NuGet可以下载各种中间件和组件,而且方便快捷(除了有时候断网,但是可以使用国内镜像),NuGet就像微软老妈给咱们的一个通讯录,并告诉咱们,如果你在某些方面需要帮助的时候,可以通过NuGet找到你的七大姑八大姨来帮忙。
3、拓扑图
根据之前文章中成功的经验,简单改造一下,中间一个Windows系统和一个Ubuntu系统承载着.Net Core程序,有人会问Windows那个咋不来个IIS啊,我要说的是.Net Core实行走出去的原则,基本脱离IIS,如果IIS上面想部署.Net Core程序的话,需要安装同样的应用程序,并且站点配置的应用程序池也要变成“无托管代码”。
4、开发.Net Core程序使用Session
4-1、创建一个Web程序
用Vs2017创建一个.Net Core的Web应用程序,且这个应用程序不包含身份验证信息
创建完如下
4-2、.Net Core调用Session
.Net Core使用Session,需要引用相关Session的NuGet包,网上一查,发现.Net Core的官方Session组件类似一个中间件,并且官方支持Redis。
注意:.Net Core的Mvc不能直接使用Session,如果你在程序里面写了个HttpContext.Session就会出现如下错误:Session has not been configured for this application or request.
4-2-1、Microsoft.AspNetCore.Session
.Net Core使用Session必须安装Microsoft.AspNetCore.Session,他的NuGet包安装如下图:
4-2-2、修改Startup.cs让Session可用
在相应位置加入高亮代码services.AddSession(); app.UseSession();
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); services.AddSession(); }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseSession(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
4-2-3、Session写入和读取
Session的读取方式,与.Net有所不同,写法如下,并且Session的HttpContext.Session.SetString或者HttpContext.Session.Set方法分别支持字符串和Byte数组,所以复杂实体需要转化成Json存入Session中。
【Session 写入方法】
HttpContext.Session.SetString("key", "strValue");
【Session 读取方法】
HttpContext.Session.GetString("key")
5、Session存储介质更换为Redis
5-1、首先配置Redis
详细配置方式见:Session分布式共享 = Session + Redis + Nginx
redis-server redis.windows.conf
详细配置方式见:Session分布式共享 = Session + Redis + Nginx
5-2、安装Microsoft.Extensions.Caching.Redis.Core
NuGet中搜索Microsoft.Extensions.Caching.Redis.Core并安装,此NuGet包是对Caching的拓展,即可以更换Caching存储介质
5-3、appsettings.json配置Redis连接字符串
appsettings.json配置Redis连接字符串(相当于web.config里面配置appsetting节点),注意:添加位置要在Logging上面,否则读不到,添加代码为下面的高亮部分
{
"Data": "RedisConnection",
"ConnectionStrings": {
"RedisConnection": "192.168.8.138:6379"
},
"Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } } }
5-4、Startup.cs的ConfigureServices方法中添加引用
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); services.AddDistributedRedisCache(option =>//redis 数据库连接字符串
{
option.Configuration = Configuration.GetConnectionString("RedisConnection");
//redis 实例名
option.InstanceName = "master";
});
services.AddSession();
}
页面运行HttpContext.Session.GetString("key"),然后用Redis管理工具RedisDesktopManager查询Session是否入库。
5-5、发布前指定IP和端口(重要)
如果你没有看这个步骤,继续下面发布步骤,等你发布时候,你会发现一个尴尬的问题,就是你用IP访问不了你的网站,用localhost可以访问,.Net Core默认是5000端口,端口占用也会让你的网站访问不了。
只需要在Program.cs中添加高亮代码即可,细心地人已经看到.UseUrls(new string[] { }) 传入的是个数组,那么这里定义多个网站,当你执行时候dotnet命令时候,多个网站都会启动。
public static void Main(string[] args) { var host = new WebHostBuilder()//增加处,*号表示ip
.UseUrls(new string[] { "http://*:7201" })
.UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .UseApplicationInsights() .Build(); host.Run(); }
6、.Net Core 发布
6-1、Windows安装.Net Core发布环境[10.2.107.100]
1)安装Windows Server Hosting (x64 & x86),相当于IIS,注意安装时候请联网(好像是自动下载sdk,具体没仔细研究)。
2)输入dotnet命令验证,如果“报’dotnet’不是内部或者外部命令”请找到“C:\Program Files\dotnet”文件夹中的dotnet.exe,用cmd来调用dotnet.exe来运行,或者添加系统环境变量(window中cmd命令可以节省在编写命令时候可以.exe,即命令dotnet就是dotnet.exe)
【坑1】
在win7下提示一下错误:Failed to load the dll from [C:\Program Files\dotnet\host\fxr\1.0.1\hostfxr.dll], HRESULT: 0x80070057
解决方法:
需要安装补丁:KB2533623
下载地址如下:
https://support.microsoft.com/en-us/kb/2533623
【坑2】
注意.net Core版本,本文主要是用的.net Core 1.1.1开发的,下面两个截图是版本按错了出的错误信息
6-2、Ubuntu安装.Net Core发布环境[10.2.107.46]
Ubuntu安装.Net Core官方写的很详细了,照着做即可,千万别抵触Linux系统,抵触的话那就别用.Net Core了,如果不知道Ubuntu和Linux的关系的话请百度。
最后验证dotnet命令是否可以使用。
6-3、发布网站
在项目上右键->发布…
点击发布按钮,生成的文件如下(SessionTest为应用程序名)
好了,有了这些文件,我们只需要把这些文件扔到服务器上就成了,但是怎么启动呢?通过查询,网上说只要用dotnet命令就成。继续实践…
说明:我的项目叫做生成了这个为主要的dll,也是程序的入口。
大家都知道.Net Core是跨平台的,不同系统的服务器环境配置好了,网上查询说是使用dotnet命令启动网站,那么可以推断出几个平台的dotnet命令是一样的。
6-3-1、Windows启动.Net Core网站[10.2.107.100:7201]
启动.Net Core网站的命令很简单,安装好发布环境的应用程序,C:\Program Files\dotnet目录如下(如果dotnet命令不能用,可以直接调用dotnet.exe这个应用程序。)
将生成好的网站复制到服务器上
cmd命令找到PublishOutput
cd C:\PublishOutput
dotnet运行网站命令
成功以后(之后再编译运行,会提示下面截图)
访问http://10.2.107.100:7201/(如果一台机子有多个网卡多个IP,其他IP的7201端口也是个独立网站)
6-3-2、Ubuntu启动.Net Core网站[10.2.107.46:7201]
想办法将发布的程序复制到Ubuntu上面去,我测试使用的VBox虚拟机。
具体方法传送门:virtualbox中ubuntu和windows共享文件夹设置
7、Nginx配置
7-1、网站端口修改
nginx.conf配置修改
listen 80; 改成 listen 81; 因为一般都被80都被使用。
server { listen 81; ……
}
7-2、增加负载均衡
nginx.conf中添加upstream节点
server 10.2.107.100:7201;
server 10.2.107.46:7201;
}
server {
.....
}
7-3、location节点修改
location / { root html; index index.aspx index.html index.htm; #其中jq_one 对应着upstream设置的集群名称 proxy_pass http://Jq_one; #设置主机头和客户端真实地址,以便服务器获取客户端真实IP proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
7-4、Nginx启动命令
C:\server\nginx-1.0.2>start nginx
或
C:\server\nginx-1.0.2>nginx.exe
7-5、Nginx重新载入命令
C:\server\nginx-1.0.2>nginx.exe -s reload
四、黎明前的黑暗-MachineKey
本以为做了上述准备和相关代码编写,就能够实现Session共享了,结果我想的太简单了,应用程序发布后并不能实现Session共享,难道分布式共享下Session需要特殊处理?.Net我是怎么实现的,它们的方法应该方法类似。我突然想到了MachineKey这个东西,之前在.Net版本分布式共享时候需要添加这个东西,评论也有人问我什么要加MachineKey。后来只能搜索.Net Core Machinekey关键词,找到了以下几篇文章做参考。
搭建分布式 ASP.NET Core Web
ASP.NET Core 数据保护(Data Protection)
坎坷路:ASP.NET Core 1.0 Identity 身份验证(中集)
此问题属于数据安全问题,微软在开发.Net Core中延续了之前的设计,采用数据保护(Data Protection)方式对一些内部数据进行加密解密设计,如:Session、Cookie等(远不止这些)。这样可以保证数据的真实性、完整性、机密性、隔离性。数据安全必然离不开加解密算法,大家想一下之前.Net的WebFrom中的ViewState,它最终解析到Html页面是个hidden标签里面有一串很复杂的字符串,这个字符串是被数据保护(Data Protection)机制加密过的。Session也一样,大家可以看看Session存到Redis中啥样,见下图:
数据保护(Data Protection)有个特性是隔离性,大家可以想象一下,数据保护核心是加密解密,常见的加密方式有对称加密和非对称加密,上一篇做分布式共享时候,两台机子拷贝了同样的MahcineKey,那么他的内部加密猜测好像是对称加密,MachineKey直译中文为“机器钥匙”在联想隔离性,那么可以推断出来不同机子密钥是不同的,那么MachineKey的作用是统一不同机子的密钥。(吐血中…….这个只是个猜测,详细原理请参考专业文章)
1、提取.Net Core的MachineKey
.Net Core的MachineKey存储是以key-xxxx-xxxx-xxxx-xxxx.xml的形式存储的,那如何提取这个xml信息呢?
Startup.cs的ConfigureServices添加下图高亮代码
public void ConfigureServices(IServiceCollection services) {//抽取key-xxxxx.xml
services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(@"D:\XML"));
services.AddSession(); services.AddDistributedRedisCache(option => { //redis 数据库连接字符串 option.Configuration = Configuration.GetConnectionString("RedisConnection"); //redis 实例名 option.InstanceName = "master"; }); services.AddMvc(); }
查看D:\Xml里的xml文件
2、重写IXmlRepository接口固定Key
在项目中添加CustomXmlRepository.cs类,其中keyContent中填写key.xml内容,注意:里面的几个时间(现在还不能确定expirationDate对项目是否有影响),有人问我KeyContent能否从文件里读,回答是可以,但是ubuntu的文件路径保准不是Windows的d:\之类的,需要使用Linux的写法,所以干脆字符串来的快。
using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; namespace SessionTest { public class CustomXmlRepository : IXmlRepository { private readonly string keyContent = @"<?xml version='1.0' encoding='utf-8'?> <key id='9108538d-9ea4-45fb-a690-438c8d788619' version='1'> <creationDate>2017-04-27T06:15:07.2194692Z</creationDate> <activationDate>2017-04-27T06:15:07.1844647Z</activationDate> <expirationDate>2017-07-26T06:15:07.1844647Z</expirationDate> <descriptor deserializerType='Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'> <descriptor> <encryption algorithm='AES_256_CBC' /> <validation algorithm='HMACSHA256' /> <masterKey p4:requiresEncryption='true' xmlns:p4='http://schemas.asp.net/2015/03/dataProtection'> <!-- Warning: the key below is in an unencrypted form. --> <value>HOz58FE6STtDHlMo2ZONoPgPTOOjRPikRWXmHOwNDS5o6NPb4hlgl/DxXUhat66soovBUFy1APXCQ4z30DDPyw==</value> </masterKey> </descriptor> </descriptor> </key>"; 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) { } } }
修改Startup.cs文件中的ConfigureServices方法加载自定义的CustomXmlRepository类
public void ConfigureServices(IServiceCollection services) { ////抽取key-xxxxx.xml //services.AddDataProtection() // .PersistKeysToFileSystem(new DirectoryInfo(@"D:\XML")); services.AddSingleton<IXmlRepository, CustomXmlRepository>();services.AddDataProtection(configure =>
{ configure.ApplicationDiscriminator = "newP.Web";
}); services.AddSession(); services.AddDistributedRedisCache(option => { //redis 数据库连接字符串 option.Configuration = Configuration.GetConnectionString("RedisConnection"); //redis 实例名 option.InstanceName = "master"; }); services.AddMvc(); }
五、实现效果演示
演示效果说明
本机127.0.0.1也为10.2.107.100,因为电脑性能有限,没有弄windows虚拟机,只弄了10.2.107.46这台Linux虚拟机。
MachineKey的这个实现思路也可以用到.Net Core的身份验证上。
UNC文件也可以实现Session共享方式
原理就是Windows和Linux通过文件共享和挂载的方式Key.xml共享一个文件,但是总觉得有点怪怪的,共享文件会不会被别人恶意篡改,所以最后采用重写的方式实现。
对UNC方式感兴趣的请看:搭建分布式 ASP.NET Core Web
希望通过本文,让大家对网站的可用性中有个简单认识,并了解到Session存入Redis中的优势。
个人观点,有可能因为知识和阅历的原因,分析片面,请多谅解。
七、参考文章
ASP.NET Core 使用 Redis 和 Protobuf 进行 Session 缓存
.Net Core Session使用
Using Sessions and HttpContext in ASP.NET Core and MVC Core
.NET Core与.NET Framework、Mono之间的关系
virtualbox中ubuntu和windows共享文件夹设置
ASP.NET Core 数据保护(Data Protection)
坎坷路:ASP.NET Core 1.0 Identity 身份验证(中集)