Session共享(2)

一、前言&回顾


       在上篇文章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复制会占用服务器资源和网络资源,最后系统会不堪重负。

image

2、Session绑定 

     Session绑定的方式,一般软/硬均衡负载服务器都会提供此功能,例如:上篇文章Nginx的IPhash方式,均衡负载服务器利用Hash算法将同一IP分配到同一台服务器上,即Session绑定在某台特定服务器上,保证Session总能在这台服务器上获得,又称作为会话黏滞。

     缺点:如果某台服务器宕机,那么这台服务器上面的Session也就不存在了,用户请求切换到其他服务器上因为没有Session而出错。

image

 

3、利用Cookie记录Session

     通过Cookie记录Session信息是大部分网站采用的方法,这种方式只要Cookie不滥用,也是非常好非常成熟的方案。Cookie记录Session就是把一些状态信息放到了客户端,每次请求都要传输到服务器。

     优势:这种方法简单易实现,可用性高,支持服务器横向拓展,方案成熟

     缺点:安全性问题,Cookie有大小限制,而且每次请求传输Cookie会影响性能

image

 

4、Session服务器

     Session服务器的方式管理Session,是一种非常好的解决方案,因为Session是为了业务需要Http状态而产生,而分布式网站设计中提倡Http无状态,为了满足这一设计,Session服务器是将有状态的Session信息与无状态的应用服务器相分离,再针对不同服务器的不同特性进行设计。例如:我们将Session信息存入到Redis中,那么Redis的集群配置、稳定性设置都有很多好的解决方案,如果将Session存入到Memcache,那么Memcache的集群配置、稳定性设置也会有很多成熟案例。这样我们就将一些问题简单化,如果我们单独应用.Net的Session,我们需要了解更多.Net深层次的东西并加以改造来保证其可用和稳定,越深层的东西越需要时间和阅历,而如果将Session存储介质转移到Redis中,Redis集群方案、管理工具都非常成熟,只需要配置配置就解决了Session的问题,何乐而不为呢。

     优势:可用性高、安全性高、伸缩性好、性能高、信息大小无限制

image

 

三、.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、拓扑图

image

          根据之前文章中成功的经验,简单改造一下,中间一个Windows系统和一个Ubuntu系统承载着.Net Core程序,有人会问Windows那个咋不来个IIS啊,我要说的是.Net Core实行走出去的原则,基本脱离IIS,如果IIS上面想部署.Net Core程序的话,需要安装同样的应用程序,并且站点配置的应用程序池也要变成“无托管代码”。

4、开发.Net Core程序使用Session

4-1、创建一个Web程序

          用Vs2017创建一个.Net Core的Web应用程序,且这个应用程序不包含身份验证信息

image

image

         创建完如下

image

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.

image

4-2-1、Microsoft.AspNetCore.Session

         .Net Core使用Session必须安装Microsoft.AspNetCore.Session,他的NuGet包安装如下图:

image

4-2-2、修改Startup.cs让Session可用

          在相应位置加入高亮代码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存储介质

image

5-3、appsettings.json配置Redis连接字符串

       appsettings.json配置Redis连接字符串(相当于web.config里面配置appsetting节点),注意:添加位置要在Logging上面,否则读不到,添加代码为下面的高亮部分

复制代码
{

},
"Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}
复制代码

5-4、Startup.cs的ConfigureServices方法中添加引用

复制代码
public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();
=>
redis 数据库连接字符串
"RedisConnection"                   //option.InstanceName = "master"}services.AddSession();
          }
复制代码

         页面运行HttpContext.Session.GetString("key"),然后用Redis管理工具RedisDesktopManager查询Session是否入库。

4

5-5、发布前指定IP和端口(重要) 

         如果你没有看这个步骤,继续下面发布步骤,等你发布时候,你会发现一个尴尬的问题,就是你用IP访问不了你的网站,用localhost可以访问,.Net Core默认是5000端口,端口占用也会让你的网站访问不了。

         只需要在Program.cs中添加高亮代码即可,细心地人已经看到

复制代码
public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
增加处,*号表示ip
                  .UseUrls(new string[] { "http://*:7201() .UseApplicationInsights() .Build(); host.Run(); }
复制代码

6、.Net Core 发布

6-1、Windows安装.Net Core发布环境[10.2.107.100]

         1)安装Windows Server Hosting (x64 & x86),相当于IIS,注意安装时候请联网(好像是自动下载sdk,具体没仔细研究)。

image

        2)输入dotnet命令验证,如果“报’dotnet’不是内部或者外部命令”请找到“C:\Program Files\dotnet”文件夹中的dotnet.exe,用cmd来调用dotnet.exe来运行,或者添加系统环境变量(window中cmd命令可以节省在编写命令时候可以.exe,即命令dotnet就是dotnet.exe)

image

       坑1】

         在win7下提示一下错误:Failed to load the dll from [C:\Program Files\dotnet\host\fxr\1.0.1\hostfxr.dll], HRESULT: 0x80070057

image

         解决方法:

         需要安装补丁:KB2533623

        下载地址如下:

         https://support.microsoft.com/en-us/kb/2533623 

      【坑2】

         注意.net Core版本,本文主要是用的.net Core 1.1.1开发的,下面两个截图是版本按错了出的错误信息

image

6-2、Ubuntu安装.Net Core发布环境[10.2.107.46]

         Ubuntu安装.Net Core官方写的很详细了,照着做即可,千万别抵触Linux系统,抵触的话那就别用.Net Core了,如果不知道Ubuntu和Linux的关系的话请百度。

image

         最后验证dotnet命令是否可以使用。

image

6-3、发布网站

       在项目上右键->发布…

image

image

       点击发布按钮,生成的文件如下(SessionTest为应用程序名)

image

        好了,有了这些文件,我们只需要把这些文件扔到服务器上就成了,但是怎么启动呢?通过查询,网上说只要用dotnet命令就成。继续实践…

        说明:我的项目叫做image生成了image这个为主要的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这个应用程序。)

image

         将生成好的网站复制到服务器上

image

 cmd命令找到PublishOutput

cd C:\PublishOutput

image

dotnet运行网站命令

 

访问http://10.2.107.100:7201/(如果一台机子有多个网卡多个IP,其他IP的7201端口也是个独立网站)

image

 

6-3-2、Ubuntu启动.Net Core网站[10.2.107.46:7201]

想办法将发布的程序复制到Ubuntu上面去,我测试使用的VBox虚拟机。

具体方法传送门:virtualbox中ubuntu和windows共享文件夹设置

 

访问http://10.2.107.46:7201/

image

7、Nginx配置

7-1、网站端口修改

        nginx.conf配置修改

image

        listen   80; 改成 listen   81; 因为一般都被80都被使用。

server {
        listen       81;
        ……
}

7-2、增加负载均衡

  nginx.conf中添加upstream节点

复制代码
10.2.107.100:720110.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>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 1.0 实现负载多服务器单点登录

      此问题属于数据安全问题,微软在开发.Net Core中延续了之前的设计,采用数据保护(Data Protection)方式对一些内部数据进行加密解密设计,如:Session、Cookie等(远不止这些)。这样可以保证数据的真实性、完整性、机密性、隔离性。数据安全必然离不开加解密算法,大家想一下之前.Net的WebFrom中的ViewState,它最终解析到Html页面是个hidden标签里面有一串很复杂的字符串,这个字符串是被数据保护(Data Protection)机制加密过的。Session也一样,大家可以看看Session存到Redis中啥样,见下图:

image

       数据保护(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
 new DirectoryInfo(@"D:\XML"));
            services.AddSession();
            services.AddDistributedRedisCache(option =>
            {
                //redis 数据库连接字符串
                option.Configuration = Configuration.GetConnectionString("RedisConnection");

                //redis 实例名
                option.InstanceName = "master";
            });
            services.AddMvc();
        }
复制代码

       查看D:\Xml里的xml文件

imageimage

 

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.AddDataProtection(configure =>
"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

 

 

5 六、后记&感悟


         希望通过本文,让大家对网站的可用性中有个简单认识,并了解到Session存入Redis中的优势。本文介绍的网站可用性内容中的冰山一角,还有许多知识需要我们去学习和积累。

        .Net Core版本的Session分布式共享,让我们对.Net Core有了初步了解,.Net Core的高性能、跨平台、开源,让许多人改变了对.Net的看法,但是.Net Core在中国市场的路还有很长要走,我认为.Net Core并不是扭转.Net语言在中国市场占有率的银弹。真正的银弹也许是我们这些天天写程序的.Neter,即使是微软大量宣传.Net Core、成功案例漫天飞,我们不去学习、不去了解新知识,我们最终会被淘汰。语言只是工具,只有通过不断学习和努力,将知识消化、吸收并最终分享给别人才会有最大的收获,我们在十字路口迷茫之时,为何不去学习新的知识和方法提升自身的经验和阅历。我经常会跟别人说,工作前几年最重要的不是知识,而是你做事的风格和为目标持之以恒的信念,俗话说“江山易改,本性难移”,如果不好的工作态度和方法变成了你的工作习惯,即使换了语言、换了工作甚至转了行,都会对你的职业发展有很大影响。好的习惯一定要坚持,有些事坚持一天可以、坚持两天可以、但是坚持三个月以上,却变成了无法完成的任务,更别提几年了,“不积跬步,无以至千里”,只有坚持每天去磨练自己才能有所成长,因为我知道我不是天才,需要后天的努力才能成长。

      “踏踏实实做人,认认真真做事”我坚信自己的努力,一定会有回报的,只是现在还没有抓住机遇。最后,向那些奋斗在一线使用.Net Core开发的人员致敬。

        以上总结是我熬的味道浓郁的心灵鸡汤01DAAF7A,可话说啥时候能改掉我工作外的拖延症啊01D99C38,这篇文章一直拖拖拖,论文一直拖拖拖,学英语拖拖拖,还有好多事要做可一直也是拖拖拖,悲剧啊01D9102F。。。突然发现鸭梨山大啊,坏习惯不好改啊!请大家引以为戒!当然别做工作狂,身体健康更重要,有时间多陪陪家里人。

 

 

 

转载自:http://www.cnblogs.com/newP/p/6689863.html

posted @ 2017-05-08 18:59  Eric-Lee  阅读(385)  评论(0编辑  收藏  举报