佳佳的博客
Menu
首页
[CSRedis] RedisClientException : Connection was not opened
Posted by
佳佳
on 2018-12-20
IT
CSRedis
Redis
线上生产 .NET Core 项目偶尔会报如下错误: ```csharp CSRedis.RedisClientException: Connection was not opened at CSRedis.CSRedisClient.GetAndExecute[T](RedisClientPool pool, Func\`2 handler, Int32 jump) at CSRedis.CSRedisClient.ExecuteScalar[T](String key, Func\`3 hander) at CSRedis.CSRedisClient.Set(String key, Object value, Int32 expireSeconds, Nullable\`1 exists) at Framework.Utils.RedisProvider.Set[T](String key, T value, Int32 minutes) ``` *CSRedisCore* 的版本号是 *3.0.0* 。 ## 2019/02/01 追记 经过查看源码、线下模拟最终怀疑是创建 Socket 连接的时候发生了未知的问题导致创建连接失败,最终引发了这个异常。 这个未知的问题是什么?我也不知道。 先说下 *CSRedis*,它的连接字符串有一个属性叫 *preheat* 预热,该属性默认为 `true`。 启用预热时,会自动创建连接至池中的最大值(*poolsize*)。*poolsize* 设置的是 50,我感觉对于 Redis 来说这个值其实并不高。 连接创建后会放回到连接池中待用,此时若超过一定时间未使用,则该连接会被关闭。这个时间由 Redis 的超时时间(timeout)设置决定。 问题就出在创建连接时,多个服务同时启动时,会触发 n * 50 次创建连接的请求。短时间创建的连接过多导致更容易引发上述异常。 然而,在线下的测试环境中模拟短时间创建大量连接时并没有出现该异常。只有在较长时间重复创建连接到一定数量后(至少1w以上)有可能发生该异常。 因为模拟了好多种情况,总共出现过如下几种异常: 1. *ERR max number of clients reached* 这个估计是达到了 Redis 服务器的连接数上限导致的。 ```csharp RedisSocket.Connect endpoint : 192.168.0.66:8000 RedisSocket.Connect method cost 00:00:00.1314587 【192.168.0.66:8000/0】仍然不可用,下一次恢复检查 时间:01/29/2019 13:58:23,错误:(ERR max number of clients reached) ``` 2. 您的主机中的软件中止了一个已建立的连接 这个应该是创建的连接过多导致本机的端口号耗尽导致的。这种情况应该不会发生在线上,不可能有那么多的请求量。 ```csharp ---> (Inner Exception #93) System.IO.IOException: Unable to write data to the transport connection: 您的主机中的软件中止了一个已建立的连接。. ---> System.Net.Sockets.SocketException: 您的主机中的软件 中止了一个已建立的连接。 at System.Net.Sockets.NetworkStream.Write(Byte[] buffer, Int32 offset, Int32 size) --- End of inner exception stack trace --- at OctToolServices.Controllers.CSRedisController.<>c__DisplayClass8_0.<TestConnectionMultiThread>b__0() in D:\middleware\octmiddlewarenet\OctApiService\OctToolServices\Controllers\CSRedisController.cs:line 157 at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location where exception was thrown --- at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)<--- ``` 3. *Connection was not opened* 个人认为这个错误是最接近线上情况的。 其中打印的时间就是创建 Socket 连接所消耗的时间。正常值都是在 1ms 以下,但这是显示超过了 20s。 另外的两行的控制台消息不确定是哪边打印出来的。 另外这个也是在大量创建连接时发生的异常,但大部分时候是报(您的主机中的软件中止了一个已建立的连接)的错误消息。 ```csharp RedisSocket.Connect method cost 00:00:21.0015986 【192.168.0.76:8000/0】仍然不可用,下一次恢复检查时间:01/29/2019 13:27:21,错误:(Connection was not opened) RedisSocket.Connect method cost 00:00:21.1406340 【192.168.0.76:8000/0】仍然不可用,下一次恢复检查时间:01/29/2019 13:27:23,错误:(Connection was not opened) ``` 4. 状态不可用,等待后台检查程序恢复方可使用。*Connection was not opened* 这个应该是和上面的3是同一个错误。 ```csharp ---> (Inner Exception #49) System.Exception: 【192.168.0.66:8000/0】状态不可用,等待后台检查程序恢复方可使用。Connection was not opened at OctToolServices.Controllers.CSRedisController.<>c__DisplayClass9_1.<CreateTestData>b__0() in D:\middleware\octmiddlewarenet\OctApiService\OctToolServices\Controllers\CSRedisController.cs:line 218 at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location where exception was thrown --- at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)<--- ``` 根据上面的测试结果,比较大的可能性在于创建 Socket 连接的时候出了问题导致的。在 [Java建立Socket慢的问题](https://windshome.iteye.com/blog/1836885) 里说了一个建立 Socket 慢的原因。 > 使用 HostName 查找主机 IP 时可能会因为要查询 DNS 服务器,所以导致了连接缓慢。 由于这里使用的 IP 应该不会有这个问题才对。官方的一个 bug 修复 [v3.0.33 解决域名访问,同时开启ssl无法连接的bug](https://github.com/2881099/csredis/commit/24e1d8d389beae3960dcfb9a51276e2978216514) 里正好有类似的改动。 **修改前:** ```csharp public RedisClient OnCreate() { var ips = Dns.GetHostAddresses(_ip); if (ips.Length == 0) throw new Exception($"无法解析“{_ip}”"); var client = new RedisClient(new IPEndPoint(ips[0], _port), _ssl, 100, _writebuffer); // ... } ``` **修改后:** ```csharp public RedisClient OnCreate() { RedisClient client = null; if (IPAddress.TryParse(_ip, out var tryip)) { client = new RedisClient(new IPEndPoint(tryip, _port), _ssl, 100, _writebuffer); } else { var ips = Dns.GetHostAddresses(_ip); if (ips.Length == 0) throw new Exception($"无法解析“{_ip}”"); client = new RedisClient(_ip, _port, _ssl, 100, _writebuffer); } // ... } ``` 修改前使用 `Dns.GetHostAddresses` 函数来获取 Redis 服务器地址,其 MSDN [Dns.GetHostAddresses(String) Method](https://docs.microsoft.com/zh-cn/dotnet/api/system.net.dns.gethostaddresses?redirectedfrom=MSDN&view=netframework-4.7.2#System_Net_Dns_GetHostAddresses_System_String_) 上的说明如下: > `GetHostAddresses` 方法将查询与主机名关联的 IP 地址的 DNS 子系统。 如果 `hostNameOrAddress` 是 IP 地址,而无需查询 DNS 服务器返回此地址。 > 如果为空字符串作为传递 `hostNameOrAddress` 参数,则此方法返回本地主机的 IPv4 和 IPv6 地址。 > Pv6 地址进行筛选的结果中的 `GetHostAddresses` 方法如果本地计算机没有安装 IPv6。 因此,很可能返回一个空 IPAddress 只要 IPv6 结果已可供 `hostNameOrAddress` 参数。 修改后明确的判断 IP 优先。 ## 最终的解决方案 1. 按照上面 `Dns.GetHostAddresses` 的说明上面的补丁效果应该是一样的。为了以防万一还是打上了这个补丁。 2. 关闭了 CSRedis 的预热(*preheat*)功能。 3. 调低了 CSRedis 的 *poolsize* (原为 50 降到 5)。 从更新补丁上线到现在 2 天了,暂时还没有再出现该异常。 ## 2019/03/11 追记 ╮(╯﹏╰)╭ 非常残念的是从更新到现在 40 天的时间里还是出现了 2 次这个异常。
版权声明:原创文章,未经允许不得转载。
https://www.liujiajia.me/2018/12/20/csredis-redis-client-exception-connection-was-not-opened
“Buy me a nongfu spring”
« [C#] 创建 System.Drawing.Font 时报错
[CSRedis] SocketException:通常每个套接字地址(协议/网络地址/端口)只允许使用一次 »
Commented by
liujunwen
on 2019-08-25
@佳佳
回复
最后怎么解决的 老哥 我也碰到了
Commented by
momo314
on 2020-04-15
回复
我也碰到了,是在业务高峰期出现的,但不是每个高峰期都会出现,只有很少的情况会出现,想问下大佬最终解决了这个问题了吗
Commented by
佳佳
on 2020-04-15
@momo314
回复
最终在连接字符串中添加如下配置项以调低池的大小和关闭预热功能; ```bash poolsize=5,preheat=false ```
Commented by
rerererererer
on 2020-07-03
@佳佳
回复
即使这样,我还是出现问题 defaultDatabase=33,testcluster=false,idleTimeout=15000,tryit=2,poolsize=5,preheat=false
昵称
*
电子邮箱
*
回复内容
*
(回复审核后才会显示)
提交
目录
AUTHOR
刘佳佳
江苏 - 苏州
软件工程师
梦嘉集团
liujunwen
on 2019-08-25 @佳佳 回复momo314
on 2020-04-15 回复佳佳
on 2020-04-15 @momo314 回复rerererererer
on 2020-07-03 @佳佳 回复