.Net Core中的内存缓存实现——Redis及MemoryCache(2个可选)方案的实现

ASP.NET Core 支持多个不同的缓存。 最简单的缓存基于 IMemoryCache。 IMemoryCache 表示存储在 Web 服务器内存中的缓存。 在服务器场(多个服务器)中运行的应用应确保在使用内存中缓存时会话是粘滞的。 粘滞会话可确保来自客户端的请求都转到同一服务器。 例如,Azure Web 应用使用应用程序请求路由 (ARR) 将所有请求路由到同一服务器。Web 场中的非粘滞会话需要分布式缓存(如 Redis)来避免缓存一致性问题。

.NET 中有两个MemoryCache类:

引自官方说明: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博客

  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值