RuoVea.ExCache
10.0.0.4
dotnet add package RuoVea.ExCache --version 10.0.0.4
NuGet\Install-Package RuoVea.ExCache -Version 10.0.0.4
<PackageReference Include="RuoVea.ExCache" Version="10.0.0.4" />
<PackageVersion Include="RuoVea.ExCache" Version="10.0.0.4" />
<PackageReference Include="RuoVea.ExCache" />
paket add RuoVea.ExCache --version 10.0.0.4
#r "nuget: RuoVea.ExCache, 10.0.0.4"
#:package RuoVea.ExCache@10.0.0.4
#addin nuget:?package=RuoVea.ExCache&version=10.0.0.4
#tool nuget:?package=RuoVea.ExCache&version=10.0.0.4
🔗 RuoVea.ExCache
统一缓存抽象层 — 提供
ICache统一接口,一行配置在内存缓存与 Redis 分布式缓存之间无缝切换,业务代码零改动。
📖 目录
📋 概览
RuoVea.ExCache 为 .NET 应用提供轻量级的缓存抽象层,通过 appsettings.json 配置驱动,运行时自动选择缓存后端。
┌─────────────────────────────────────────────────────┐
│ RuoVea.ExCache │
├─────────────────────────────────────────────────────┤
│ │
│ appsettings.json │
│ ┌──────────────────┐ │
│ │ Cache:CacheType │──── 配置驱动 ────┐ │
│ │ Cache:Prefix │ │ │
│ │ Cache:RedisConn │ ▼ │
│ └──────────────────┘ ┌──────────────────────┐ │
│ │ CacheFactery │ │
│ │ (静态工厂 / 配置中心) │ │
│ └──────────┬───────────┘ │
│ │ │
│ ┌────────────▼─────────────┐ │
│ │ ICache │ │
│ │ (统一缓存接口) │ │
│ └────────┬─────────────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────┐ │
│ │ MemoryCaches │ │ RedisCache │ │
│ │ (进程内 MemoryCache) │ │ (CSRedis 分布式) │ │
│ └──────────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
设计原则
| 原则 | 说明 |
|---|---|
| 配置驱动 | 通过 Cache:CacheType 切换后端,不修改业务代码 |
| 统一接口 | ICache 17 个方法覆盖读写删查全生命周期 |
| 键前缀隔离 | 所有 Key 自动加全局前缀,多租户/多环境键空间隔离 |
| 双工 API | 每个操作同时提供同步与异步版本 |
| 零 DI 依赖 | 无需 IServiceCollection 注册,CacheFactery.Cache 直接获取 |
📦 安装
.NET CLI
# .NET 8.0
dotnet add package RuoVea.ExCache --version 8.0.*
# .NET 10.0
dotnet add package RuoVea.ExCache --version 10.0.*
Package Manager
Install-Package RuoVea.ExCache -Version 8.0.*
自动安装的依赖
| 依赖包 | 最低版本 | 用途 |
|---|---|---|
| CSRedisCore | 3.8.807 | Redis 客户端驱动 |
| Microsoft.Extensions.Caching.Memory | 8.0.1 | 内存缓存后端 |
| RuoVea.ExConfig | 8.0.* | 内部配置读取 |
支持的 Target Framework
| TFM | 最低版本 |
|---|---|
net8.0 |
8.0.1.3 |
net10.0 |
10.0.0.3 |
⚡ 30 秒快速开始
1. 配置 appsettings.json
{
"Cache": {
"Prefix": "MyApp",
"CacheType": "MemoryCache",
"RedisConnectionString": "127.0.0.1:6379,password=,defaultDatabase=2"
}
}
2. 第一行缓存代码
using RuoVea.ExCache;
using RuoVea.ExCache.Cache;
// <inheritdoc cref="CacheFactery.Cache"/>
// 获取缓存实例(根据配置自动选择 MemoryCache 或 RedisCache)
ICache cache = CacheFactery.Cache;
// <inheritdoc cref="ICache.Write(string, object, TimeSpan)"/>
// 写入缓存(30 分钟过期)
cache.Write("user:1001", new { Name = "张三", Role = "Admin" }, TimeSpan.FromMinutes(30));
// <inheritdoc cref="ICache.Read{T}(string)"/>
// 泛型读取
var user = cache.Read<UserInfo>("user:1001");
// <inheritdoc cref="ICache.Exists"/>
if (cache.Exists("user:1001"))
Console.WriteLine("缓存命中 ✓");
// <inheritdoc cref="ICache.Del"/>
cache.Del("user:1001");
3. 异步版本
// <inheritdoc cref="ICache.WriteAsync(string, object, TimeSpan)"/>
await cache.WriteAsync("metrics:daily", data, TimeSpan.FromHours(1));
// <inheritdoc cref="ICache.ReadAsync{T}(string)"/>
var result = await cache.ReadAsync<Metrics>("metrics:daily");
// <inheritdoc cref="ICache.ExistsAsync"/>
bool exists = await cache.ExistsAsync("metrics:daily");
// <inheritdoc cref="ICache.DelAsync(string[])"/>
await cache.DelAsync("metrics:daily", "metrics:weekly");
30 秒内你完成了:配置 → 获取实例 → 写入 → 泛型读取 → 存在检查 → 删除,同步/异步全覆盖。
🧩 核心场景
场景一:Read-Through 缓存(防穿透)
┌───────┐ Read(key) ┌──────────┐
│ 业务层 │ ────────────▶ │ 缓存层 │
└───┬───┘ └────┬─────┘
│ │
│ 命中 → 直接返回 │ 未命中 → 调用 getFun
│ │ ↓
│ │ ┌──────────┐
│ │ │ 数据库/API │
│ │ └────┬─────┘
│ │ │ 写回缓存 + 返回
│ │ ◀────┘
▼ ▼
返回结果 返回结果
// <inheritdoc cref="ICache.Cof_ReadWrite{T}"/>
// 同步 Read-Through:缓存未命中自动回源
public Product GetProduct(long productId)
{
return CacheFactery.Cache.Cof_ReadWrite<Product>(
$"product:{productId}",
getFun: () =>
{
// 缓存未命中 → 从数据库查询
return _db.Products.FirstOrDefault(p => p.Id == productId);
},
timeSpanMin: TimeSpan.FromMinutes(10)
);
}
// <inheritdoc cref="ICache.ReadAsync{T}(string)"/>
// 异步 Read-Through(手动实现——ICache 未提供 Cof_ReadWrite 的异步版本)
public async Task<Product> GetProductAsync(long productId)
{
var key = $"product:{productId}";
// 1. 先查缓存
var cached = await CacheFactery.Cache.ReadAsync<Product>(key);
if (cached != null) return cached;
// 2. 回源
var product = await _db.Products.FindAsync(productId);
if (product != null)
// 3. 写回缓存
await CacheFactery.Cache.WriteAsync(key, product, TimeSpan.FromMinutes(10));
return product;
}
⚠️ 线程安全说明:
Cof_ReadWrite<T>内部使用键级SemaphoreSlim+ 双重检查来大幅减少高并发下的重复回源调用。但不同进程/实例之间不互斥,严格防击穿请配合分布式锁。
// 推荐:带分布式锁的安全 Read-Through
public async Task<T> GetSafeAsync<T>(string key, Func<Task<T>> factory, TimeSpan ttl)
where T : class
{
var cached = await cache.ReadAsync<T>(key);
if (cached != null) return cached;
using var lockHandle = await _distributedLock.AcquireAsync($"lock:{key}");
cached = await cache.ReadAsync<T>(key); // 双重检查
if (cached != null) return cached;
var result = await factory();
if (result != null)
await cache.WriteAsync(key, result, ttl);
return result;
}
场景二:模式批量删除
// <inheritdoc cref="ICache.DelByPattern"/>
// 同步 —— 删除匹配 "product:category:*" 的所有键
public void InvalidateCategoryCache(long categoryId)
{
long deleted = CacheFactery.Cache.DelByPattern($"product:category:{categoryId}:*");
_logger.LogInformation("失效分类缓存 {CategoryId},删除 {Count} 键", categoryId, deleted);
}
// <inheritdoc cref="ICache.DelByPrefixAsync"/>
// 异步 —— 按前缀删除
public async Task InvalidateUserSessionsAsync(string sessionPrefix)
{
long deleted = await CacheFactery.Cache.DelByPrefixAsync(sessionPrefix);
_logger.LogInformation("清理会话 {Prefix},删除 {Count}", sessionPrefix, deleted);
}
❗ 性能陷阱:Redis 模式下
DelByPattern/DelByPrefix底层使用KEYS命令(O(N) 全库扫描)。生产环境数据量大时严禁高频调用。MemoryCache 模式也执行 O(N) 全量正则匹配遍历。
场景三:统一会话管理
public class SessionManager
{
// ✅ 持有单例实例,避免每次访问 CacheFactery.Cache 创建新对象
private static readonly ICache _cache = CacheFactery.Cache;
// <inheritdoc cref="ICache.Write(string, object, TimeSpan)"/>
public void CreateSession(string sessionId, UserSession session)
{
// 实际键 = "MyApp:session:{sessionId}"(全局前缀自动添加)
var success = _cache.Write($"session:{sessionId}", session, TimeSpan.FromHours(2));
if (!success)
throw new InvalidOperationException("创建会话缓存失败");
}
// <inheritdoc cref="ICache.Read{T}(string)"/>
public UserSession GetSession(string sessionId)
=> _cache.Read<UserSession>($"session:{sessionId}");
// <inheritdoc cref="ICache.DelAsync(string[])"/>
public async Task LogoutAsync(params string[] sessionIds)
{
var keys = sessionIds.Select(id => $"session:{id}").ToArray();
await _cache.DelAsync(keys);
}
}
场景四:键空间巡检
// <inheritdoc cref="ICache.GetAllKeysByPrefix"/>
// 同步 —— 列出指定前缀下的所有缓存键(运维/监控场景)
public IReadOnlyList<string> GetActiveSessionKeys()
=> CacheFactery.Cache.GetAllKeysByPrefix("session:");
// <inheritdoc cref="ICache.GetAllKeysByPrefixAsync"/>
// 异步版本
public async Task<IReadOnlyList<string>> GetActiveSessionKeysAsync()
=> await CacheFactery.Cache.GetAllKeysByPrefixAsync("session:");
❗ 性能陷阱:Redis 模式触发
KEYS命令,MemoryCache 模式做 O(N) 扫描。仅限管理后台/调试工具等低频场景,禁止放入热路径。
⚙️ 配置选项详解
配置节点路径
全部读取自 IConfiguration(通过 RuoVea.ExConfig.AppSettings):
| 路径 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
Cache:Prefix |
string |
否 | — | 全局键前缀。最终键格式:{Prefix}:{yourKey} |
Cache:CacheType |
string |
否 | MemoryCache |
"MemoryCache"(进程内)或 "RedisCache"/"Redis"(分布式) |
Cache:RedisConnectionString |
string |
Redis 时必填 | — | Redis 连接字符串 host:port,password=,defaultDatabase=0 |
配置示例
开发环境 — 内存缓存
{
"Cache": {
"Prefix": "DevApp",
"CacheType": "MemoryCache"
}
}
生产环境 — Redis 哨兵
{
"Cache": {
"Prefix": "ProdApp",
"CacheType": "Redis",
"RedisConnectionString": "10.0.1.10:6379,password=SecureP@ss,defaultDatabase=0,prefix=prod"
}
}
运行时覆盖
// ⚠️ 必须在首次调用 CacheFactery.Cache 之前设置
CacheFactery.CacheType = CacheEnum.RedisCache;
CacheFactery.Prefix = "DynamicPrefix";
CacheFactery.RedisConnectionString = "redis-master:6379,password=xxx,defaultDatabase=1";
ICache cache = CacheFactery.Cache; // 获取 RedisCache 实例
🛡️ 错误处理与日志
常见异常
| 异常类型 | 触发条件 | 处理建议 |
|---|---|---|
CSRedis.CSRedisException |
Redis 连接失败/超时/OOM | 启用连接池重试,设置合理超时 |
ObjectDisposedException |
Redis 客户端已释放后调用 | 确保应用生命周期内不提前释放 |
InvalidCastException |
Read<T> 类型与写入类型不匹配 |
读写使用同一类型 |
推荐的弹性缓存模式
public class ResilientCacheService
{
private static readonly ICache _cache = CacheFactery.Cache;
private readonly ILogger<ResilientCacheService> _logger;
public ResilientCacheService(ILogger<ResilientCacheService> logger) => _logger = logger;
/// <summary>
/// 安全读取:缓存异常时降级到回源,不影响业务
/// </summary>
public async Task<T> GetOrFallbackAsync<T>(string key, Func<Task<T>> fallback, TimeSpan ttl)
{
try
{
var cached = await _cache.ReadAsync<T>(key);
if (cached != null)
{
_logger.LogDebug("缓存命中 Key={Key}", key);
return cached;
}
}
catch (Exception ex)
{
// 缓存不可用时降级,不阻断业务
_logger.LogWarning(ex, "缓存读取异常,降级回源 Key={Key}", key);
}
var result = await fallback();
if (result != null)
{
try { await _cache.WriteAsync(key, result, ttl); }
catch (Exception ex) { _logger.LogWarning(ex, "缓存回写失败 Key={Key}", key); }
}
return result;
}
}
🧵 线程安全
| 组件 | 线程安全 | 说明 |
|---|---|---|
CacheFactery.CacheType / Prefix / RedisConnectionString |
⚠️ 条件安全 | 启动阶段赋值安全;运行时修改需调用 ResetCacheInstance() 使其生效。调用 SealConfig() 后可禁止修改 |
CacheFactery.Cache getter |
✅ 是 | Lazy<ICache> + LazyThreadSafetyMode.ExecutionAndPublication 保证单例且线程安全 |
CacheFactery.RedisClient |
✅ 是 | 由 Lazy<ICache> 内部初始化,单次赋值 |
MemoryCaches._cacheKeys |
✅ 是 | lock (_lock) 保护读写 |
MemoryCaches._memoryCache |
✅ 是 | MemoryCache 本身线程安全 |
MemoryCaches.Cof_ReadWrite<T> |
⚠️ 部分 | 使用键级 SemaphoreSlim + 双重检查大幅减少重复回源调用,但非严格原子性(不同进程/实例间不互斥) |
RedisCache 全部方法 |
✅ 是 | 委托给 CSRedis.RedisHelper(线程安全) |
RedisCache.Cof_ReadWrite<T> |
⚠️ 部分 | 同上——单实例内键级锁保护,分布式环境需配合分布式锁 |
改进说明(v8.0.1.3+):
CacheFactery.Cache已改为Lazy<ICache>单例模式,不再每次创建新实例。Cof_ReadWrite<T>新增键级SemaphoreSlim双重检查以减少并发回源。如需运行时切换配置,使用SealConfig()/ResetCacheInstance()。
📊 API 完整速查
ICache 接口(17 个成员)
| 分类 | 同步方法 | 异步方法 | 说明 |
|---|---|---|---|
| 写入 | Write(key, value) |
WriteAsync(key, value) |
写入(无过期) |
Write(key, value, expire) |
WriteAsync(key, value, expire) |
写入(指定过期) | |
| 读取 | Read(key) → string |
ReadAsStringAsync(key) |
读取字符串 |
Read<T>(key) → T |
ReadAsync<T>(key) |
读取泛型 | |
| 删除 | Del(keys...) |
DelAsync(keys...) |
删除一个或多个键 |
DelByPattern(pattern) |
DelByPatternAsync(pattern) |
通配符模式删除 | |
| — | DelByPrefixAsync(prefix) |
前缀删除(仅异步) | |
| 存在 | Exists(key) |
ExistsAsync(key) |
检查键是否存在 |
| 穿透 | Cof_ReadWrite<T>(key, factory, ttl) |
— | Read-Through(仅同步) |
| 巡检 | GetAllKeysByPrefix(prefix) |
GetAllKeysByPrefixAsync(prefix) |
列出前缀下所有键 |
CacheEnum 枚举
| 值 | 说明 |
|---|---|
MemoryCache |
进程内 System.Runtime.Caching.MemoryCache |
RedisCache |
CSRedis 分布式缓存 |
🗺️ 版本迁移指南
从 v7.x → v8.0
| 变更项 | 说明 |
|---|---|
ICache 接口新增 |
DelByPattern、DelByPatternAsync、GetAllKeysByPrefix、GetAllKeysByPrefixAsync |
ICache.ReadAsync<T> |
签名兼容,底层序列化器升级至 CSRedisCore 最新版 |
| 配置路径 | 无变化 — Cache 节点完全兼容 |
| 命名空间 | 无变化 — RuoVea.ExCache / RuoVea.ExCache.Cache |
迁移步骤:
- 更新 NuGet 版本至
8.0.* - 重新编译确认无错误(接口向下兼容)
- (可选)将手写
DelByPrefix替换为内置的DelByPattern/DelByPrefixAsync - (可选)利用新增
GetAllKeysByPrefix替换反射/私有字段访问的巡检代码
📄 License
MIT License © RuoVea
🔗 相关资源: NuGet Gallery · 问题反馈
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- CSRedisCore (>= 3.8.807)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Caching.Memory (>= 10.0.8)
- RuoVea.ExConfig (>= 10.0.0.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on RuoVea.ExCache:
| Package | Downloads |
|---|---|
|
RuoVea.ExSugar
Sqlsugar扩展 快速注入,支持简体中文、繁体中文、粤语、日语、法语、英语.使用方式:service.AddSqlsugar();继承RestFulLog 重写异常日志,操作日志,差异日志 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0.4 | 54 | 6/24/2026 |
| 10.0.0.3 | 627 | 5/28/2026 |
| 10.0.0.2 | 592 | 1/26/2026 |
| 10.0.0.1 | 164 | 1/12/2026 |
| 9.0.0.3 | 141 | 5/28/2026 |
| 9.0.0.2 | 1,168 | 1/26/2026 |
| 9.0.0.1 | 166 | 1/12/2026 |
| 8.0.1.4 | 51 | 6/24/2026 |
| 8.0.1.3 | 1,203 | 5/28/2026 |
| 8.0.1.2 | 1,658 | 1/26/2026 |
| 8.0.1.1 | 891 | 1/12/2026 |
| 7.0.1.3 | 613 | 5/28/2026 |
| 7.0.1.2 | 1,729 | 1/26/2026 |
| 7.0.1.1 | 1,020 | 1/12/2026 |
| 6.0.6.3 | 711 | 5/28/2026 |
| 6.0.6.2 | 2,069 | 1/26/2026 |
| 6.0.6.1 | 1,238 | 1/12/2026 |
| 5.0.5.3 | 121 | 5/28/2026 |
| 5.0.5.2 | 235 | 1/26/2026 |
| 5.0.5.1 | 194 | 1/12/2026 |