各种小问题?
如果你之前用过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中,也要修改下,毕竟改成了异步,还是要注意的: