第十章:缓存管理
10.1 缓存概述与策略
缓存是提升应用性能的关键技术之一。通过将频繁访问的数据存储在高速存储介质中(如内存),可以显著减少数据库查询次数,降低响应延迟,提高系统吞吐量。Furion 框架完全兼容 ASP.NET Core 的缓存体系,并提供了便捷的集成方式。
10.1.1 常见缓存策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| Cache-Aside(旁路缓存) | 先查缓存,未命中则查数据库并写入缓存 | 通用场景,最常用 |
| Read-Through(读穿透) | 缓存层自动从数据源加载缺失数据 | 简化应用层逻辑 |
| Write-Through(写穿透) | 写操作同时更新缓存和数据源 | 数据一致性要求高 |
| Write-Behind(异步写回) | 写操作先更新缓存,异步回写数据源 | 高写入吞吐量 |
| Refresh-Ahead(预刷新) | 在缓存过期前提前刷新 | 热点数据预加载 |
10.1.2 缓存类型对比
| 类型 | 存储位置 | 速度 | 共享性 | 持久化 | 适用场景 |
|---|---|---|---|---|---|
| 内存缓存 | 应用进程内存 | 最快 | 不共享 | 不持久化 | 单机应用 |
| 分布式缓存 | 外部存储(Redis等) | 快 | 可共享 | 可持久化 | 分布式/微服务 |
| 响应缓存 | HTTP层 | 最快 | 基于HTTP | 不持久化 | 静态内容/API响应 |
| 多级缓存 | 组合多层 | 综合最优 | 部分共享 | 部分持久化 | 高性能要求 |
10.2 内存缓存(IMemoryCache)
10.2.1 注册内存缓存
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 注册内存缓存
builder.Services.AddMemoryCache();
10.2.2 基本用法
using Microsoft.Extensions.Caching.Memory;
using Furion.DynamicApiController;
public class ProductService : IDynamicApiController
{
private readonly IMemoryCache _cache;
private readonly IRepository<Product> _productRepo;
private readonly ILogger<ProductService> _logger;
public ProductService(
IMemoryCache cache,
IRepository<Product> productRepo,
ILogger<ProductService> logger)
{
_cache = cache;
_productRepo = productRepo;
_logger = logger;
}
/// <summary>
/// 获取商品详情(带缓存)
/// </summary>
public async Task<Product> GetProduct(int id)
{
var cacheKey = $"product:{id}";
// 尝试从缓存获取
if (_cache.TryGetValue(cacheKey, out Product product))
{
_logger.LogDebug("命中缓存:{CacheKey}", cacheKey);
return product;
}
// 未命中,从数据库查询
_logger.LogDebug("未命中缓存,从数据库查询:{CacheKey}", cacheKey);
product = await _productRepo.FindAsync(id);
if (product != null)
{
// 写入缓存
var cacheOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(30)) // 绝对过期时间
.SetSlidingExpiration(TimeSpan.FromMinutes(10)) // 滑动过期时间
.SetSize(1) // 缓存大小
.SetPriority(CacheItemPriority.Normal); // 缓存优先级
_cache.Set(cacheKey, product, cacheOptions);
}
return product;
}
/// <summary>
/// 使用 GetOrCreate 模式(推荐)
/// </summary>
public async Task<List<Product>> GetHotProducts()
{
return await _cache.GetOrCreateAsync("hot_products", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
entry.SlidingExpiration = TimeSpan.FromMinutes(2);
_logger.LogInformation("加载热门商品数据到缓存");
return await _productRepo.Entities
.OrderByDescending(p => p.SalesCount)
.Take(20)
.ToListAsync();
});
}
/// <summary>
/// 更新商品(同时更新缓存)
/// </summary>
public async Task UpdateProduct(int id, ProductUpdateInput input)
{
var product = await _productRepo.FindAsync(id);
if (product == null) throw Oops.Oh("商品不存在");
product.Name = input.Name;
product.Price = input.Price;
await _productRepo.UpdateAsync(product);
await _productRepo.SaveNowAsync();
// 更新缓存
var cacheKey = $"product:{id}";
_cache.Set(cacheKey, product, TimeSpan.FromMinutes(30));
// 同时清除热门商品列表缓存
_cache.Remove("hot_products");
_logger.LogInformation("商品缓存已更新:{ProductId}", id);
}
/// <summary>
/// 删除商品(清除缓存)
/// </summary>
public async Task DeleteProduct(int id)
{
await _productRepo.DeleteAsync(id);
await _productRepo.SaveNowAsync();
// 清除缓存
_cache.Remove($"product:{id}");
_cache.Remove("hot_products");
}
}
10.2.3 缓存过期策略
/// <summary>
/// 缓存过期策略示例
/// </summary>
public class CacheExpirationDemo : IDynamicApiController
{
private readonly IMemoryCache _cache;
public CacheExpirationDemo(IMemoryCache cache)
{
_cache = cache;
}
public void DemoExpiration()
{
// 1. 绝对过期 - 到达指定时间后过期
_cache.Set("key1", "value1", new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(30)));
// 2. 滑动过期 - 如果在指定时间内没有访问,则过期
_cache.Set("key2", "value2", new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(10)));
// 3. 组合过期 - 滑动过期 + 绝对过期上限
_cache.Set("key3", "value3", new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(10))
.SetAbsoluteExpiration(TimeSpan.FromHours(2)));
// 4. 过期回调
var options = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(5))
.RegisterPostEvictionCallback((key, value, reason, state) =>
{
// 缓存被驱逐时的回调
Console.WriteLine($"缓存 {key} 被驱逐,原因:{reason}");
});
_cache.Set("key4", "value4", options);
// 5. 基于 Token 的过期(手动控制)
var cts = new CancellationTokenSource();
_cache.Set("key5", "value5", new MemoryCacheEntryOptions()
.AddExpirationToken(new CancellationChangeToken(cts.Token)));
// 手动触发过期
// cts.Cancel();
}
}
10.3 分布式缓存(IDistributedCache)
10.3.1 注册分布式缓存
// Program.cs
// 方式1:内存分布式缓存(开发环境使用)
builder.Services.AddDistributedMemoryCache();
// 方式2:SQL Server 分布式缓存
builder.Services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("CacheConnection");
options.SchemaName = "dbo";
options.TableName = "DistributedCache";
});
10.3.2 使用分布式缓存
using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;
public class DistributedCacheService : IDynamicApiController
{
private readonly IDistributedCache _cache;
private readonly IRepository<User> _userRepo;
public DistributedCacheService(IDistributedCache cache, IRepository<User> userRepo)
{
_cache = cache;
_userRepo = userRepo;
}
/// <summary>
/// 获取用户信息(分布式缓存)
/// </summary>
public async Task<User> GetUser(int id)
{
var cacheKey = $"user:{id}";
// 从缓存获取
var cachedData = await _cache.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(cachedData))
{
return JsonSerializer.Deserialize<User>(cachedData);
}
// 从数据库查询
var user = await _userRepo.FindAsync(id);
if (user != null)
{
// 写入缓存
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
SlidingExpiration = TimeSpan.FromMinutes(10)
};
var json = JsonSerializer.Serialize(user);
await _cache.SetStringAsync(cacheKey, json, options);
}
return user;
}
/// <summary>
/// 删除缓存
/// </summary>
public async Task RemoveCache(string key)
{
await _cache.RemoveAsync(key);
}
/// <summary>
/// 刷新缓存(重置滑动过期时间)
/// </summary>
public async Task RefreshCache(string key)
{
await _cache.RefreshAsync(key);
}
}
10.3.3 分布式缓存扩展方法
/// <summary>
/// 分布式缓存扩展方法
/// </summary>
public static class DistributedCacheExtensions
{
/// <summary>
/// 获取或创建缓存(泛型版本)
/// </summary>
public static async Task<T> GetOrCreateAsync<T>(
this IDistributedCache cache,
string key,
Func<Task<T>> factory,
DistributedCacheEntryOptions options = null) where T : class
{
var cachedData = await cache.GetStringAsync(key);
if (!string.IsNullOrEmpty(cachedData))
{
return JsonSerializer.Deserialize<T>(cachedData);
}
var data = await factory();
if (data != null)
{
options ??= new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
};
var json = JsonSerializer.Serialize(data);
await cache.SetStringAsync(key, json, options);
}
return data;
}
/// <summary>
/// 设置对象到缓存
/// </summary>
public static async Task SetObjectAsync<T>(
this IDistributedCache cache,
string key,
T value,
DistributedCacheEntryOptions options = null)
{
options ??= new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
};
var json = JsonSerializer.Serialize(value);
await cache.SetStringAsync(key, json, options);
}
/// <summary>
/// 从缓存获取对象
/// </summary>
public static async Task<T> GetObjectAsync<T>(
this IDistributedCache cache,
string key) where T : class
{
var cachedData = await cache.GetStringAsync(key);
return string.IsNullOrEmpty(cachedData) ? null : JsonSerializer.Deserialize<T>(cachedData);
}
}
10.4 Redis 缓存集成
10.4.1 安装与配置
# 安装 Redis 缓存包
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
dotnet add package StackExchange.Redis
// Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
options.InstanceName = "FurionApp:";
});
appsettings.json 配置:
{
"ConnectionStrings": {
"Redis": "localhost:6379,password=your_password,defaultDatabase=0,ssl=false,abortConnect=false,connectTimeout=5000"
}
}
10.4.2 Redis 连接管理
using StackExchange.Redis;
/// <summary>
/// Redis 连接管理(单例)
/// </summary>
public class RedisConnectionManager : IDisposable
{
private readonly Lazy<ConnectionMultiplexer> _connection;
public RedisConnectionManager(IConfiguration configuration)
{
var connectionString = configuration.GetConnectionString("Redis");
_connection = new Lazy<ConnectionMultiplexer>(() =>
ConnectionMultiplexer.Connect(connectionString));
}
public IDatabase GetDatabase(int db = -1) => _connection.Value.GetDatabase(db);
public IServer GetServer(string host, int port) => _connection.Value.GetServer(host, port);
public ISubscriber GetSubscriber() => _connection.Value.GetSubscriber();
public void Dispose()
{
if (_connection.IsValueCreated)
{
_connection.Value.Dispose();
}
}
}
// 注册 Redis 连接管理
builder.Services.AddSingleton<RedisConnectionManager>();
10.4.3 Redis 缓存服务
using StackExchange.Redis;
using System.Text.Json;
/// <summary>
/// Redis 缓存服务
/// </summary>
public class RedisCacheService : IDynamicApiController
{
private readonly IDatabase _redis;
private readonly ILogger<RedisCacheService> _logger;
public RedisCacheService(
RedisConnectionManager connectionManager,
ILogger<RedisCacheService> logger)
{
_redis = connectionManager.GetDatabase();
_logger = logger;
}
/// <summary>
/// 设置字符串缓存
/// </summary>
public async Task SetString(string key, string value, int expireMinutes = 30)
{
await _redis.StringSetAsync(key, value, TimeSpan.FromMinutes(expireMinutes));
}
/// <summary>
/// 获取字符串缓存
/// </summary>
public async Task<string> GetString(string key)
{
return await _redis.StringGetAsync(key);
}
/// <summary>
/// 设置对象缓存
/// </summary>
public async Task SetObject<T>(string key, T value, int expireMinutes = 30)
{
var json = JsonSerializer.Serialize(value);
await _redis.StringSetAsync(key, json, TimeSpan.FromMinutes(expireMinutes));
}
/// <summary>
/// 获取对象缓存
/// </summary>
public async Task<T> GetObject<T>(string key) where T : class
{
var value = await _redis.StringGetAsync(key);
return value.HasValue ? JsonSerializer.Deserialize<T>(value) : null;
}
/// <summary>
/// 删除缓存
/// </summary>
public async Task<bool> Remove(string key)
{
return await _redis.KeyDeleteAsync(key);
}
/// <summary>
/// 批量删除(按前缀)
/// </summary>
public async Task RemoveByPrefix(string prefix)
{
var server = _redis.Multiplexer.GetServer(_redis.Multiplexer.GetEndPoints().First());
var keys = server.Keys(pattern: $"{prefix}*").ToArray();
if (keys.Length > 0)
{
await _redis.KeyDeleteAsync(keys);
_logger.LogInformation("批量删除缓存,前缀:{Prefix},数量:{Count}", prefix, keys.Length);
}
}
/// <summary>
/// 检查缓存是否存在
/// </summary>
public async Task<bool> Exists(string key)
{
return await _redis.KeyExistsAsync(key);
}
/// <summary>
/// 设置过期时间
/// </summary>
public async Task<bool> SetExpire(string key, TimeSpan expiry)
{
return await _redis.KeyExpireAsync(key, expiry);
}
/// <summary>
/// 原子递增
/// </summary>
public async Task<long> Increment(string key, long value = 1)
{
return await _redis.StringIncrementAsync(key, value);
}
/// <summary>
/// 原子递减
/// </summary>
public async Task<long> Decrement(string key, long value = 1)
{
return await _redis.StringDecrementAsync(key, value);
}
/// <summary>
/// Hash 操作 - 设置
/// </summary>
public async Task HashSet(string key, string field, string value)
{
await _redis.HashSetAsync(key, field, value);
}
/// <summary>
/// Hash 操作 - 获取
/// </summary>
public async Task<string> HashGet(string key, string field)
{
return await _redis.HashGetAsync(key, field);
}
/// <summary>
/// Hash 操作 - 获取所有字段
/// </summary>
public async Task<Dictionary<string, string>> HashGetAll(string key)
{
var entries = await _redis.HashGetAllAsync(key);
return entries.ToDictionary(e => e.Name.ToString(), e => e.Value.ToString());
}
/// <summary>
/// 分布式锁
/// </summary>
public async Task<bool> LockTake(string lockKey, string lockValue, TimeSpan expiry)
{
return await _redis.LockTakeAsync(lockKey, lockValue, expiry);
}
/// <summary>
/// 释放分布式锁
/// </summary>
public async Task<bool> LockRelease(string lockKey, string lockValue)
{
return await _redis.LockReleaseAsync(lockKey, lockValue);
}
}
10.5 缓存键管理
10.5.1 缓存键命名规范
/// <summary>
/// 缓存键管理(统一定义缓存键)
/// </summary>
public static class CacheKeys
{
/// <summary>
/// 键前缀
/// </summary>
private const string Prefix = "furion";
// 用户相关
public static string UserInfo(int userId) => $"{Prefix}:user:info:{userId}";
public static string UserPermissions(int userId) => $"{Prefix}:user:permissions:{userId}";
public static string UserToken(int userId) => $"{Prefix}:user:token:{userId}";
public static string UserList => $"{Prefix}:user:list";
// 商品相关
public static string ProductDetail(int productId) => $"{Prefix}:product:detail:{productId}";
public static string ProductList(string category) => $"{Prefix}:product:list:{category}";
public static string HotProducts => $"{Prefix}:product:hot";
// 系统配置
public static string SystemConfig(string key) => $"{Prefix}:config:{key}";
public static string DictData(string dictType) => $"{Prefix}:dict:{dictType}";
// 验证码
public static string CaptchaCode(string captchaId) => $"{Prefix}:captcha:{captchaId}";
// 速率限制
public static string RateLimit(string clientId, string api) => $"{Prefix}:ratelimit:{clientId}:{api}";
// 分布式锁
public static string Lock(string resource) => $"{Prefix}:lock:{resource}";
}
10.5.2 缓存键使用示例
public class UserCacheService : IDynamicApiController
{
private readonly IDistributedCache _cache;
private readonly IRepository<User> _userRepo;
public UserCacheService(IDistributedCache cache, IRepository<User> userRepo)
{
_cache = cache;
_userRepo = userRepo;
}
public async Task<User> GetUser(int userId)
{
var cacheKey = CacheKeys.UserInfo(userId);
return await _cache.GetOrCreateAsync(cacheKey, async () =>
{
return await _userRepo.FindAsync(userId);
});
}
public async Task<List<string>> GetUserPermissions(int userId)
{
var cacheKey = CacheKeys.UserPermissions(userId);
return await _cache.GetOrCreateAsync(cacheKey, async () =>
{
return await _userRepo.Entities
.Where(u => u.Id == userId)
.SelectMany(u => u.Roles)
.SelectMany(r => r.Permissions)
.Select(p => p.Code)
.Distinct()
.ToListAsync();
}, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60)
});
}
/// <summary>
/// 用户信息变更时清除相关缓存
/// </summary>
public async Task InvalidateUserCache(int userId)
{
await _cache.RemoveAsync(CacheKeys.UserInfo(userId));
await _cache.RemoveAsync(CacheKeys.UserPermissions(userId));
await _cache.RemoveAsync(CacheKeys.UserToken(userId));
}
}
10.6 缓存失效策略
| 策略 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| 定时过期 | 到达指定时间后自动失效 | 实现简单 | 可能存在数据不一致窗口 |
| 主动失效 | 数据变更时主动删除缓存 | 数据一致性好 | 需要在所有变更点处理 |
| 延迟双删 | 先删缓存、更新数据库、延迟后再删缓存 | 减少不一致窗口 | 实现稍复杂 |
| 版本号 | 通过版本号判断缓存是否有效 | 灵活性高 | 需要额外存储版本号 |
| 发布订阅 | 通过消息通知各节点失效缓存 | 适合分布式环境 | 依赖消息中间件 |
/// <summary>
/// 延迟双删策略示例
/// </summary>
public class DoubleDeleteCacheService
{
private readonly IDistributedCache _cache;
private readonly IRepository<Product> _productRepo;
public DoubleDeleteCacheService(IDistributedCache cache, IRepository<Product> productRepo)
{
_cache = cache;
_productRepo = productRepo;
}
public async Task UpdateProduct(int id, ProductUpdateInput input)
{
var cacheKey = CacheKeys.ProductDetail(id);
// 第一次删除缓存
await _cache.RemoveAsync(cacheKey);
// 更新数据库
var product = await _productRepo.FindAsync(id);
product.Name = input.Name;
product.Price = input.Price;
await _productRepo.UpdateAsync(product);
await _productRepo.SaveNowAsync();
// 延迟后再次删除缓存(处理并发场景下的脏读)
_ = Task.Run(async () =>
{
await Task.Delay(500); // 延迟500ms
await _cache.RemoveAsync(cacheKey);
});
}
}
10.7 多级缓存
10.7.1 两级缓存实现
/// <summary>
/// 多级缓存服务(L1: 内存缓存, L2: Redis 缓存)
/// </summary>
public class MultiLevelCacheService
{
private readonly IMemoryCache _l1Cache; // 一级缓存:内存
private readonly IDistributedCache _l2Cache; // 二级缓存:Redis
private readonly ILogger<MultiLevelCacheService> _logger;
public MultiLevelCacheService(
IMemoryCache l1Cache,
IDistributedCache l2Cache,
ILogger<MultiLevelCacheService> logger)
{
_l1Cache = l1Cache;
_l2Cache = l2Cache;
_logger = logger;
}
/// <summary>
/// 获取缓存数据
/// </summary>
public async Task<T> GetAsync<T>(string key, Func<Task<T>> factory,
TimeSpan? l1Expiry = null, TimeSpan? l2Expiry = null) where T : class
{
// 1. 先查一级缓存(内存)
if (_l1Cache.TryGetValue(key, out T value))
{
_logger.LogDebug("L1缓存命中:{Key}", key);
return value;
}
// 2. 再查二级缓存(Redis)
var l2Value = await _l2Cache.GetObjectAsync<T>(key);
if (l2Value != null)
{
_logger.LogDebug("L2缓存命中:{Key}", key);
// 回写到一级缓存
_l1Cache.Set(key, l2Value, l1Expiry ?? TimeSpan.FromMinutes(5));
return l2Value;
}
// 3. 都未命中,从数据源获取
_logger.LogDebug("缓存未命中,从数据源加载:{Key}", key);
value = await factory();
if (value != null)
{
// 写入两级缓存
_l1Cache.Set(key, value, l1Expiry ?? TimeSpan.FromMinutes(5));
await _l2Cache.SetObjectAsync(key, value,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = l2Expiry ?? TimeSpan.FromMinutes(30)
});
}
return value;
}
/// <summary>
/// 删除多级缓存
/// </summary>
public async Task RemoveAsync(string key)
{
_l1Cache.Remove(key);
await _l2Cache.RemoveAsync(key);
_logger.LogDebug("多级缓存已删除:{Key}", key);
}
/// <summary>
/// 刷新缓存
/// </summary>
public async Task RefreshAsync<T>(string key, Func<Task<T>> factory,
TimeSpan? l1Expiry = null, TimeSpan? l2Expiry = null) where T : class
{
var value = await factory();
if (value != null)
{
_l1Cache.Set(key, value, l1Expiry ?? TimeSpan.FromMinutes(5));
await _l2Cache.SetObjectAsync(key, value,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = l2Expiry ?? TimeSpan.FromMinutes(30)
});
}
}
}
// 注册多级缓存服务
builder.Services.AddSingleton<MultiLevelCacheService>();
10.8 缓存穿透/击穿/雪崩防护
10.8.1 缓存穿透防护
缓存穿透是指查询一个不存在的数据,缓存和数据库都不命中,导致每次请求都直接打到数据库。
/// <summary>
/// 缓存穿透防护 - 布隆过滤器 + 空值缓存
/// </summary>
public class CachePenetrationGuard
{
private readonly IDistributedCache _cache;
private readonly IRepository<Product> _productRepo;
// 空值缓存标识
private const string NullCacheValue = "__NULL__";
public CachePenetrationGuard(IDistributedCache cache, IRepository<Product> productRepo)
{
_cache = cache;
_productRepo = productRepo;
}
/// <summary>
/// 防穿透查询
/// </summary>
public async Task<Product> GetProduct(int id)
{
var cacheKey = CacheKeys.ProductDetail(id);
var cachedData = await _cache.GetStringAsync(cacheKey);
// 如果缓存中存储了空值标识,直接返回 null
if (cachedData == NullCacheValue)
{
return null;
}
// 缓存命中
if (!string.IsNullOrEmpty(cachedData))
{
return JsonSerializer.Deserialize<Product>(cachedData);
}
// 缓存未命中,查数据库
var product = await _productRepo.FindAsync(id);
if (product != null)
{
// 正常缓存
await _cache.SetStringAsync(cacheKey,
JsonSerializer.Serialize(product),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
});
}
else
{
// 缓存空值(短过期时间)
await _cache.SetStringAsync(cacheKey, NullCacheValue,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2)
});
}
return product;
}
}
10.8.2 缓存击穿防护
缓存击穿是指某个热点 key 在过期的瞬间有大量并发请求,全部打到数据库。
using System.Collections.Concurrent;
/// <summary>
/// 缓存击穿防护 - 互斥锁
/// </summary>
public class CacheBreakdownGuard
{
private readonly IDistributedCache _cache;
private readonly IRepository<Product> _productRepo;
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new();
public CacheBreakdownGuard(IDistributedCache cache, IRepository<Product> productRepo)
{
_cache = cache;
_productRepo = productRepo;
}
/// <summary>
/// 防击穿查询(互斥锁方案)
/// </summary>
public async Task<Product> GetHotProduct(int id)
{
var cacheKey = CacheKeys.ProductDetail(id);
// 先尝试从缓存获取
var cachedData = await _cache.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(cachedData))
{
return JsonSerializer.Deserialize<Product>(cachedData);
}
// 获取互斥锁
var lockObj = _locks.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await lockObj.WaitAsync();
try
{
// 双重检查 - 可能其他线程已经加载了数据
cachedData = await _cache.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(cachedData))
{
return JsonSerializer.Deserialize<Product>(cachedData);
}
// 查询数据库
var product = await _productRepo.FindAsync(id);
if (product != null)
{
await _cache.SetStringAsync(cacheKey,
JsonSerializer.Serialize(product),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
});
}
return product;
}
finally
{
lockObj.Release();
}
}
}
10.8.3 缓存雪崩防护
缓存雪崩是指大量缓存同时过期,导致大量请求直接打到数据库。
/// <summary>
/// 缓存雪崩防护 - 随机过期时间
/// </summary>
public class CacheAvalancheGuard
{
private readonly IDistributedCache _cache;
private static readonly Random _random = new();
public CacheAvalancheGuard(IDistributedCache cache)
{
_cache = cache;
}
/// <summary>
/// 设置缓存(带随机过期时间,防止雪崩)
/// </summary>
public async Task SetWithRandomExpiry<T>(string key, T value, int baseMinutes = 30)
{
// 在基础过期时间上增加随机偏移(0-10分钟)
var randomOffset = _random.Next(0, 10);
var expiry = TimeSpan.FromMinutes(baseMinutes + randomOffset);
var json = JsonSerializer.Serialize(value);
await _cache.SetStringAsync(key, json, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiry
});
}
/// <summary>
/// 批量预热缓存(分散过期时间)
/// </summary>
public async Task WarmupCache<T>(Dictionary<string, T> dataMap, int baseMinutes = 30)
{
var tasks = dataMap.Select(kvp =>
{
var randomOffset = _random.Next(0, baseMinutes / 2);
return SetWithRandomExpiry(kvp.Key, kvp.Value, baseMinutes + randomOffset);
});
await Task.WhenAll(tasks);
}
}
10.9 缓存使用最佳实践
10.9.1 最佳实践清单
| 实践项 | 说明 |
|---|---|
| 统一键命名 | 使用统一的缓存键命名规范,如 app:module:entity:id |
| 合理设置过期时间 | 根据数据变化频率设置合适的过期时间 |
| 避免大对象缓存 | 缓存数据不宜过大,必要时进行压缩 |
| 缓存预热 | 应用启动时预加载热点数据 |
| 监控命中率 | 监控缓存命中率,优化缓存策略 |
| 序列化选择 | 选择高效的序列化方式(如 MessagePack) |
| 防护策略 | 针对穿透、击穿、雪崩设计防护方案 |
| 数据一致性 | 制定缓存与数据库的一致性保障策略 |
10.9.2 缓存预热
using Microsoft.Extensions.Hosting;
/// <summary>
/// 缓存预热服务(应用启动时自动执行)
/// </summary>
public class CacheWarmupService : IHostedService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<CacheWarmupService> _logger;
public CacheWarmupService(
IServiceProvider serviceProvider,
ILogger<CacheWarmupService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("开始缓存预热...");
using var scope = _serviceProvider.CreateScope();
var cache = scope.ServiceProvider.GetRequiredService<IDistributedCache>();
var productRepo = scope.ServiceProvider.GetRequiredService<IRepository<Product>>();
var configRepo = scope.ServiceProvider.GetRequiredService<IRepository<SystemConfig>>();
try
{
// 预热热门商品
var hotProducts = await productRepo.Entities
.OrderByDescending(p => p.SalesCount)
.Take(100)
.ToListAsync();
foreach (var product in hotProducts)
{
await cache.SetObjectAsync(
CacheKeys.ProductDetail(product.Id),
product,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2)
});
}
// 预热系统配置
var configs = await configRepo.Entities.ToListAsync();
foreach (var config in configs)
{
await cache.SetStringAsync(
CacheKeys.SystemConfig(config.Key),
config.Value,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24)
});
}
_logger.LogInformation(
"缓存预热完成,预热商品 {ProductCount} 个,配置 {ConfigCount} 条",
hotProducts.Count, configs.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "缓存预热失败");
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
// 注册预热服务
builder.Services.AddHostedService<CacheWarmupService>();
10.10 性能优化
10.10.1 序列化性能优化
using System.Text.Json;
/// <summary>
/// 高性能缓存序列化配置
/// </summary>
public static class CacheSerializerOptions
{
/// <summary>
/// 高性能 JSON 序列化选项
/// </summary>
public static readonly JsonSerializerOptions HighPerformance = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false, // 不格式化,减少空间占用
PropertyNameCaseInsensitive = true
};
/// <summary>
/// 序列化为字节数组(比字符串更高效)
/// </summary>
public static byte[] SerializeToBytes<T>(T value)
{
return JsonSerializer.SerializeToUtf8Bytes(value, HighPerformance);
}
/// <summary>
/// 从字节数组反序列化
/// </summary>
public static T DeserializeFromBytes<T>(byte[] bytes)
{
return JsonSerializer.Deserialize<T>(bytes, HighPerformance);
}
}
10.10.2 缓存性能监控
/// <summary>
/// 缓存性能统计
/// </summary>
public class CacheMetrics
{
private long _hitCount;
private long _missCount;
private long _setCount;
private long _removeCount;
public long HitCount => _hitCount;
public long MissCount => _missCount;
public long SetCount => _setCount;
public long RemoveCount => _removeCount;
public double HitRate => _hitCount + _missCount == 0
? 0
: (double)_hitCount / (_hitCount + _missCount) * 100;
public void RecordHit() => Interlocked.Increment(ref _hitCount);
public void RecordMiss() => Interlocked.Increment(ref _missCount);
public void RecordSet() => Interlocked.Increment(ref _setCount);
public void RecordRemove() => Interlocked.Increment(ref _removeCount);
public object GetStatistics() => new
{
HitCount = _hitCount,
MissCount = _missCount,
SetCount = _setCount,
RemoveCount = _removeCount,
HitRate = $"{HitRate:F2}%",
TotalRequests = _hitCount + _missCount
};
public void Reset()
{
Interlocked.Exchange(ref _hitCount, 0);
Interlocked.Exchange(ref _missCount, 0);
Interlocked.Exchange(ref _setCount, 0);
Interlocked.Exchange(ref _removeCount, 0);
}
}
// 注册缓存指标(单例)
builder.Services.AddSingleton<CacheMetrics>();
// 查看缓存统计的接口
public class CacheMonitorService : IDynamicApiController
{
private readonly CacheMetrics _metrics;
public CacheMonitorService(CacheMetrics metrics)
{
_metrics = metrics;
}
/// <summary>
/// 获取缓存统计信息
/// </summary>
public object GetCacheStatistics()
{
return _metrics.GetStatistics();
}
/// <summary>
/// 重置统计信息
/// </summary>
public void ResetStatistics()
{
_metrics.Reset();
}
}
10.10.3 性能优化总结
| 优化项 | 策略 | 预期效果 |
|---|---|---|
| 序列化 | 使用 UTF-8 字节序列化 | 减少 CPU 和内存开销 |
| 压缩 | 大对象使用 GZip 压缩 | 减少网络传输和存储空间 |
| 批量操作 | 使用 Pipeline 批量读写 | 减少网络往返次数 |
| 本地缓存 | L1 内存 + L2 Redis | 减少 Redis 网络请求 |
| 连接池 | 复用 Redis 连接 | 减少连接建立开销 |
| 异步操作 | 使用 async/await | 提高线程利用率 |
| 合理过期 | 根据场景设置过期时间 | 平衡一致性和性能 |
| 缓存预热 | 启动时加载热点数据 | 减少冷启动影响 |