前几天在生产环境上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.redisClientS