# 缓存 (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

# 🔧 支持的缓存提供器

提供器 枚举值 说明 适用场景
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

# 配置项详解

配置项 类型 默认值 说明
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

说明: 缓存处理器采用单例模式注入。


# 💡 使用示例

# 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. 删除缓存

public async Task UpdateAsync(UserEntity user)
{
    await _userRepository.UpdateAsync(user);
    
    // 删除缓存
    await _cache.RemoveAsync($"user:{user.Id}");
}
1
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

# 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

# 5. 自增/自减

// 访问计数
var count = _cache.Increment("visit:today", 1);

// 库存扣减
var stock = _cache.Decrement($"product:{productId}:stock", 1);
1
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

# 📚 缓存策略

# 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. 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

特点: 同时更新数据库和缓存,保证数据一致性。


# 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

特点: 先更新缓存,异步更新数据库,提高写入性能。


# 📚 最佳实践

# 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. 设置合理的过期时间

// 频繁变化的数据 - 短过期
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

# 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

# 4. 批量删除缓存

// 删除用户相关的所有缓存
await _cache.RemoveByPrefixAsync("user:1001:");

// 删除订单相关的所有缓存
await _cache.RemoveByPrefixAsync("order:20230101:");
1
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

# 🔍 常见问题

# Q1: 缓存雪崩如何解决?

问题: 大量缓存同时过期,导致数据库压力激增

解决方案:

// 添加随机过期时间
var expires = 60 + new Random().Next(0, 60);  // 60-120 分钟
await _cache.SetAsync(key, value, expires);
1
2
3

# Q2: 缓存穿透如何解决?

问题: 查询不存在的数据,缓存不命中,直接打到数据库

解决方案:

// 缓存空值
if (user == null)
{
    await _cache.SetAsync(key, new object(), 1);  // 1 分钟过期
}
1
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

# Q4: 数据一致性如何保证?

问题: 数据库和缓存数据不一致

解决方案:

  1. 设置合理的过期时间
  2. 更新数据库时同步删除/更新缓存
  3. 使用 Write-Through 模式

# Q5: 如何选择合适的缓存提供器?

选择建议:

场景 推荐提供器 理由
开发环境 MemoryCache 快速、无需外部依赖
单机应用 MemoryCache 简单、性能好
分布式系统 Redis 支持分布式、持久化
高并发 Redis/Memcached 高性能、支持集群

# 📚 相关文档


# 🔗 参考链接


最后更新: 2024-09-20