如何在ASP.NET Core 9中通过内置混合缓存按前缀删除缓存条目?
基于ASP.NET Core内置缓存的前缀式键删除最优方案
ASP.NET Core内置缓存分为内存缓存(IMemoryCache)和分布式缓存(IDistributedCache)两类,由于原生接口未提供直接的前缀删除能力,最优方案核心是通过维护前缀与缓存键的映射关系实现精准批量删除,以下分场景说明:
一、内存缓存(IMemoryCache)实现方案
核心思路
用线程安全集合(如ConcurrentDictionary<string, HashSet<string>>)记录每个前缀对应的所有缓存键,删除时遍历对应集合的键执行删除;同时利用缓存过期回调自动清理映射集合中的无效键,避免内存泄漏。
代码实现
public class MemoryCachePrefixManager { private readonly IMemoryCache _cache; private readonly ConcurrentDictionary<string, HashSet<string>> _prefixKeyMap = new(); public MemoryCachePrefixManager(IMemoryCache cache) { _cache = cache; } // 添加缓存时同步记录前缀与键的映射 public void AddWithPrefix(string prefix, string key, object value, MemoryCacheEntryOptions? options = null) { options ??= new MemoryCacheEntryOptions(); // 缓存过期/移除时同步更新映射集合 options.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration { EvictionCallback = (entryKey, _, reason, _) => { if (entryKey is string k && _prefixKeyMap.TryGetValue(prefix, out var keys)) { lock (keys) { keys.Remove(k); if (keys.Count == 0) { _prefixKeyMap.TryRemove(prefix, out _); } } } } }); _cache.Set(key, value, options); // 更新前缀-键映射 if (_prefixKeyMap.TryGetValue(prefix, out var keySet)) { lock (keySet) { keySet.Add(key); } } else { _prefixKeyMap.TryAdd(prefix, new HashSet<string> { key }); } } // 根据前缀删除所有对应缓存 public void RemoveByPrefix(string prefix) { if (_prefixKeyMap.TryGetValue(prefix, out var keys)) { List<string> keysToRemove; lock (keys) { keysToRemove = keys.ToList(); keys.Clear(); } foreach (var key in keysToRemove) { _cache.Remove(key); } _prefixKeyMap.TryRemove(prefix, out _); } } }
使用方式
注册服务(Program.cs/Startup.cs):
builder.Services.AddMemoryCache(); builder.Services.AddSingleton<MemoryCachePrefixManager>();
业务代码调用:
// 添加带前缀的缓存 _cachePrefixManager.AddWithPrefix("user-picture-", "user-picture-1", pictureData); _cachePrefixManager.AddWithPrefix("user-picture-", "user-picture-2", anotherPictureData); // 删除前缀为user-picture-的所有缓存 _cachePrefixManager.RemoveByPrefix("user-picture-");
二、分布式缓存(IDistributedCache)实现方案
核心思路
与内存缓存逻辑一致,但需将前缀-键的映射关系存储在分布式缓存中(序列化为集合),同时通过分布式锁保证多实例场景下的操作原子性。
代码实现
public class DistributedCachePrefixManager { private readonly IDistributedCache _cache; private readonly string _prefixMapKeyPrefix = "cache_prefix_map_"; public DistributedCachePrefixManager(IDistributedCache cache) { _cache = cache; } // 添加缓存时同步记录前缀与键的映射 public async Task AddWithPrefixAsync(string prefix, string key, object value, DistributedCacheEntryOptions? options = null) { options ??= new DistributedCacheEntryOptions(); // 序列化并存储缓存值 var jsonValue = JsonSerializer.Serialize(value); await _cache.SetStringAsync(key, jsonValue, options); // 更新前缀-键映射(分布式锁避免并发冲突) var mapKey = $"{_prefixMapKeyPrefix}{prefix}"; using var lockObj = new SemaphoreSlim(1, 1); await lockObj.WaitAsync(); try { var existingKeysJson = await _cache.GetStringAsync(mapKey); var keys = existingKeysJson != null ? JsonSerializer.Deserialize<HashSet<string>>(existingKeysJson) : new HashSet<string>(); keys.Add(key); await _cache.SetStringAsync(mapKey, JsonSerializer.Serialize(keys)); } finally { lockObj.Release(); } } // 根据前缀删除所有对应缓存 public async Task RemoveByPrefixAsync(string prefix) { var mapKey = $"{_prefixMapKeyPrefix}{prefix}"; using var lockObj = new SemaphoreSlim(1, 1); await lockObj.WaitAsync(); try { var existingKeysJson = await _cache.GetStringAsync(mapKey); if (existingKeysJson == null) return; var keys = JsonSerializer.Deserialize<HashSet<string>>(existingKeysJson); if (keys == null || keys.Count == 0) return; // 批量删除缓存条目 foreach (var key in keys) { await _cache.RemoveAsync(key); } // 删除前缀映射条目 await _cache.RemoveAsync(mapKey); } finally { lockObj.Release(); } } }
使用方式
注册服务(以Redis分布式缓存为例):
builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = builder.Configuration.GetConnectionString("Redis"); }); builder.Services.AddSingleton<DistributedCachePrefixManager>();
业务代码调用:
// 添加带前缀的缓存 await _distCachePrefixManager.AddWithPrefixAsync("user-picture-", "user-picture-1", pictureData); await _distCachePrefixManager.AddWithPrefixAsync("user-picture-", "user-picture-2", anotherPictureData); // 删除前缀缓存 await _distCachePrefixManager.RemoveByPrefixAsync("user-picture-");
方案优势
- 完全基于内置组件:不依赖第三方缓存扩展库,符合需求约束;
- 性能高效:避免扫描全量缓存键的开销,精准定位目标条目;
- 状态一致:内存缓存通过过期回调维护映射有效性,分布式缓存通过锁保证原子操作;
- 易扩展:可根据业务需求添加缓存过期时间、批量操作优化等功能。
内容的提问来源于stack exchange,提问作者nima ansari
相关产品推荐
相关产品推荐

