ASP.NET Core负载均衡Session共享

负载均衡,很直白的说,就是:某一个服务,由多台服务器均摊后由多台机器作为服务器对该服务做出响应。其中最为困扰就是Session共享了,处理起来特麻烦。尤其让人吐血的是,透明替换啊,简直颠覆了我对程序认识。通过一番痛苦挣扎,终于解决!于是乎,就来简单记录下吧。一共三步:使用Session->Redis存储Session->统一不同机器之间的MachineKey。不同于缓存的是,Session记住需要保证是当前会话的一个状态,而缓存就相对比较简单了,如我需要一个key值,那我直接缓存服务器读取ok,对吧。假设这样一个场景:在成功使用负载均衡的情况下,用户的第一个操作,是服务器A响应,在未登录的情况下进行登录并成功,可就在登录成功后,由于负载均衡,接下来的操作,由服务器B响应,可是服务器BSession服务器ASession是独立在两台物理机的。这时候用户在已经登录过的情况下,还得重新登录一下来保持状态,是不是很烦,这种糟糕的情况,是不是程序中严禁允许的,对吧。这是不是比单纯的缓存要复杂的多,原理图大致如下:

在这里插入图片描述

在上述描述的情景中,如果我们能实现Session共享,即:Session透明替换,缓存的数据,被存储到第三方库,在使用Session的时候,其实调用的就是用来存储SessionRedis,这样的话问题不就解决了,对吧。那么我们首先就来使用Session吧。
NuGet : Microsoft.AspNetCore.Session

在这里插入图片描述

Startup.csConfigureServices方法中进行注入,并在Configure方法中,添加使用Session的标识。代码如下:

  
 public class Startup
    {
    
    
        public Startup(IConfiguration configuration)
        {
    
    
            Configuration = configuration;
        }

        public IConfiguration Configuration {
    
     get; }

        /// <summary>
        /// 此方法由运行时调用,使用此方法可将服务添加到容器中
        /// </summary>
        public void ConfigureServices(IServiceCollection services)
        {
    
    
            //注入Session
            services.AddSession(options =>
            {
    
    
                options.IdleTimeout = TimeSpan.FromMinutes(30);
            });

            //注入自启动
            services.AddControllersWithViews().AddRazorRuntimeCompilation();
        }

        /// <summary>
        /// 此方法由运行时调用,使用此方法配置HTTP请求管道
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
    
    
            if (env.IsDevelopment())
            {
    
    
                app.UseDeveloperExceptionPage();                
            }
            else
            {
    
    
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseRouting();

            //session
            app.UseSession();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
    
    
                endpoints.MapControllerRoute(
                    name:"WX",
                    pattern:"{area:exists}/{controller=WeChat}/{action=GetValid}/{id?}");
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
            
        }
    }

这时在HomeController.cs文件中就可以使用Session了。示例如下:

public class HomeController : Controller
    {
    
    
       public IActionResult Index()
        {
    
    
            HttpContext.Session.SetString("key","key")
            var json=new 
            {
    
    
                 key=HttpContext.Session.GetString("key")
            };
            return Json(json);
        }
    }


使用Redis透明存储Session,至于如何搭建Redis参见我的上篇文章Redis汇总NuGet :
1、Microsoft.Extensions.Caching.StackExchangeRedis
2、Microsoft.AspNetCore.DataProtection.StackExchangeRedis

在这里插入图片描述

再去Startup.csConfigureServices方法中注入一下Redis即可实现。这里我是用的Redis集群,代码如下:

public void ConfigureServices(IServiceCollection services)
        {
    
    
            //注入Session
            services.AddSession(options =>
            {
    
    
                options.IdleTimeout = TimeSpan.FromMinutes(30);
            });
           //使用Redis作为缓存,存储Session等
            var redis_Con = "42.193.5.237:6379,password=123@456,defaultdatabase=0,8.135.26.156:6379,password=123@456,defaultdatabase=0,154.8.183.144:6379,password=123@456,defaultdatabase=0,39.107.65.223:6379,password=123@456,defaultdatabase=0,183.60.104.198:6379,password=123@456,defaultdatabase=0,124.71.148.52:6379,password=123@456,defaultdatabase=0";            
            services.AddStackExchangeRedisCache(option =>
            {
    
    
                option.Configuration = redis_Con;
            });
            //注入自启动
            services.AddControllersWithViews().AddRazorRuntimeCompilation();
        }

这时重新启动项目,运行时我们发现Redis已经存储了我们想要的数据。这个过程是透明的,所以不好理解,就算看到了数据,直观上也感受不到有什么直接联系,但是此时的Session确确实实已经在Redis进行了数据加密存储。

在这里插入图片描述

虽然上述操作已经完整实现了Redis存储Sesssion,实质上也就是说现在Redis已经透明替换掉Session了,记得我在JavaSpringBoot中,实现到这一步,已经可以实现Session共享了,但在.NET Core还远未实现啊,这到底是怎么回事。通过查阅资料,发现是不同机器之间的 MachineKey导致的,于是通过一番痛苦的折腾,终于发现了一个第三方包来解决这个问题。NuGet : Microsoft.AspNetCore.DataProtection.StackExchangeRedis

在这里插入图片描述

继续在Startup.csConfigureServices方法中添加如下代码,配置不同机器之间使用MachineKey的统一标识即可:

//为不同机器的作MachineKey的统一标识
var redis = ConnectionMultiplexer.Connect(redis_Con);
services.AddDataProtection()
                .SetApplicationName("session_application_name")
                .PersistKeysToStackExchangeRedis(redis, "SNK-DataProtection-Keys");

再来改造下HomeController.csIndex方法,注意这里的Machine是一个类就两个属性ip,key,自行创建这个类不难吧。ip分别用于部署在两台服务器的标识,key使用从Session读取来验证是Session共享的内容。39.107.65.223服务器代码:

public IActionResult Index()
        {
    
    
            var key = HttpContext.Session.Get("key");
            Machine m = new Machine()
            {
    
    
                ip = "39.107.65.223",
                key = "key"
            };
            if (key == null)
            {
    
    
                HttpContext.Session.Set("key", Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(m)));
            }           
            byte[] by = HttpContext.Session.Get("key");
            string str = Encoding.UTF8.GetString(by);
            return Json(JsonConvert.DeserializeObject<Machine>(str));
        }

打包发布完后,另一台服务器154.8.183.144代码如下,其余部分完全一样:

public IActionResult Index()
        {
    
              
            var key = HttpContext.Session.GetString("key");
            if (key == null)
            {
    
    
                HttpContext.Session.SetString("key", "154.8.183.144设置的key");
            }
            Machine m = new Machine()
            {
    
    
                ip = "154.8.183.144",
                key = HttpContext.Session.GetString("key")
            };
            byte[] by = HttpContext.Session.Get("key");
            string str = Encoding.UTF8.GetString(by);
            return Json(JsonConvert.DeserializeObject<Machine>(str));
        }

接下来在第三台服务器42.193.5.237反下载nginx,官方下载连接,作为负载均衡反向代理服务器。

在这里插入图片描述

打开nginx.conf添加如下内容来配置负载均衡:

upstream servers1{
    
    
     server 39.107.65.223 weight=1;
     server 154.8.183.144 weight=1;
}

   
 server {
    
      
        listen       81;  
        server_name  _;  
        location / {
    
      
                #反向代理地址 
                proxy_pass http://servers1;      
                #设置主机头和客户端真实地址,以便服务器获取客户端真实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;

        }  
     } 

响应结果:ip是用来标识进行响应的服务器,可以直观的感受到,在同一个会话中,分别由不同的服务器进行响应,但使用的Session 数据却是共享了。到此,大功告成!

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_42799562/article/details/112676990
今日推荐