【BCVP更新】StackExchange.Redis 的异步开发方式

2022-04-11 15:43:46 浏览数 (1)

各种小问题?

如果你之前用过Redis的话,肯定会使用过StackExchange.Redis,我之前很久就用过,在.netfwk的时候,当时并发还比较小,没有什么问题,后来我就迁移到Blog.Core里了,但是有很多小伙伴,反馈高并发下,使用同步的方法会有问题,比如超时的问题,偶尔还会出现什么内存的问题,一直被很多网友所诟病。

一直说国内有一个组件很不错,这个大家自己去使用吧,我也不多说什么,但是我想着StackExchange.Redis既然是官方推荐的不会这么菜吧,果然官方给的方案是,用异步的方式写,会解决超时的问题。

那具体应该怎么写呢,我还没有来得及思考,正好这两天研究微软的微服务案例eShopOnContainers,我发现他就是用的StackExchange.Redis实现的购物车缓存的子服务逻辑,研究了下,迁移到Blog.Core项目中,当然我这里简单测试了下,并发下没有问题,大家还是可以自行测试,当然还是那句话,有问题了我会继续修改,如果还是不行,那自己就换其他的组件吧。

设计异步方案

这个比较简单的,设计思路和之前的是一样的,只不过有一点,连接调制器的注入方式我做了调整(ConnectionMultiplexer),之前用的是双if夹lock的方式,实现的单例,现在直接使用依赖注入AddSingleton的方式,更专业些,也没那么幺蛾子:

代码语言:javascript复制
/// <summary>
/// Redis缓存 启动服务
/// </summary>
public static class RedisCacheSetup
{
    public static void AddRedisCacheSetup(this IServiceCollection services)
    {
        if (services == null) throw new ArgumentNullException(nameof(services));

        services.AddTransient<IRedisBasketRepository, RedisBasketRepository>();
        services.AddSingleton<ConnectionMultiplexer>(sp =>
        {
            //获取连接字符串
            string redisConfiguration = Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "ConnectionString" });
            var configuration = ConfigurationOptions.Parse(redisConfiguration, true);
            configuration.ResolveDns = true;
            return ConnectionMultiplexer.Connect(configuration);
        });
    }
}

这里是Redis依赖注入的扩展方法,需要在Startup里配置上,别忘记了:

services.AddRedisCacheSetup();

然后就是设计接口和实现类了,也很简单,接口和之前的一样,只不过都换成了异步:

代码语言:javascript复制
 /// <summary>
 /// Redis缓存接口
 /// </summary>
 public interface IRedisBasketRepository
 {

     //获取 Reids 缓存值
     Task<string> GetValue(string key);

     //获取值,并序列化
     Task<TEntity> Get<TEntity>(string key);

     //保存
     Task Set(string key, object value, TimeSpan cacheTime);

     //判断是否存在
     Task<bool> Exist(string key);

     //移除某一个缓存值
     Task Remove(string key);

     //全部清除
     Task Clear();
 }

接口的名称我为了纪念eShop项目,保持一致了,虽然比较怪,自己酌情修改吧。

然后实现类也很简单,构造函数直接注入日志和连接调制器实例,不用写复杂的单例模式了:

代码语言:javascript复制
 public class RedisBasketRepository : IRedisBasketRepository
 {
     private readonly ILogger<RedisBasketRepository> _logger;
     private readonly ConnectionMultiplexer _redis;
     private readonly IDatabase _database;

     public RedisBasketRepository(ILogger<RedisBasketRepository> logger, ConnectionMultiplexer redis)
     {
         _logger = logger;
         _redis = redis;
         _database = redis.GetDatabase();
     }

     private IServer GetServer()
     {
         var endpoint = _redis.GetEndPoints();
         return _redis.GetServer(endpoint.First());
     }

     public async Task Clear()
     {
         foreach (var endPoint in _redis.GetEndPoints())
         {
             var server = GetServer();
             foreach (var key in server.Keys())
             {
                 await _database.KeyDeleteAsync(key);
             }
         }
     }

     public async Task<bool> Exist(string key)
     {
         return await _database.KeyExistsAsync(key);
     }

     public async Task<string> GetValue(string key)
     {
         return await _database.StringGetAsync(key);
     }

     public async Task Remove(string key)
     {
         await _database.KeyDeleteAsync(key);
     }

     public async Task Set(string key, object value, TimeSpan cacheTime)
     {
         if (value != null)
         {
             //序列化,将object值生成RedisValue
            await _database.StringSetAsync(key, SerializeHelper.Serialize(value), cacheTime);
         }
     }

     public async Task<TEntity> Get<TEntity>(string key)
     {
         var value = await _database.StringGetAsync(key);
         if (value.HasValue)
         {
             //需要用的反序列化,将Redis存储的Byte[],进行反序列化
             return SerializeHelper.Deserialize<TEntity>(value);
         }
         else
         {
             return default(TEntity);
         }
     }
 }

相应的逻辑也很简单,我详细基本都能看得懂。

最后就是在BlogRedisCacheAOP.cs中,也要修改下,毕竟改成了异步,还是要注意的:

0 人点赞