前言: 虽说公司 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
- ?
- [Route("[action]")]
- [ApiController]
- public class testController : ControllerBase
- {
- // GET: API/test
- [HttpGet]
- public IEnumerable 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/ https://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/ http://localhost:7665/Get1 , 请求分发到站点 7667, 设置了 Session;
请求 http://localhost:7665/ http://localhost:7665/Get2 , 请求分发到站点 7666, 读取不到该 Session;
再次请求 http://localhost:7665/Get2 , 请求分发到站点 7667, 可以读取到 Session.
结论: 说明负载均衡的两个站点之间不会读取同一个 Session, 也就是说 Session 不会共享.
2, 过程: 再次请求 Get1, 请求分发到站点 7666;
再次请求 http://localhost:7665/Get2 , 请求分发到站点 7667, 读取不到该 Session;
再次请求 http://localhost:7665/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 实现负载均衡 https://www.cnblogs.com/qlqwjy/p/8536779.html
2,Session 分布式共享 = Session + Redis + Nginx https://www.cnblogs.com/newP/p/6518918.html
来源: http://www.bubuko.com/infodetail-3384473.html