.NET Core多实例下StackExchangeRedis会话为空问题排查
问题背景
部署在PCF(Pivotal Cloud Foundry)多实例环境的.NET Core 6项目,使用Microsoft.Extensions.Caching.StackExchangeRedis 6.0和StackExchange.Redis 2.6.66。单实例部署时Session正常,多实例下Session为空,后续出现异常:
解密会话Cookie时出错。密钥环中未找到密钥[]。
Redis中能看到两个实例的密钥,但跨实例无法读取Session,且无法配置请求固定路由到同一实例。项目从.NET Framework迁移而来,使用了包含静态IHttpContextAccessor的AppHttpContext,并执行了AppHttpContext.ContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();。
核心原因
- Data Protection密钥未共享:.NET Core默认会为每个实例本地生成Data Protection密钥环,用于加密/解密Session Cookie。多实例环境下,不同实例的密钥不一致,导致A实例加密的Cookie,B实例无法解密,进而无法获取SessionId读取Redis中的Session数据。
- Redis缓存配置不完整:未明确指定Redis连接字符串,多实例可能无法连接到同一Redis实例(或使用不同的键前缀)。
- 缺少
IHttpContextAccessor注册:Session依赖IHttpContextAccessor访问上下文,服务集合中未显式注册该服务可能导致上下文获取异常。
解决方案
1. 注册IHttpContextAccessor
在ConfigureServices中添加:
services.AddHttpContextAccessor();
2. 配置共享的Data Protection密钥环
安装Microsoft.AspNetCore.DataProtection.StackExchangeRedis包,然后配置Data Protection将密钥存储到Redis,确保所有实例共享同一密钥环:
using StackExchange.Redis; public void ConfigureServices(IServiceCollection services) { // 从配置中获取Redis连接字符串(可从PCF服务绑定中获取) var redisConnection = Configuration.GetConnectionString("Redis"); var multiplexer = ConnectionMultiplexer.Connect(redisConnection); // 配置Data Protection共享密钥 services.AddDataProtection() .SetApplicationName("YourUniqueAppName") // 所有实例必须使用相同的应用名称 .PersistKeysToStackExchangeRedis(multiplexer, "DataProtection-Keys"); // 配置Redis缓存 services.AddStackExchangeRedisCache(options => { options.Configuration = redisConnection; options.InstanceName = "YourApp-Session-"; // 可选,为Session键添加统一前缀 }); // 配置Session services.AddSession(options => { options.Cookie.Name = ".YourApp.Session"; options.Cookie.SameSite = SameSiteMode.Lax; // 根据业务需求调整,确保跨实例Cookie有效 options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; options.IdleTimeout = TimeSpan.FromMinutes(30); // 按需设置Session超时时间 }); services.AddControllers(); }
3. 确保中间件顺序正确
Configure方法中中间件顺序必须符合要求,UseSession要在UseEndpoints之前,且在UseRouting之后(如果有):
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseSession(); // 必须在UseEndpoints之前 app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); // 初始化静态HttpContextAccessor AppHttpContext.ContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>(); }
4. 验证Redis连接
确保所有PCF实例都能访问同一个Redis服务,可通过PCF的服务绑定自动注入Redis连接字符串到配置中,避免硬编码。
关键说明
SetApplicationName的参数必须在所有实例中保持一致,这是Data Protection识别同一密钥环的关键。- 密钥存储到Redis后,新启动的实例会自动从Redis获取密钥,无需手动同步。
- Session Cookie的配置需确保在多实例环境下能被正确传递,避免因Cookie属性限制导致无法跨实例读取。
内容的提问来源于stack exchange,提问作者Daisy007

