ASP.NET Core 使用 StackExchangeRedis 包构建 Redis 分布式缓存:IDistributedCache 配置与实现深度解析
ASP.NET Core 使用 StackExchangeRedis 包构建 Redis 分布式缓存IDistributedCache 配置与实现深度解析【免费下载链接】aspnetcoreASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcoreMicrosoft.Extensions.Caching.StackExchangeRedis是 ASP.NET Coredotnet/aspnetcore仓库中提供的 Redis 分布式缓存实现它基于 StackExchange.Redis 客户端完整实现了IDistributedCache接口。本指南围绕仓库中的 PACKAGE.md 展开从安装注册、选项配置、底层存储模型到连接与故障恢复机制带你掌握在 ASP.NET Core 应用中接入 Redis 分布式缓存、进行多实例键隔离与精细过期策略的完整实战方案。一、该包是什么定位与源码结构Microsoft.Extensions.Caching.StackExchangeRedis提供的是Microsoft.Extensions.Caching.Distributed.IDistributedCache的一个基于 Redis 的分布式缓存实现。所谓“分布式缓存”是指缓存数据存放在独立的进程或服务此处即 Redis中供多个应用实例共享——这与内存缓存IMemoryCache不同它天然支持横向扩展与多副本一致读取。在仓库中该包的完整实现位于 src/Caching/StackExchangeRedis 目录其src子目录下源码结构如下src/Caching/StackExchangeRedis/ ├── src/ │ ├── Microsoft.Extensions.Caching.StackExchangeRedis.csproj # 包工程文件 │ ├── PACKAGE.md # 包说明文档 │ ├── RedisCache.cs # 核心实现RedisCache 类型 │ ├── RedisCache.Log.cs # 基于源生成器的日志定义 │ ├── RedisCacheImpl.cs # 内部 DI 实现支持 HybridCache 感知 │ ├── RedisCacheOptions.cs # 配置选项 │ └── StackExchangeRedisCacheServiceCollectionExtensions.cs # AddStackExchangeRedisCache 扩展方法 └── test/ # 单元测试与 Redis 测试基础设施包工程文件 Microsoft.Extensions.Caching.StackExchangeRedis.csproj 中声明其描述为 Distributed cache implementation of Microsoft.Extensions.Caching.Distributed.IDistributedCache using Redis.并以IsPackabletrue、IsShippingtrue作为正式发布包产出。二、安装与所有 NuGet 包一致安装命令为dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis从 工程文件 的TargetFrameworks可以看出该包同时面向net10.0DefaultNetCoreTargetFramework、当前 LTS 目标框架、.NET FrameworkDefaultNetFxTargetFramework以及netstandard2.0因此可以用于传统 .NET Framework 应用、现代 .NET Core/.NET 应用以及类库项目中。它的核心外部依赖仅有一个StackExchange.Redis客户端见该文件的Reference IncludeStackExchange.Redis /。三、快速开始注册与使用3.1 通过 AddStackExchangeRedisCache 完成 DI 注册在Program.cs中使用AddStackExchangeRedisCache扩展方法注册服务即可。这是 PACKAGE.md 提供的标准示例var builder WebApplication.CreateBuilder(); builder.Services.AddStackExchangeRedisCache(options { options.Configuration builder.Configuration.GetConnectionString(MyRedisConStr); options.InstanceName MyCache; }); var app builder.Build();要点说明Configuration接收的是 Redis 连接配置字符串例如localhost:6379可直接绑定appsettings.json中ConnectionStrings:MyRedisConStr配置项的值InstanceName用于为键添加前缀其核心价值是让多个应用/服务共享同一个 Redis 后端时互不干扰详见第六节该方法以扩展方法的形式位于命名空间Microsoft.Extensions.DependencyInjection下实现见 StackExchangeRedisCacheServiceCollectionExtensions.cs注册时依次完成三件事services.AddOptions(); services.Configure(setupAction); services.Add(ServiceDescriptor.SingletonIDistributedCache, RedisCacheImpl());也就是说缓存实例以单例Singleton生命周期注册到容器容器解析IDistributedCache时拿到的是RedisCacheImplRedisCache的内部子类。测试 CacheServiceExtensionsTests.cs 中AddStackExchangeRedisCache_RegistersDistributedCacheAsSingleton用例验证了这一行为而AddStackExchangeRedisCache_allows_chaining则验证了该方法支持链式调用返回同一个IServiceCollection。3.2 在业务代码中注入与读写注册完成后通过构造器注入IDistributedCache即可使用。由于IDistributedCache面向的是byte[]字符串读写需要借助Encoding.UTF8完成编解码public class WeatherService(IDistributedCache cache) { public async Taskstring? GetWeatherAsync(string city, CancellationToken token default) { var key $weather:{city}; var bytes await cache.GetAsync(key, token); return bytes is null ? null : Encoding.UTF8.GetString(bytes); } public async Task SetWeatherAsync(string city, string payload, CancellationToken token default) { var key $weather:{city}; await cache.SetAsync(key, Encoding.UTF8.GetBytes(payload), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow TimeSpan.FromMinutes(30) }, token); } }3.3 appsettings.json 中管理连接配置实践中推荐把连接串放在配置文件中保持注册代码的简洁{ ConnectionStrings: { MyRedisConStr: localhost:6379,passwordyourpassword,defaultDatabase0 } }注意ConnectionString 本身由 RedisCacheOptions.cs 中GetConfiguredOptions()方法交给 StackExchange.Redis 的ConfigurationOptions.Parse解析因此支持 StackExchange.Redis 的所有标准配置项主机、端口、密码、defaultDatabase、ssl、超时等。四、核心类型PACKAGE.md 中列举了两个主要类型它们是理解该包的门户类型职责RedisCache基于 Redis 的分布式缓存核心实现。其类注释明确说明内部使用 StackExchange.Redis 作为 Redis 客户端见 RedisCache.cs。它实现了IBufferDistributedCacheIDistributedCache的增强版本支持将数据直接写入IBufferWriterbyte并实现IDisposableRedisCacheOptions用于配置RedisCache的选项类同时实现了IOptionsRedisCacheOptions见 RedisCacheOptions.cs从已发布公共 API 清单PublicAPI.Shipped.txt可以看到RedisCache对外暴露的成员即标准分布式缓存操作Get/GetAsync、Set/SetAsync、Refresh/RefreshAsync、Remove/RemoveAsync以及构造函数RedisCache(IOptionsRedisCacheOptions)和Dispose()。五、RedisCacheOptions六大可配置项详解RedisCacheOptions.cs 是配置的入口除Configuration与InstanceName外还提供了多个高级选项官方公共 API 中公开的可写属性总结如下属性类型说明Configurationstring?连接 Redis 的配置字符串示例localhost:6379。最终会被ConfigurationOptions.Parse解析ConfigurationOptionsStackExchange.Redis.ConfigurationOptions?以强类型对象方式提供的连接配置文档注释注明“优先于Configuration”。二者同时设置时ConfigurationOptions生效InstanceNamestring?Redis 实例名用于让单个后端缓存被多个应用/服务分区共享设置后缓存键会以该值为前缀ConnectionMultiplexerFactoryFuncTaskIConnectionMultiplexer?创建ConnectionMultiplexer实例的委托工厂。适合需要完全自定义连接复用器创建过程如预配置ConfigurationOptions、连接前校验、接入自定义多路复用器的场景ProfilingSessionFuncProfilingSession?注册 StackExchange.Redis 性能分析会话工厂。设置后连接建立时会通过connection.RegisterProfiler(...)挂接见 RedisCache.cs可用于采集 Redis 命令级耗时指标UseForceReconnect内部属性bool无法直接通过对象初始化器设置但可通过AppContext 开关Microsoft.AspNetCore.Caching.StackExchangeRedis.UseForceReconnect启用“强制重连”容错模式见下文第八节5.1 一个必知细节AbortOnConnectFail 被强制关闭在 RedisCacheOptions.cs 的GetConfiguredOptions()中可以看到var options ConfigurationOptions ?? ConfigurationOptions.Parse(Configuration!); // we dont want an initially unavailable server to prevent DI creating the service itself options.AbortOnConnectFail false; return options;无论你在连接字符串里是否设置了abortConnectfalse实现都会强制将AbortOnConnectFail设为false。这样做的目的正如注释所说Redis 服务暂时不可用时DI 容器仍然可以正常创建缓存服务不会因连接失败阻断应用启动。连接会转入后台重试命令在恢复后自动可用。5.2 使用 ConfigurationOptions 的注册示例当需要以强类型方式而非字符串配置连接时写法如下builder.Services.AddStackExchangeRedisCache(options { options.ConfigurationOptions new StackExchange.Redis.ConfigurationOptions { EndPoints { myredis.contoso.com, 6380 }, Password ******, Ssl true, AbortOnConnectFail false, ConnectTimeout 5000, SyncTimeout 5000, }; options.InstanceName MyCache; });六、InstanceName多应用共享 Redis 的键前缀隔离当多个应用或多环境共用同一个 Redis 实例时InstanceName提供了一种简单可靠的隔离手段。在 RedisCache.cs 的构造函数中可以看到具体实现var instanceName _options.InstanceName; if (!string.IsNullOrEmpty(instanceName)) { _instancePrefix (RedisKey)Encoding.UTF8.GetBytes(instanceName); }InstanceName会被 UTF-8 编码成字节形式的前缀。所有键操作读写、刷新、删除都会经过_instancePrefix.Append(key)处理例如构造缓存键为MyCacheweather:Shanghai。从源码注释看这里特意提前把前缀编码为byte[]是为了帮助 StackExchange.Redis 在键前缀拼接场景下避免重复分配与额外的 UTF-8 编码开销——代码以字节形态存放前缀即可让后续拼接“零转换”。需要注意前缀不是Redis 的命名空间或者 DB 号它只是拼接在键前面的普通字符串。因此如果两个应用设置了相同的InstanceName它们仍会互相看到对方的数据请确保不同应用使用互不相同的实例名。七、底层存储模型一个缓存项 一个 Redis Hash理解RedisCache的实现关键在于知道它在 Redis 中如何组织数据。与简单SET/GET一个字符串不同RedisCache将每个缓存项存储为一个Redis Hash内部使用三个固定字段见 RedisCache.cs哈希字段名含义absexp绝对过期时间AbsoluteExpiration以DateTimeOffset.Ticks存储无绝对过期时写入哨兵值-1源码中常量NotPresentsldexp滑动过期时长SlidingExpiration以TimeSpan.Ticks存储无滑动过期时同样写入-1data用户实际写入的缓存负载byte[]一次Set操作在 SetImpl 中完成先计算 TTL再构造包含上述三个字段的HashEntry[]。当不需要 TTL 时直接执行HashSet当需要 TTL 时实现借助 StackExchange.Redis 的批处理Batch管道把两个命令一起发出var batch cache.CreateBatch(); var setFields batch.HashSetAsync(prefixedKey, fields); var setTtl batch.KeyExpireAsync(prefixedKey, TimeSpan.FromSeconds(ttl.GetValueOrDefault())); batch.Execute(); cache.WaitAll(setFields, setTtl);异步路径SetImplAsync则通过Task.WhenAll(HashSetAsync, KeyExpireAsync)并行发出。这里使用**秒级 TTLKeyExpire而非KeyExpire的毫秒版**是刻意的设计取舍源码注释解释TTL 以整数秒计算可兼容更多 Redis 服务端实现见 GetExpirationInSeconds。7.1 读取即刷新的滑动过期语义每次Get/Refresh内部都走GetAndRefresh或GetAndRefreshAsync见 RedisCache.cs。它一次性读取所需的哈希字段absexp、sldexp需要时再加data然后通过MapMetadata还原两个过期时间若只存在绝对过期则不做额外操作若存在滑动过期则重新执行KeyExpire把键的 TTL 重置为滑动时长相当于 LRU 命中刷新。有趣的是源码里保留了GetAndRefresh的 TODO 注释能否把这套“读取 元数据解析 可能刷新过期”的逻辑在 Redis 服务端一步完成。目前的实现方式是滑动过期会把过期时间刷新为min(距绝对过期剩余时间, 滑动时长)见 RedisCache.Refresh从而保证滑动刷新永远不会越过绝对过期边界。7.2 过期的校验与计算写入时的过期时间由 GetAbsoluteExpiration 与 GetExpirationInSeconds 完成。前者有一个容易踩坑的约束如果AbsoluteExpiration已经是过去时间会直接抛出ArgumentOutOfRangeException消息为 The absolute expiration value must be in the future.。测试文件 TimeExpirationTests.cs 中的AbsoluteExpirationInThePastThrows、AbsoluteExpirationExpires、AbsoluteSubSecondExpirationExpiresImmediately等用例正是围绕这套过期语义进行验证的这些用例在无 Redis 环境时默认被跳过需要本地启动 Redis 并将RedisTestConfig.RedisPort调为实际端口。过期时长优先级上AbsoluteExpirationRelativeToNow会直接换算为绝对过期时间点随后在绝对与滑动同时存在时取两者的较近者作为 TTLMath.Min。八、连接管理、懒连接与强制重连容错RedisCache采用懒连接策略真正的ConnectionMultiplexer直到第一次读写操作时才建立。连接对象缓存于字段_cache并用SemaphoreSlim(_connectionLock)保证并发安全见 Connect 与 ConnectSlowAsync。同时RedisCache还实现了IDisposable在Dispose()中会把当前连接关闭并释放。为提升可观测性与诊断效率PrepareConnection 在每次连接建立后还会注册性能分析会话若配置了ProfilingSession通过AddLibraryNameSuffix(aspnet)、AddLibraryNameSuffix(DC)为连接附加库名后缀帮助识别 ASP.NET Core 缓存产生的流量DC表示 Distributed Cache。若检测到应用同时注册了HybridCache还会追加HC后缀。这一调用可能抛出的异常被记录为 Debug 级日志UnableToAddLibraryNameSuffix定义见 RedisCache.Log.cs。8.1 强制重连模式UseForceReconnectStackExchange.Redis 自身具备自动重连能力但在个别网络环境下如 Azure Redis 的故障转移场景仍可能出现客户端复用陈旧连接的问题。为此实现提供了一条注释中引用的 “force reconnect” 最佳实践路径见 RedisCache.cs可以通过 AppContext 开关启用AppContext.SetSwitch(Microsoft.AspNetCore.Caching.StackExchangeRedis.UseForceReconnect, isEnabled: true);启用后每次 Redis 操作抛出的异常会经过 OnRedisError 处理只有异常属于RedisConnectionException或SocketException时才可能触发顶层重连且遵循两个时间阈值ReconnectMinInterval 60s距上次连接/重连不足 60 秒时不主动重建连接避免与 StackExchange.Redis 内部重连机制“打架”ReconnectErrorThreshold 30s只有当错误已经持续至少 30 秒、且最近一次错误也落在 30 秒窗口内时才会真正重建防止基于“陈旧错误”误触发。若错误中断超过 30 秒计时会复位重新累计。判定通过后实现会以Interlocked.CompareExchange将共享的_cache字段清空并释放旧连接下一次调用自然会走懒连接逻辑重新建立全新连接。这层重连只在RedisCache层做“顶层兜底”StackExchange.Redis 内部的自动重连仍是第一道防线。九、与 HybridCache 的协同RedisCacheImplRedisCacheImpl.cs作为 DI 注册的实现类额外注入了IServiceProviderIsService用于检测容器中是否注册了Microsoft.Extensions.Caching.Hybrid.HybridCacheinternal override bool IsHybridCacheActive() _serviceProviderIsService?.IsService(typeof(HybridCache)) true;当应用同时使用HybridCache并把本包作为其二级L2分布式缓存时连接会追加HC库名后缀便于在 Redis 监控如CLIENT LIST、INFO中区分流量来源。注册AddStackExchangeRedisCache本身并不自动引入HybridCache两者是组合关系而非替代关系。十、工程与测试佐证注册行为验证CacheServiceExtensionsTests.cs 覆盖了“单例注册”“覆盖用户先前注册的 Scoped 实现”“链式调用返回同一 ServiceCollection”“无/有日志时均可解析IDistributedCache”等场景其中RedisCache的可解析性得益于构造函数对ILogger的可选注入设计内部构造器在无日志工厂时使用NullLogger。过期语义验证TimeExpirationTests.cs 与其异步版本 TimeExpirationAsyncTests.cs 验证绝对过期、秒级以下过期、负值报错等行为。读写删除验证RedisCacheSetAndRemoveTests.cs 覆盖Set/Remove相关操作。测试基础设施RedisTestConfig.cs 演示了如何以localhost:6379RedisPort 6379这样的连接串直接构造RedisCache其CreateCacheInstance仅设置了Configuration与InstanceName两个选项也展示了测试进程自行拉起/连接 Redis 服务器的整套逻辑——这正是第五、六节所述两个最小配置项在真实场景中的直接应用。十一、小结与建议Microsoft.Extensions.Caching.StackExchangeRedis是 ASP.NET Core 生态中把 Redis 接入IDistributedCache抽象的标准通道。回顾核心要点注册极简AddStackExchangeRedisCache一行完成单例注册连接串放入ConnectionStrings即可示例见 PACKAGE.md。隔离靠前缀多应用共享 Redis 时务必设置互不相同的InstanceName。存储结构透明每个键是一个包含absexp/sldexp/data三个字段的 Redis Hash过期时间以 Ticks 存放、TTL 以秒下发。健壮性内置AbortOnConnectFail被强制关闭保证启动可用出现持续网络故障时可启用UseForceReconnectAppContext 开关获得 60 秒节流 30 秒错误窗口的强制重连兜底。升级路径清晰同一包内的RedisCache实现了支持零拷贝写入的IBufferDistributedCache并可与HybridCache组合形成两级缓存源码中均保留了相应挂点。本文所有结论均可在仓库 src/Caching/StackExchangeRedis 目录下的源码、公共 API 清单与测试用例中得到验证读者可结合这些文件进一步深入。【免费下载链接】aspnetcoreASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcore创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考