.NET Core MemoryCache缓存批量获取Key或者删除
.Net Core下使用缓存,除了大家耳熟能详的Redis做分布式缓存外,本地内存缓存也会一起结合来使用,它存取更快,使我们的应用达到极致性能要求。这也是我们经常提到的3级或者4级缓存,每一层都有自己的使用场景,优缺点,结合业务特点来选择合适的才是王道。
这里我们就使用Net原生的 Microsoft.Extensions.Caching.Memory.IMemoryCache 来实现本机内存缓存的一些延展问题进行探讨。该接口实现可以参考源代码:
接口公开了几个常用的方法:
其中,根据缓存键值来清理缓存用的比较频繁,前提是要精确找到曾经使用的Key名称或者集合,再来逐个清理。业务上有时候我们希望能根据一些特征,批量把一批Key来移除掉,这个时候就找不到枚举缓存Key集合的方法了,网上有也有很多示例:
1 List<string> getCacheKeys() 2 { 3 BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; 4 var entries = _memoryCache.GetType().GetField("_entries", flags).GetValue(_memoryCache); 5 var cacheItems = entries.GetType().GetProperty("Keys").GetValue(entries) as ICollection<object>; 6 var keys = new List<string>(); 7 if (cacheItems == null || cacheItems.Count() == 0) 8 return keys; 9 10 return cacheItems.Select(r=>r.ToString()).ToList(); 11 }
但是执行时找不到"_entries"属性了,接口调整了内部逻辑,继续查找上述源代码发现:
1 ... 2 CoherentState coherentState = _coherentState; // Clear() can update the reference in the meantime 3 if (coherentState._entries.TryGetValue(entry.Key, out CacheEntry? priorEntry)) 4 { 5 priorEntry.SetExpired(EvictionReason.Replaced); 6 } 7 ...
说明"_entries"属性被移动到 “_coherentState”属性里面了,我们可以据此修改上述获取Key的代码:
1 public List<string> GetCacheKeys() 2 { 3 const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; 4 var coherentState = Cache.GetType().GetField("_coherentState", flags).GetValue(Cache); 5 var entries = coherentState.GetType().GetField("_entries", flags).GetValue(coherentState); 6 var cacheItems = entries as IDictionary; 7 var keys = new List<string>(); 8 if (cacheItems == null) return keys; 9 foreach (DictionaryEntry cacheItem in cacheItems) 10 { 11 keys.Add(cacheItem.Key.ToString()); 12 } 13 return keys; 14 }
先反射获取_coherentState 属性对象,再接着找里面的缓存键集合,如此就可以批量获取到Key集合了。若需要按照特殊前缀来模糊清理,可以类似下面写法实现:
1 /// <summary> 2 /// 清理指定前缀的键值内存缓存 3 /// </summary> 4 /// <param name="keyPrefix">平台字典缓存键</param> 5 public void CleanMemoryCache(string keyPrefix = "mck.dict") 6 { 7 var _memoryCache = Furion.App.GetRequiredService<IMemoryCache>(); 8 9 //以下反射发现所有内存缓存,一旦量大了,或者在分布式下,就不能去枚举,性能会严重下降 10 11 const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; 12 var coherentState = _memoryCache.GetType().GetField("_coherentState", flags).GetValue(_memoryCache); 13 var entries = coherentState.GetType().GetField("_entries", flags).GetValue(coherentState); 14 var cacheItems = entries as IDictionary; 15 16 foreach (DictionaryEntry cacheItem in cacheItems) 17 { 18 if (cacheItem.Key.ToString().StartsWith(keyPrefix)) 19 { 20 _memoryCache.Remove(cacheItem.Key.ToString()); 21 Logger.Debug($"Auto clean memory cache:{cacheItem.Key.ToString()}"); 22 } 23 } 24 }
请注意,使用反射来访问内部字段可能会受到版本更新的影响,因为这些字段可能会在不同的.NET Core版本中发生变化。因此,这种方法可能需要随着.NET Core的更新而进行调整。此外,由于这种方法涉及到内部实现,它可能会在未来的版本中被弃用或更改。在生产环境中,建议使用官方提供的API来操作缓存,以确保代码的稳定性和兼容性。
msn: pccai1983@hotmail.com