前几天在生产环境上redis创建连接方面的故障,分析过程中对ServiceStack.Redis的连接创建和连接池机制有了进一步了解。问题分析结束后,通过此文系统的将学习到的知识点整理出来。
从连接池获取RedisClient的流程
业务程序中通过PooledRedisClientManager对象的GetClient()方法获取客户端对象,就以此处的源码作为入口:
查看代码
public IRedisClient GetClient()
{
RedisClient redisClient = null;
DateTime now = DateTime.Now;
for (; ; )
{
if (!this.deactiveClientQueue.TryPop(out redisClient))
{
if (this.redisClientSize >= this.maxRedisClient)
{
Thread.Sleep(3);
if (this.PoolTimeout != null && (DateTime.Now - now).TotalMilliseconds >= (double)this.PoolTimeout.Value)
{
break;
}
}
else
{
redisClient = this.CreateRedisClient();
if (redisClient != null)
{
goto Block_5;
}
}
}
else
{
if (!redisClient.HadExceptions)
{
goto Block_6;
}
List<RedisClient> obj = this.writeClients;
lock (obj)
{
this.writeClients.Remove(redisClient);
this.redisClientSize--;
}
RedisState.DisposeDeactivatedClient(redisClient);
}
}
bool flag2 = true;
if (flag2)
{
throw new TimeoutException("Redis Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use.");
}
return redisClient;
Block_5:
this.writeClients.Add(redisClient);
return redisClient;
Block_6:
redisClient.Active = true;
this.InitClient(redisClient);
return redisClient;
}
此方法的主体是死循环,主要实现了这几项功能:
- this.deactiveClientQueue代表空闲的Client集合,是ConcurrentStack<RedisClient>类型的。
- 当this.deactiveClientQueue能够Pop出redisClient时,则跳转到Block_6分支:标记redisClient.Active属性,并执行this.InitClient(redisClient),然后将redisClient实例返回。
- 当this.deactiveClientQueue没有可以Pop的元素时,首先执行Client数量上限的判断this.redisClientSize >= this.maxRedisClient;
- 如果未到达上限,则执行redisClient = this.CreateRedisClient();
- 如果达到上限,则先休眠3毫秒,然后判断是否超过连接池超时时间this.PoolTimeout,单位毫秒。超时的话直接break中断循环,不超时的话继续下一次for循环。
上述流程就是从连接池获取Client的主要流程,其中this.deactiveClientQueue相当于“Client池”。需要注意this.PoolTimeout的含义是当连接池耗尽时调用方等待的时间。
上述过程通过流程图表示为:
创建新Client的过程:CreateRedisClient()
源码如下:
查看代码
private RedisClient CreateRedisClient()
{
if (this.redisClientSize >= this.maxRedisClient)
{
return null;
}
object obj = this.lckObj;
RedisClient result;
lock (obj)
{
if (this.redisClientSize >= this.maxRedisClient)
{
result = null;
}
else
{
Random random = new Random((int)DateTime.Now.Ticks);
RedisClient newClient = this.InitNewClient(this.RedisResolver.CreateMasterClient(random.Next(100)));
newClient.OnDispose += delegate()