1、前言&回顾html
在上篇文章Session分布式共享 = Session + Redis + Nginx中,好多同窗留言问了我好多问题,其中印象深入的有:nginx挂了怎么办?采用Redis的Session方案与微软Session方案相比,有什么优点呢?Cookie也能够取代Session的,采用Redis的Session方案优点在哪里?Nginx的iphash方式究竟是什么?MachineKey有啥用?Net Core怎样实现?linux
那会儿看到你们的提问,个人回答也只是从应用层面回答,基本上的回答能够总结为:“别人这么作了,解决了这个问题,我用这个方法也解决了这个问题,原理请看连接。”很惭愧的说,那时的我并无彻底理解他真正的优点在哪里,只是凭着直觉和经验知道这样作比较好,知道当一部分东西不可控时候,将其解耦、可视化、集群就可让一个系统更加健壮,但没有一个理论支撑。通过最近一段时间的查阅资料和阅读书籍,对此有了深入理解,本文将从网站架构的可用性角度对这种Session共享进行分析和讲解,并用.net core再次实现这种架构模式。(Session分布式共享的net core版,由于工做没有机会应用到生产环境,过往经验就更别提了,因此只是研究性的,请你们注意,但园子里早有大牛写出了相关文章,本文结束会将相关文章贴出)nginx
2、网站可用性--Session管理web
可用性是网站架构中很是重要的一环,什么是可用性,说的简单些,就是用户随时随地打开这个网站,这个网站都能打开,而且里面的功能都能用。若是可用性不高会出现什么状况?你们想象一下春节在12306抢票的情景,网站各类崩溃,你们保准会想:要是有别的方式能买到票,我才不用12306这个破网站呢。这个例子有点极端,由于业务场景比较极端,固然,这种现象也不光是网站可用性这一环出了问题。可是一个网站三天两头打不开,要么是点开了里面的页面处处是报错页面和操做无反应,你还会用这个网站么?我相信咱们在浏览网站时候,只要不像12306这种垄断业务的网站,出现不可用的状况,咱们必定会离开寻找其余相似的网站。redis
Session管理是网站可用性的内容之一,你们都知道Http是无状态请求,即没法追踪上次Http请求的相关信息,可是业务中大量须要将Http变为有状态请求,Session就随之产生了,但是在分布式网站设计中,无状态请求才能实现网站的横向拓展(增减应用服务器),所以又与Session相矛盾,由于Session信息若是存储在网站应用服务器的缓存中,加台服务器就不能用了,所以将Session解耦是解决此问题的关键,下面介绍网站常见的Session管理手段。算法
一、Session复制数据库
Session复制是最先企业应用系统使用较多的一种服务集群Session管理机制,开启Session复制功能,便是在集群中的几台服务器之间同步Session对象,Java中好像JBoss有这个功能,.Net暂不知道。json
优点:Session信息读取快,实现简单。ubuntu
缺点:集群规模较大时,服务器之间Session复制会占用服务器资源和网络资源,最后系统会不堪重负。windows
二、Session绑定
Session绑定的方式,通常软/硬均衡负载服务器都会提供此功能,例如:上篇文章Nginx的IPhash方式,均衡负载服务器利用Hash算法将同一IP分配到同一台服务器上,即Session绑定在某台特定服务器上,保证Session总能在这台服务器上得到,又称做为会话黏滞。
缺点:若是某台服务器宕机,那么这台服务器上面的Session也就不存在了,用户请求切换到其余服务器上由于没有Session而出错。
三、利用Cookie记录Session
经过Cookie记录Session信息是大部分网站采用的方法,这种方式只要Cookie不滥用,也是很是好很是成熟的方案。Cookie记录Session就是把一些状态信息放到了客户端,每次请求都要传输到服务器。
优点:这种方法简单易实现,可用性高,支持服务器横向拓展,方案成熟
缺点:安全性问题,Cookie有大小限制,并且每次请求传输Cookie会影响性能
四、Session服务器
Session服务器的方式管理Session,是一种很是好的解决方案,由于Session是为了业务须要Http状态而产生,而分布式网站设计中提倡Http无状态,为了知足这一设计,Session服务器是将有状态的Session信息与无状态的应用服务器相分离,再针对不一样服务器的不一样特性进行设计。例如:咱们将Session信息存入到Redis中,那么Redis的集群配置、稳定性设置都有不少好的解决方案,若是将Session存入到Memcache,那么Memcache的集群配置、稳定性设置也会有不少成熟案例。这样咱们就将一些问题简单化,若是咱们单独应用.Net的Session,咱们须要了解更多.Net深层次的东西并加以改造来保证其可用和稳定,越深层的东西越须要时间和阅历,而若是将Session存储介质转移到Redis中,Redis集群方案、管理工具都很是成熟,只须要配置配置就解决了Session的问题,何乐而不为呢。
优点:可用性高、安全性高、伸缩性好、性能高、信息大小无限制
3、.Net Core+Redis+Nginx实现Session分布式共享
一、前期准备&环境
(1)Vs2017 (2).Net Core 1.1 (3) Win 7 (4)ubuntu 16.04
二、.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找到你的七大姑八大姨来帮忙。
三、拓扑图
根据以前文章中成功的经验,简单改造一下,中间一个Windows系统和一个Ubuntu系统承载着.Net Core程序,有人会问Windows那个咋不来个IIS啊,我要说的是.Net Core实行走出去的原则,基本脱离IIS,若是IIS上面想部署.Net Core程序的话,须要安装一样的应用程序,而且站点配置的应用程序池也要变成“无托管代码”。
四、开发.Net Core程序使用Session
4-一、建立一个Web程序
用Vs2017建立一个.Net Core的Web应用程序,且这个应用程序不包含身份验证信息
建立完以下
4-二、.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-一、Microsoft.AspNetCore.Session
.Net Core使用Session必须安装Microsoft.AspNetCore.Session,他的NuGet包安装以下图:
4-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-三、Session写入和读取
Session的读取方式,与.Net有所不一样,写法以下,而且Session的HttpContext.Session.SetString或者HttpContext.Session.Set方法分别支持字符串和Byte数组,因此复杂实体须要转化成Json存入Session中。
【Session 写入方法】
HttpContext.Session.SetString("key", "strValue");
【Session 读取方法】
HttpContext.Session.GetString("key")
五、Session存储介质更换为Redis
5-一、首先配置Redis
详细配置方式见:Session分布式共享 = Session + Redis + Nginx
redis-server redis.windows.conf
详细配置方式见:Session分布式共享 = Session + Redis + Nginx
5-二、安装Microsoft.Extensions.Caching.Redis.Core
NuGet中搜索Microsoft.Extensions.Caching.Redis.Core并安装,此NuGet包是对Caching的拓展,便可以更换Caching存储介质
5-三、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-四、Startup.cs的ConfigureServices方法中添加引用
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); services.AddDistributedRedisCache(option =>//redis 数据库链接字符串
{
页面运行HttpContext.Session.GetString("key"),而后用Redis管理工具RedisDesktopManager查询Session是否入库。
5-五、发布前指定IP和端口(重要)
若是你没有看这个步骤,继续下面发布步骤,等你发布时候,你会发现一个尴尬的问题,就是你用IP访问不了你的网站,用localhost能够访问,.Net Core默认是5000端口,端口占用也会让你的网站访问不了。
只须要在Program.cs中添加高亮代码便可,细心地人已经看到.UseUrls(new string[] { }) 传入的是个数组,那么这里定义多个网站,当你执行时候dotnet命令时候,多个网站都会启动。
public static void Main(string[] args) { var host = new WebHostBuilder()//增长处,*号表示ip
.UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .UseApplicationInsights() .Build(); host.Run(); }
六、.Net Core 发布
6-一、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-二、Ubuntu安装.Net Core发布环境[10.2.107.46]
Ubuntu安装.Net Core官方写的很详细了,照着作便可,千万别抵触Linux系统,抵触的话那就别用.Net Core了,若是不知道Ubuntu和Linux的关系的话请百度。
最后验证dotnet命令是否可使用。
6-三、发布网站
在项目上右键->发布…
点击发布按钮,生成的文件以下(SessionTest为应用程序名)
好了,有了这些文件,咱们只须要把这些文件扔到服务器上就成了,可是怎么启动呢?经过查询,网上说只要用dotnet命令就成。继续实践…
说明:个人项目叫作生成了
这个为主要的dll,也是程序的入口。
你们都知道.Net Core是跨平台的,不一样系统的服务器环境配置好了,网上查询说是使用dotnet命令启动网站,那么能够推断出几个平台的dotnet命令是同样的。
6-3-一、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-二、Ubuntu启动.Net Core网站[10.2.107.46:7201]
想办法将发布的程序复制到Ubuntu上面去,我测试使用的VBox虚拟机。
具体方法传送门:virtualbox中ubuntu和windows共享文件夹设置
七、Nginx配置
7-一、网站端口修改
nginx.conf配置修改
listen 80; 改为 listen 81; 由于通常都被80都被使用。
server { listen 81; ……
}
7-二、增长负载均衡
nginx.conf中添加upstream节点
}
server {
.....
}
7-三、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-四、Nginx启动命令
C:\server\nginx-1.0.2>start nginx
或
C:\server\nginx-1.0.2>nginx.exe
7-五、Nginx从新载入命令
C:\server\nginx-1.0.2>nginx.exe -s reload
4、黎明前的黑暗-MachineKey
本觉得作了上述准备和相关代码编写,就可以实现Session共享了,结果我想的太简单了,应用程序发布后并不能实现Session共享,难道分布式共享下Session须要特殊处理?.Net我是怎么实现的,它们的方法应该方法相似。我忽然想到了MachineKey这个东西,以前在.Net版本分布式共享时候须要添加这个东西,评论也有人问我什么要加MachineKey。后来只能搜索.Net Core Machinekey关键词,找到了如下几篇文章作参考。
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的做用是统一不一样机子的密钥。(吐血中…….这个只是个猜想,详细原理请参考专业文章)
一、提取.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文件
二、重写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(); }
5、实现效果演示
演示效果说明
本机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中的优点。
我的观点,有可能由于知识和阅历的缘由,分析片面,请多谅解。
7、参考文章
ASP.NET Core 使用 Redis 和 Protobuf 进行 Session 缓存
.NET Core与.NET Framework、Mono之间的关系
virtualbox中ubuntu和windows共享文件夹设置