ASP.NET Core 支持多个不同的缓存。 最简单的缓存基于 IMemoryCache。
IMemoryCache
表示存储在 Web 服务器内存中的缓存。 在服务器场(多个服务器)中运行的应用应确保在使用内存中缓存时会话是粘滞的。 粘滞会话可确保来自客户端的请求都转到同一服务器。 例如,Azure Web 应用使用应用程序请求路由 (ARR) 将所有请求路由到同一服务器。Web 场中的非粘滞会话需要分布式缓存(如 Redis)来避免缓存一致性问题。
.NET 中有两个MemoryCache类:
- System.Runtime.Caching.MemoryCache
已非推荐, .NET Standard 2.0 或更高版本。使用场景比如:将代码从 ASP.NET 4.x 移植到 ASP.NET Core 时,使用System.Runtime.Caching
/MemoryCache
作为兼容性桥。 - Microsoft.Extensions.Caching.Memory.MemoryCache
推荐使用,因为它更好地集成到 ASP.NET Core 中。 MemoryCache实现IMemoryCache
引自官方说明:ASP.NET Core 中的内存中缓存 | Microsoft Learn
.NET Core中使用MemoryCache
调用自带的AddMemoryCache扩展方法,将IMemoryCache注入容器中,后面便可通过容器获取IMemoryCache的实现MemoryCache对本机内存缓存进行操纵
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache(); //会注入IMemoryCache的实现类MemoryCache
using IHost host = builder.Build();
封装内存缓存工具类
以下给出代码,通过对 ICacheTool接口 的两种实现,分别封装“本地MemoryCache”和“分布式Redis”两种方案的内存缓存实现,项目中根据需要选择一种作为 ICacheTool 的实现注入依赖容器即可。
1. MemoryCacheTool:
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ZhonTai.Common.Extensions;namespace ZhonTai.Admin.Tools.Cache;/// <summary>
/// 内存缓存
/// </summary>
public partial class MemoryCacheTool : ICacheTool
{private static readonly string PatternRegex = @"\{.*\}";private readonly IMemoryCache _memoryCache;public MemoryCacheTool(IMemoryCache memoryCache){_memoryCache = memoryCache;}public List<string> Keys => GetAllKeys();public long Del(params string[] key){foreach (var k in key){_memoryCache.Remove(k);}return key.Length;}public Task<long> DelAsync(params string[] key){foreach (var k in key){_memoryCache.Remove(k);}return Task.FromResult(key.Length.ToLong());}public async Task<long> DelByPatternAsync(string pattern){if (pattern.IsNull())return default;pattern = Regex.Replace(pattern, PatternRegex, "(.*)");var keys = GetAllKeys().Where(k => Regex.IsMatch(k, pattern));if (keys != null && keys.Count() > 0){return await DelAsync(keys.ToArray());}return default;}public bool Exists(string key){return _memoryCache.TryGetValue(key, out _);}public Task<bool> ExistsAsync(string key){return Task.FromResult(_memoryCache.TryGetValue(key, out _));}public string Get(string key){return _memoryCache.Get(key)?.ToString();}public T Get<T>(string key){return _memoryCache.Get<T>(key);}public Task<string> GetAsync(string key){return Task.FromResult(Get(key));}public Task<T> GetAsync<T>(string key){return Task.FromResult(Get<T>(key));}public void Set(string key, object value){_memoryCache.Set(key, value);}public void Set(string key, object value, TimeSpan expire){_memoryCache.Set(key, value, expire);}public Task SetAsync(string key, object value, TimeSpan? expire = null){if(expire.HasValue){Set(key, value, expire.Value);}else{Set(key, value);}return Task.CompletedTask;}public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> func, TimeSpan? expire = null){if (await ExistsAsync(key)){try{return await GetAsync<T>(key);}catch{await DelAsync(key);}}var result = await func.Invoke();if (expire.HasValue){await SetAsync(key, result, expire.Value);}else{await SetAsync(key, result);}return result;}private List<string> GetAllKeys(){const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;var coherentState = _memoryCache.GetType().GetField("_coherentState", flags).GetValue(_memoryCache);var entries = coherentState.GetType().GetField("_entries", flags).GetValue(coherentState);var cacheItems = entries as IDictionary;var keys = new List<string>();if (cacheItems == null) return keys;foreach (DictionaryEntry cacheItem in cacheItems){keys.Add(cacheItem.Key.ToString());}return keys;}public List<string> GetKeysByPattern(string pattern){return GetAllKeys().Where(k => Regex.IsMatch(k, pattern)).ToList();}
}
2. RedisCacheTool:
using FreeRedis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ZhonTai.Common.Extensions;namespace ZhonTai.Admin.Tools.Cache;/// <summary>
/// Redis缓存
/// </summary>
public partial class RedisCacheTool : ICacheTool
{private static readonly string PatternRegex = @"\{.*\}";private readonly RedisClient _redisClient;public List<string> Keys => _redisClient.Keys("*").ToList();public RedisCacheTool(RedisClient redisClient){_redisClient = redisClient;}public long Del(params string[] key){return _redisClient.Del(key);}public Task<long> DelAsync(params string[] key){return _redisClient.DelAsync(key);}public async Task<long> DelByPatternAsync(string pattern){if (pattern.IsNull())return default;pattern = Regex.Replace(pattern, PatternRegex, "*");var keys = await _redisClient.KeysAsync(pattern);if (keys != null && keys.Length > 0){return await _redisClient.DelAsync(keys);}return default;}public bool Exists(string key){return _redisClient.Exists(key);}public Task<bool> ExistsAsync(string key){return _redisClient.ExistsAsync(key);}public string Get(string key){return _redisClient.Get(key);}public T Get<T>(string key){return _redisClient.Get<T>(key);}public Task<string> GetAsync(string key){return _redisClient.GetAsync(key);}public Task<T> GetAsync<T>(string key){return _redisClient.GetAsync<T>(key);}public void Set(string key, object value){_redisClient.Set(key, value);}public void Set(string key, object value, TimeSpan expire){_redisClient.Set(key, value, expire);}public Task SetAsync(string key, object value, TimeSpan? expire = null){return _redisClient.SetAsync(key, value, expire.HasValue ? expire.Value.TotalSeconds.ToInt() : 0);}public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> func, TimeSpan? expire = null){if (await _redisClient.ExistsAsync(key)){try{return await _redisClient.GetAsync<T>(key);}catch{await _redisClient.DelAsync(key);}}var result = await func.Invoke();await _redisClient.SetAsync(key, result, expire.HasValue ? expire.Value.TotalSeconds.ToInt() : 0);return result;}public List<string> GetKeysByPattern(string pattern){return _redisClient.Keys(pattern).ToList();}
}
3. 根据配置选择其中一种内存缓存方案,作为ICacheTool接口的实现注入容器
...//缓存操作类相关注册
var cacheConfig = AppInfo.GetOptions<CacheConfig>(); //获取缓存相关配置//调用自带的AddMemoryCache扩展方法,将IMemoryCache注入容器(用于MemoryCache作为内存缓存的方案)
services.AddMemoryCache();if (cacheConfig.Type == CacheType.Redis)
{//【要使用Redis作为内存缓存的实现方案】//FreeRedis客户端var redis = new RedisClient(cacheConfig.Redis.ConnectionString){Serialize = JsonConvert.SerializeObject,Deserialize = JsonConvert.DeserializeObject};services.AddSingleton(redis);services.AddSingleton<IRedisClient>(redis);//Redis缓存services.AddSingleton<ICacheTool, RedisCacheTool>();//分布式Redis缓存services.AddSingleton<IDistributedCache>(new DistributedCache(redis));if(_hostAppOptions?.ConfigureIdGenerator != null){_hostAppOptions?.ConfigureIdGenerator?.Invoke(appConfig.IdGenerator);YitIdHelper.SetIdGenerator(appConfig.IdGenerator);}else{//分布式Id生成器services.AddIdGenerator();}
}
else
{//【要使用MemoryCache作为内存缓存的实现方案】//内存缓存services.AddSingleton<ICacheTool, MemoryCacheTool>();//分布式内存缓存services.AddDistributedMemoryCache();//Id生成器_hostAppOptions?.ConfigureIdGenerator?.Invoke(appConfig.IdGenerator);YitIdHelper.SetIdGenerator(appConfig.IdGenerator);
}
4. CacheConfig参考配置信息:
{"CacheConfig": {//缓存类型 Memory = 0,Redis = 1"type": "Memory",//限流缓存类型 Memory = 0,Redis = 1"typeRateLimit": "Memory",//Redis配置"redis": {//连接字符串"connectionString": "127.0.0.1:6379,password=,defaultDatabase=1",//限流连接字符串"connectionStringRateLimit": "127.0.0.1:6379,password=,defaultDatabase=1"}}
}
相关知识记录:
封装System.Runtime.Caching.MemoryCache实现服务端缓存,以及在依赖注入中使用时要注意的坑-CSDN博客