# 缓存 (06_Cache)
最后更新: 2024-09-20
# 📚 概述
DarkM 缓存模块提供统一的缓存抽象层,支持多种缓存提供器(MemoryCache、Redis、Memcached),通过接口实现,可以根据需求灵活切换。
源代码位置: DarkM/src/Framework/Cache
# 🏗️ 模块架构
# 完整目录结构
Cache/
├── Cache.Abstractions/
│ ├── CacheConfig.cs # 缓存配置
│ ├── CacheProvider.cs # 缓存提供器枚举
│ ├── ICacheHandler.cs # 缓存处理接口
│ ├── CacheKeyDescriptor.cs # 缓存键描述
│ └── CacheKeyInfo.cs # 缓存键信息
│
├── Cache.MemoryCache/
│ └── MemoryCacheHandler.cs # 内存缓存实现
│
├── Cache.Redis/
│ ├── RedisCacheHandler.cs # Redis 缓存实现
│ ├── RedisHelper.cs # Redis 帮助类
│ ├── IRedisSerializer.cs # Redis 序列化接口
│ └── DefaultRedisSerializer.cs # 默认序列化器
│
├── Cache.Memcached/
│ ├── MemcachedCacheHandler.cs # Memcached 缓存实现
│ └── MemcachedHelper.cs # Memcached 帮助类
│
└── Cache.Integration/
└── ServiceCollectionExtensions.cs # DI 扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 🔧 支持的缓存提供器
| 提供器 | 枚举值 | 说明 | 适用场景 |
|---|---|---|---|
| MemoryCache | 0 | 内存缓存,单机使用 | 开发环境、单机应用 |
| Redis | 1 | Redis 缓存,分布式 | 生产环境、分布式系统 |
| Memcached | 2 | Memcached 缓存,分布式 | 高并发场景 |
# 💡 配置说明
# appsettings.json 配置
{
"Cache": {
// 缓存提供器:0=MemoryCache, 1=Redis, 2=Memcached
"Provider": 1,
// Redis 配置
"Redis": {
// 默认数据库
"DefaultDb": 0,
// 前缀,用于区分不同的项目
"Prefix": "DM",
// 连接字符串
"ConnectionString": "127.0.0.1:6379"
},
// Memcached 配置
"Memcached": {
// 前缀
"Prefix": "DM",
// 服务器列表
"Servers": [
{
"Address": "localhost",
"Port": 11211
}
],
// 认证配置
"Authentication": {
"Type": "Enyim.Caching.Memcached.PlainTextAuthenticator",
"Parameters": {
"zone": "DM",
"userName": "",
"password": ""
}
},
"MinPoolSize": 5,
"MaxPoolSize": 1000
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 配置项详解
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| Provider | CacheProvider | 1 | 缓存提供器 |
| Redis.DefaultDb | int | 0 | Redis 默认数据库 |
| Redis.Prefix | string | DM | Redis 键前缀 |
| Redis.ConnectionString | string | - | Redis 连接字符串 |
| Memcached.Prefix | string | DM | Memcached 键前缀 |
| Memcached.Servers | Array | - | Memcached 服务器列表 |
# 🔧 核心接口
# ICacheHandler
文件位置: Cache.Abstractions/ICacheHandler.cs
public interface ICacheHandler
{
/// <summary>
/// 获取缓存
/// </summary>
T Get<T>(string key);
Task<T> GetAsync<T>(string key);
/// <summary>
/// 设置缓存
/// </summary>
bool Set<T>(string key, T value, int expires);
Task<bool> SetAsync<T>(string key, T value, int expires);
/// <summary>
/// 删除缓存
/// </summary>
bool Remove(string key);
Task<bool> RemoveAsync(string key);
/// <summary>
/// 检查是否存在
/// </summary>
bool Exists(string key);
Task<bool> ExistsAsync(string key);
/// <summary>
/// 删除指定前缀的缓存
/// </summary>
Task RemoveByPrefixAsync(string prefix);
/// <summary>
/// 分布式锁
/// </summary>
bool LockTake(string key, string value, int expires);
bool LockRelease(string key, string value);
/// <summary>
/// 自增/自减
/// </summary>
long Increment(string key, long value = 1);
long Decrement(string key, long value = 1);
/// <summary>
/// Hash 操作
/// </summary>
Task HashSetAsync<T>(string key, string field, T obj);
Task<T> HashGetAsync<T>(string key, string field);
Task<bool> HashDeleteAsync(string key, string field);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
说明: 缓存处理器采用单例模式注入。
# 💡 使用示例
# 1. 获取/设置缓存
using DarkM.Lib.Cache.Abstractions;
public class UserService : IUserService
{
private readonly ICacheHandler _cache;
public UserService(ICacheHandler cache)
{
_cache = cache;
}
public async Task<UserEntity> GetAsync(int id)
{
var cacheKey = $"user:{id}";
// 尝试从缓存获取
var user = await _cache.GetAsync<UserEntity>(cacheKey);
if (user != null)
return user;
// 从数据库查询
user = await _userRepository.GetAsync(id);
// 设置缓存(5 分钟过期)
await _cache.SetAsync(cacheKey, user, 5);
return user;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 2. 删除缓存
public async Task UpdateAsync(UserEntity user)
{
await _userRepository.UpdateAsync(user);
// 删除缓存
await _cache.RemoveAsync($"user:{user.Id}");
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3. 缓存穿透保护
public async Task<UserEntity> GetAsync(int id)
{
var cacheKey = $"user:{id}";
// 尝试从缓存获取
var user = await _cache.GetAsync<UserEntity>(cacheKey);
if (user != null)
return user;
// 从数据库查询
user = await _userRepository.GetAsync(id);
if (user != null)
{
// 设置缓存(5 分钟过期)
await _cache.SetAsync(cacheKey, user, 5);
}
else
{
// 缓存空值,防止缓存穿透(1 分钟过期)
await _cache.SetAsync(cacheKey, new object(), 1);
}
return user;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 4. 分布式锁
public async Task ProcessOrderAsync(int orderId)
{
var lockKey = $"lock:order:{orderId}";
var lockValue = Guid.NewGuid().ToString();
// 获取锁(10 秒过期)
if (_cache.LockTake(lockKey, lockValue, 10))
{
try
{
// 执行业务逻辑
await DoProcessAsync(orderId);
}
finally
{
// 释放锁
_cache.LockRelease(lockKey, lockValue);
}
}
else
{
throw new Exception("获取锁失败,可能有其他进程正在处理");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 5. 自增/自减
// 访问计数
var count = _cache.Increment("visit:today", 1);
// 库存扣减
var stock = _cache.Decrement($"product:{productId}:stock", 1);
1
2
3
4
5
2
3
4
5
# 6. Hash 操作
// 设置 Hash 字段
await _cache.HashSetAsync("user:1001", "name", "张三");
await _cache.HashSetAsync("user:1001", "email", "zhangsan@example.com");
// 获取 Hash 字段
var name = await _cache.HashGetAsync<string>("user:1001", "name");
// 获取所有字段
var values = await _cache.HashValuesAsync<UserField>("user:1001");
// 删除 Hash 字段
await _cache.HashDeleteAsync("user:1001", "email");
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 📚 缓存策略
# 1. Cache-Aside 模式(推荐)
public async Task<UserEntity> GetAsync(int id)
{
var key = $"user:{id}";
// 先读缓存
var user = await _cache.GetAsync<UserEntity>(key);
if (user != null)
return user;
// 缓存未命中,读数据库
user = await _userRepository.GetAsync(id);
// 写入缓存
await _cache.SetAsync(key, user, 5);
return user;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
特点: 先读缓存,未命中再读数据库,适用于读多写少场景。
# 2. Write-Through 模式
public async Task UpdateAsync(UserEntity user)
{
// 同时更新数据库和缓存
await _userRepository.UpdateAsync(user);
var key = $"user:{user.Id}";
await _cache.SetAsync(key, user, 5);
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
特点: 同时更新数据库和缓存,保证数据一致性。
# 3. Write-Behind 模式
public async Task UpdateAsync(UserEntity user)
{
// 先更新缓存
await _cache.SetAsync($"user:{user.Id}", user, 5);
// 异步更新数据库
_ = Task.Run(async () =>
{
await Task.Delay(1000); // 延迟写入
await _userRepository.UpdateAsync(user);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
特点: 先更新缓存,异步更新数据库,提高写入性能。
# 📚 最佳实践
# 1. 使用有意义的 Key 命名
// ✅ 推荐
var key = $"user:{userId}:profile";
var key = $"product:{productId}:stock";
// ❌ 不推荐
var key = "data1";
var key = userId.ToString();
1
2
3
4
5
6
7
2
3
4
5
6
7
# 2. 设置合理的过期时间
// 频繁变化的数据 - 短过期
await _cache.SetAsync("stock:1001", stock, 1); // 1 分钟
// 相对稳定的数据 - 长过期
await _cache.SetAsync("config:site", config, 60); // 60 分钟
// 字典数据 - 很长过期
await _cache.SetAsync("dict:province", provinces, 1440); // 24 小时
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 3. 缓存预热
public class CacheWarmupService : IHostedService
{
private readonly ICacheHandler _cache;
private readonly IDictRepository _dictRepository;
public async Task StartAsync(CancellationToken cancellationToken)
{
// 预热字典数据
var dicts = await _dictRepository.GetAllAsync();
await _cache.SetAsync("dict:all", dicts, 1440);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 4. 批量删除缓存
// 删除用户相关的所有缓存
await _cache.RemoveByPrefixAsync("user:1001:");
// 删除订单相关的所有缓存
await _cache.RemoveByPrefixAsync("order:20230101:");
1
2
3
4
5
2
3
4
5
# 5. 使用 Hash 存储对象
// 存储用户信息
await _cache.HashSetAsync("user:1001", "profile", userProfile);
await _cache.HashSetAsync("user:1001", "settings", userSettings);
// 比存储整个对象更灵活
1
2
3
4
5
2
3
4
5
# 🔍 常见问题
# Q1: 缓存雪崩如何解决?
问题: 大量缓存同时过期,导致数据库压力激增
解决方案:
// 添加随机过期时间
var expires = 60 + new Random().Next(0, 60); // 60-120 分钟
await _cache.SetAsync(key, value, expires);
1
2
3
2
3
# Q2: 缓存穿透如何解决?
问题: 查询不存在的数据,缓存不命中,直接打到数据库
解决方案:
// 缓存空值
if (user == null)
{
await _cache.SetAsync(key, new object(), 1); // 1 分钟过期
}
1
2
3
4
5
2
3
4
5
# Q3: 缓存击穿如何解决?
问题: 热点数据过期时,大量请求同时访问
解决方案:
// 使用分布式锁
if (_cache.LockTake(lockKey, lockValue, 10))
{
try
{
// 重建缓存
user = await LoadFromDbAsync();
await _cache.SetAsync(key, user, 60);
}
finally
{
_cache.LockRelease(lockKey, lockValue);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# Q4: 数据一致性如何保证?
问题: 数据库和缓存数据不一致
解决方案:
- 设置合理的过期时间
- 更新数据库时同步删除/更新缓存
- 使用 Write-Through 模式
# Q5: 如何选择合适的缓存提供器?
选择建议:
| 场景 | 推荐提供器 | 理由 |
|---|---|---|
| 开发环境 | MemoryCache | 快速、无需外部依赖 |
| 单机应用 | MemoryCache | 简单、性能好 |
| 分布式系统 | Redis | 支持分布式、持久化 |
| 高并发 | Redis/Memcached | 高性能、支持集群 |
# 📚 相关文档
# 🔗 参考链接
- 源代码 (opens new window) -
src/Framework/Cache - Redis 文档 (opens new window)
- Memcached 文档 (opens new window)
最后更新: 2024-09-20