• net core+Redis+IIS+nginx实现Session共享


    .Net Core Web Api实践(二).net core+Redis+IIS+nginx实现Session共享

     

    前言:虽说公司app后端使用的是.net core+Redis+docker+k8s部署的,但是微信公众号后端使用的是IIS部署的,虽说公众号并发量不大,但领导还是使用了负载均衡,所以在介绍docker+k8s实现分布式Session共享之前,就先介绍一下IIS+nginx实现Session共享的方案,两者其实区别不大,所以这篇着重介绍方案,下篇介绍测试的区别以及填坑的方式。

    1、环境准备

    操作系统:Windows10

    IIS:需要安装模块

    VS2019、本地Redis数据库、ngnix(windows版)

    2、Session共享的简易说明

    下图简要说明了负载均衡通过轮询方式,将同一个客户端请求发送到不同的站点下,操作的Session应该是同一个。

    3、添加测试项目

    虽然个人认为本来WebApi中使用Session本身就是一种不合理的设计,但这是旧项目迁移需要保留的历史逻辑,所以只能硬着头皮寻找对应的解决方案了。

    在VS2019中添加一个.net core 的WebApi项目,使用Session的话需要添加以下配置。

    Startup.cs类中,ConfigureServices方法添加services.AddSession();  Configure方法中添加app.UseSession();  注意要放到UseMVC方法前面。

    测试代码如下,添加testController类,在Get1方法中设置Session,记录当前时间,Get2方法中读取Session

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    [Route("[action]")]
        [ApiController]
        public class testController : ControllerBase
        {
            // GET: api/test
            [HttpGet]
            public IEnumerable<string> Get()
            {           
                return new string[] { "value1""value2", HttpContext.Connection.LocalIpAddress.ToString(), HttpContext.Connection.LocalPort.ToString()};
            }
     
            // GET: api/test/5
            [HttpGet]
            public string Get1(int temp1)
            {
                if (string.IsNullOrEmpty(HttpContext.Session.GetString("qqq")))
                {
                    HttpContext.Session.SetString("qqq", DateTime.Now.ToString());
                }
                return HttpContext.Connection.LocalIpAddress.ToString() + "|" + HttpContext.Connection.LocalPort.ToString();
            }
     
            [HttpGet]
            public string Get2(int temp1, int temp2)
            {           
                return HttpContext.Connection.LocalIpAddress.ToString() + "|" + HttpContext.Connection.LocalPort.ToString() + "|" + HttpContext.Session.GetString("qqq");
            }
        }

    4、发布.net core项目到IIS  

    (1)右键项目点击发布

     (2)记录下发布路径,并在IIS中新增两个站点,指向该路径,并设置不同的端口号

     

    记得把应用程序池中改为无托管代码

     

    5、nginx配置负载均衡

    下载地址:http://nginx.org/

    配置方式:

    (1)找到nginx的安装路径,打开nginx.conf文件

    (2)添加upstream配置,配置用于负载均衡轮询的站点,即上一步骤中添加的两个站点

    (3)配置location节点,注意proxy_pass与upstream中配置的名称保持一致。

    (4)启动ngnix,用cmd命令指定nginx的安装目录,然后start nginx

     

    6、在没有做Session共享方案的情况下进行测试

    浏览器分别输入http://localhost:7665/Get1与http://localhost:7665/Get2,由于ip是一样的,所以没有参考必要,不停刷新http://localhost:7665/Get1,最后看到的端口号在7666与7667之间不停的来回切换,说明nginx的轮询是成功的。当然这里只是为了实现session共享做的负载均衡,所以把负载均衡放在了同一台服务器上进行配置,感兴趣的同学可以使用不同服务器配置负载均衡,并用压力测试工具测试站点在配置负载均衡的吞吐能力,后面有机会我可以单独介绍这部分内容。

     

    测试结果:

    1、过程:  请求http://localhost:7665/Get1,请求分发到站点7667,设置了Session;

    请求http://localhost:7665/Get2,请求分发到站点7666,读取不到该Session;

    再次请求Get2,请求分发到站点7667,可以读取到Session。

    结论:说明负载均衡的两个站点之间不会读取同一个Session,也就是说Session不会共享。

    2、 过程: 再次请求Get1,请求分发到站点7666

    再次请求Get2,请求分发到站点7667,读取不到该Session;

    再次请求Get2,请求分发到站点7666,可以读取到Session,且session值被刷新。

    结论:因为nginx每次都将请求分发到另外一站点,且session没有共享,所以string.IsNullOrEmpty(HttpContext.Session.GetString("qqq"))总是true,然后session都会被刷新,另一个站点的session就丢失了(这里的丢失应该是刷新session后产生了新的cookie值,导致原来的session无法读取,感兴趣的同学还可以用Fiddler跟踪记录下Get1产生cookie值,然后在Get2请求时带上cookie进行验证)。

    7、使用Redis将Session存放在Redis服务器

    (1)在Startup.cs文件中,ConfigureServices方法加入以下代码

    复制代码
    services.Configure<CookiePolicyOptions>(options =>
                {
                    // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                    options.CheckConsentNeeded = context => false; //这里要改为false,默认是true,true的时候session无效
                    options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None;
                });
                
                services.AddDataProtection(configure =>
                {
                    configure.ApplicationDiscriminator = "wxweb";
                })
                .SetApplicationName("wxweb")
                .AddKeyManagementOptions(options =>
                {
                    //配置自定义XmlRepository
                    options.XmlRepository = new SessionShare();
                });
    
                #region 使用Redis保存Session
                // 这里取连接字符串 
                services.AddDistributedRedisCache(option =>
                {
                    //redis 连接字符串
                    option.Configuration = Configuration.GetValue<string>("RedisConnectionStrings");
                    //redis 实例名
                    option.InstanceName = "Wx_Session";
                });
    
                //添加session 设置过期时长分钟  
                //var sessionOutTime = con.ConnectionConfig.ConnectionRedis.SessionTimeOut;
                services.AddSession(options =>
                {
                    options.IdleTimeout = TimeSpan.FromSeconds(Convert.ToDouble(8 * 60 * 60)); //session活期时间
                    options.Cookie.HttpOnly = true;//设为httponly
                });
                #endregion
    复制代码

    简要说明:

    services.Configure<CookiePolicyOptions>是为了可以使用cookie

    SetApplicationName("wxweb")是为了保证不同站点下的应用程序名称是一致的。

    options.XmlRepository = new SessionShare();是为了保证不同站点下应用程序使用的machinekey是一样的,详情见https://www.cnblogs.com/newP/p/6518918.html

    AddDistributedRedisCache是一个官方的拓展组件,用户将session保存在redis中。

    RedisConnectionStrings是Redis连接字符串

    (2)SessionShare的实现如下

    复制代码
    public class SessionShare : IXmlRepository
        {
            private readonly string keyContent =
    @"自己的machinekey";
    
            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)
            {
            }
        }
    复制代码

    (3)再次进行发布

    8、添加Session共享方案以后进行测试

    测试结果:无论Get1刷新多少次,Get2都能拿到Session值,且Session没有被刷新(当前时间没有变化),即Session共享成功。

    总结:以前总是看别人的文章了解Session共享,这次自己从配置负载均衡到解决Session共享从头到尾走了一遍,所以记录了下来。下篇文章我会介绍AddDistributedRedisCached在并发量较高时timeout的解决方案。

    参考文章(同时感谢这些大佬的文章提供的帮助)

    1、【nginx】配置Nginx实现负载均衡

    2、Session分布式共享 = Session + Redis + Nginx

  • 相关阅读:
    [POI2014]KUR-Couriers
    [题解向] Luogu4092 [HEOI2016/TJOI2016]树
    [探究] OI中各种初级数论算法相关
    [SCOI2005]骑士精神
    [intoj#7]最短距离
    数列分块入门
    动态规划问题基础
    Luogu P1967 货车运输
    Luogu P3379 【模板】最近公共祖先(LCA)
    Luogu P3378 【模板】堆
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/12377760.html
Copyright © 2020-2023  润新知