很长一段时间以来,一直在项目中使用Redis作为辅助存储,确切来说是利用Redis的内存存储,而不是将其作为缓存。比如常见的利用Set集合来判断某个数值是否存在,或者将来自不同请求的数据放在Redis中进行拼接然后一起写入MySQL等数据库。
这种存储目的的使用要求对Redis的访问不能失败(如果作为缓存使用,是接受失败的),所以作为存储目的使用代码中要对请求Redis的代码进行异常处理以及重试等。
在最初的代码中采用了最常见的方法如try ... catch ...处理异常,递归进行重试,类似:
//伪代码
public void Process(int retry)
{
if(retry>3)
{
//记录错误
return;
}
try
{
//业务代码
}
catch(Exception ex)
{
//重试
++retry;
Process(retry);
}
}
后来有一天看到了园友Jeffcky推荐的Polly库,瞬间眼前一亮,这才是我们处理异常和重试所需要的东西。
关于Polly的使用,可以参考Jeffcky的博文或者Polly项目的GitHub主页(文档很详细)。
大致的代码结构如:
var tsArr = new TimeSpan[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(1)
};
// 构造一种重试测试(其它可选的包括熔断等)
var policy = Policy
.Handle()
.WaitAndRetryAsync(tsArr);
// 需要有Polly调用的业务代码,以异步方法为例
async Task SomeToInvoke()
{
// 一些异步调用
}
// 使用Polly执行业务代码(如不需要捕获异常可选用其它重载)
var pollyRet = await policy.ExecuteAndCaptureAsync(SomeToInvoke);
// 处理返回值判断调用是否成功,或发生了什么异常
下面一步步来看博主的实现过程。
先放上一些测试所用的代码,首先是创建Redis连接的接口和类,它们是从NopCommerce项目一个早起版本借(chao)鉴(xi)来的(文件名都没改,为了测试方便代码略有改动),一直用着没啥大问题就这样用了。
public interface IRedisConnectionWrapper : IDisposable
{
IDatabase Database(int? db = null);
IServer Server(EndPoint endPoint);
EndPoint[] GetEndpoints();
void FlushDb(int? db = null);
}
public class RedisConnectionWrapper : IRedisConnectionWrapper
{
private readonly Lazy _connectionString;
private readonly Lazy _auth;
private volatile ConnectionMultiplexer _connection;
private readonly object _lock = new object();
public RedisConnectionWrapper(string server, string pswd)
{
this._connectionString = new Lazy(() => server);
this._auth = new Lazy(() => pswd);
}
private ConnectionMultiplexer GetConnection()
{
if (_connection != null && _connection.IsConnected) return _connection;
lock (_lock)
{
if (_connection != null && _connection.IsConnected) return _connection;
if (_connection != null)
{
_connection.Dispose();
}
var options = new ConfigurationOptions();
options.EndPoints.Add(_connectionString.Value);
if (!string.IsNullOrEmpty(_auth.Value))
options.Password = _auth.Value;
_connection