二、网站可用性 --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 方法中添加引用
//redis 数据库连接字符串 option.Configuration = Configuration.GetConnectionString("RedisConnection"); //redis 实例名 option.InstanceName = "master";});services.AddSession();
- public void ConfigureServices(IServiceCollection services)
- {
- // Add framework services.
- services.AddMvc(); services.AddDistributedRedisCache(option => {
}
页面运行 HttpContext.Session.GetString("key"),然后用 Redis 管理工具 RedisDesktopManager 查询 Session 是否入库。
5-5、发布前指定 IP 和端口 (重要)
如果你没有看这个步骤,继续下面发布步骤,等你发布时候,你会发现一个尴尬的问题,就是你用 IP 访问不了你的网站,用 localhost 可以访问,.Net Core 默认是 5000 端口,端口占用也会让你的网站访问不了。
只需要在 Program.cs 中添加高亮代码即可,细心地人已经看到. UseUrls(new string[] {}) 传入的是个数组,那么这里定义多个网站,当你执行时候 dotnet 命令时候,多个网站都会启动。
// 增加处,* 号表示 ip .UseUrls(new string[] {"http://*:7201" })
- public static voidMain(string[] args)
- {
- varhost =new WebHostBuilder()
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .UseIISIntegration()
- .UseStartup()
- .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 运行网站命令
dotnet SessionTest.dll成功以后(之后再编译运行,会提示下面截图)
访问 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 共享文件夹设置
dotnet SessionTest.dll访问 http://10.2.107.46:7201/
7、Nginx 配置
7-1、网站端口修改
nginx.conf 配置修改
listen 80; 改成 listen 81; 因为一般都被 80 都被使用。
- server {
- listen 81;
- ……
- }
7-2、增加负载均衡
nginx.conf 中添加 upstream 节点
upstream Jq_one {server10.2.107.100:7201;server10.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 数据保护(Data Protection)
坎坷路:ASP.NET Core 1.0 Identity 身份验证(中集)
net core 1.0 实现负载多服务器单点登录
此问题属于数据安全问题,微软在开发. 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 添加下图高亮代码
// 抽取 key-xxxxx.xml
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddDataProtection().PersistKeysToFileSystem(newDirectoryInfo(@"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 stringkeyContent =@"<?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 virtualIReadOnlyCollection GetAllElements()
- {
- return GetAllElementsCore().ToList().AsReadOnly();
- }
- privateIEnumerable GetAllElementsCore()
- {
- yield return XElement.Parse(keyContent);
- }
- public virtual voidStoreElement(XElement element,string friendlyName)
- {
- if(element ==null)
- {
- throw new ArgumentNullException(nameof(element));
- }
- StoreElementCore(element, friendlyName);
- }
- private voidStoreElementCore(XElement element,string filename)
- {
- }
- }
- }
修改 Startup.cs 文件中的 ConfigureServices 方法加载自定义的 CustomXmlRepository 类
services.AddDataProtection(configure =>
- public void ConfigureServices(IServiceCollection services)
- {
- ////抽取key-xxxxx.xml
- //services.AddDataProtection()
- // .PersistKeysToFileSystem(new DirectoryInfo(@"D:\XML"));
- services.AddSingleton<IXmlRepository, CustomXmlRepository>();
- {
- 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 中的优势。本文介绍的网站可用性内容中的冰山一角,还有许多知识需要我们去学习和积累。
.Net Core 版本的 Session 分布式共享,让我们对. Net Core 有了初步了解,.Net Core 的高性能、跨平台、开源,让许多人改变了对. Net 的看法,但是. Net Core 在中国市场的路还有很长要走,我认为. Net Core 并不是扭转. Net 语言在中国市场占有率的银弹。真正的银弹也许是我们这些天天写程序的. Neter,即使是微软大量宣传. Net Core、成功案例漫天飞,我们不去学习、不去了解新知识,我们最终会被淘汰。语言只是工具,只有通过不断学习和努力,将知识消化、吸收并最终分享给别人才会有最大的收获,我们在十字路口迷茫之时,为何不去学习新的知识和方法提升自身的经验和阅历。我经常会跟别人说,工作前几年最重要的不是知识,而是你做事的风格和为目标持之以恒的信念,俗话说 "江山易改,本性难移",如果不好的工作态度和方法变成了你的工作习惯,即使换了语言、换了工作甚至转了行,都会对你的职业发展有很大影响。好的习惯一定要坚持,有些事坚持一天可以、坚持两天可以、但是坚持三个月以上,却变成了无法完成的任务,更别提几年了,"不积跬步,无以至千里",只有坚持每天去磨练自己才能有所成长,因为我知道我不是天才,需要后天的努力才能成长。
"踏踏实实做人,认认真真做事" 我坚信自己的努力,一定会有回报的,只是现在还没有抓住机遇。最后,向那些奋斗在一线使用. Net Core 开发的人员致敬。
以上总结是我熬的味道浓郁的心灵鸡汤,可话说啥时候能改掉我工作外的拖延症啊,这篇文章一直拖拖拖,论文一直拖拖拖,学英语拖拖拖,还有好多事要做可一直也是拖拖拖,悲剧啊。。。突然发现鸭梨山大啊,坏习惯不好改啊!请大家引以为戒!当然别做工作狂,身体健康更重要,有时间多陪陪家里人。
个人观点,有可能因为知识和阅历的原因,分析片面,请多谅解。
七、参考文章
ASP.NET Core 使用 Redis 和 Protobuf 进行 Session 缓存
Asp.net Core 使用 Redis 存储 Session
.NET Core 与. NET Framework、Mono 之间的关系
virtualbox 中 ubuntu 和 windows 共享文件夹设置
搭建分布式 ASP.NET Core Web
坎坷路:ASP.NET Core 1.0 Identity 身份验证(中集)
net core 1.0 实现负载多服务器单点登录
来源: http://www.cnblogs.com/newP/p/6689863.html