FreeRedis icon indicating copy to clipboard operation
FreeRedis copied to clipboard

连接池整个不可用是否恰当?

Open xiaolipro opened this issue 1 year ago • 39 comments

1 异常堆栈

😭 轨迹中台测试环境异常_Error

cap-msg-id:316993851092066309

System.Exception: 【redis-test.default.svc.cluster.local:6379/6】Status unavailable, waiting for recovery. Connect to redis-server(redis-test.default.svc.cluster.local:6379 -> Unspecified/redis-test.default.svc.cluster.local:6379) timeout, DEBUG: Dns.GetHostEntry(redis-test.default.svc.cluster.local)=System.Net.IPHostEntry

---> System.TimeoutException: Connect to redis-server(redis-test.default.svc.cluster.local:6379 -> Unspecified/redis-test.default.svc.cluster.local:6379) timeout, DEBUG: Dns.GetHostEntry(redis-test.default.svc.cluster.local)=System.Net.IPHostEntry

at FreeRedis.Internal.DefaultRedisSocket.Connect()

at FreeRedis.Internal.DefaultRedisSocket.Write(CommandPacket cmd)

at FreeRedis.RedisClient.SingleInsideAdapter.<>c__DisplayClass5_0`1.<AdapterCall>b__0()

at FreeRedis.RedisClient.LogCallCtrl[T](CommandPacket cmd, Func`1 func, Boolean aopBefore, Boolean aopAfter)

at FreeRedis.RedisClient.LogCall[T](CommandPacket cmd, Func`1 func)

at FreeRedis.RedisClient.SingleInsideAdapter.AdapterCall[TValue](CommandPacket cmd, Func`2 parse)

at FreeRedis.RedisClient.Call(CommandPacket cmd)

at FreeRedis.Internal.RedisClientPoolPolicy.PrevReheatConnectionPool(ObjectPool`1 pool, Int32 minPoolSize)

--- End of inner exception stack trace ---

at FreeRedis.Internal.ObjectPool.ObjectPool`1.GetFree(Boolean checkAvailable)

at FreeRedis.Internal.ObjectPool.ObjectPool1.Get(Nullable1 timeout)

at FreeRedis.RedisClient.PoolingAdapter.GetRedisSocket(CommandPacket cmd)

at FreeRedis.RedisClient.PoolingAdapter.<>c__DisplayClass10_0`1.<<AdapterCallAsync>b__0>d.MoveNext()

--- End of stack trace from previous location ---

at FreeRedis.RedisClient.LogCallAsync[T](CommandPacket cmd, Func`1 func)

at SJZY.Track.Application.Services.TrackPush.TrackPushCacheService.GetPushContentConfigsAsync(Int32 subSystemId, Int32 customId) in /var/lib/jenkins/workspace/trackapi-test/src/SJZY.Track.Application/Services/TrackPush/TrackPushCacheService.cs:line 24

at SJZY.Track.Application.EventHandlers.TrackPushEventHandler.ConsumerAsync(TrackPushEvent event) in /var/lib/jenkins/workspace/trackapi-test/src/SJZY.Track.Application/EventHandlers/TrackPushEventHandler.cs:line 43

at lambda_method987(Closure, Object)

at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()

at DotNetCore.CAP.Internal.SubscribeInvoker.ExecuteWithParameterAsync(ObjectMethodExecutor executor, Object class, Object[] parameter)

at DotNetCore.CAP.Internal.SubscribeInvoker.InvokeAsync(ConsumerContext context, CancellationToken cancellationToken)

2 猜测

2.1 连接池问题

2.2 网络问题

3 问题分析

此问题和业务代码木有直接关系,业务代码只是简单访问redis。

从异常来看是网络问题,找了运维看几次也没看出问题,所以只能分析freeredis源码了。

2.1 定位异常

直接定位到freedis核心方法Get,这是我们取得redis client的方法

img

可以看见,正是因为满足UnavailableException != null,才出现了我们看见的

img

我们找到赋值UnavailableException 的地方:将对象池设置为不可用,后续 Get/GetAsync 均会报错,同时启动后台定时检查服务恢复可用

img

反推引用,会发现回到了innert exception的stack底部 AdapterCall

img

2.2 排除连接池

通过上面的定位已经可以排除连接池嫌疑了,因为get rdc的点只有一个:GetRedisSocket。

感兴趣的自己去具体了解一下,这里稍微介绍一下free redis连接池设计:任何redis操作都要从ObjectPool Get一个rdc(RedisClient)出来

// 如果木有空闲rdc 并且 池内rdc小于MaxPoolSize(默认100)
if ((_freeObjects.TryPop(out var obj) == false || obj == null) && _allObjects.Count < Policy.PoolSize)
{
    lock (_allObjectsLock)
        // DCL 如果池内rdc数量仍然小于MaxPoolSize,则添加一个rdc到池子里去
        if (_allObjects.Count < Policy.PoolSize)
            _allObjects.Add(obj = new Object<T> { Pool = this, Id = _allObjects.Count + 1 });
}

// ...

// 如果rdc释放了 或者 rdc已经超过IdleTimeout(默认20s)未接收数据
if (obj != null && obj.Value == null ||
    obj != null && Policy.IdleTimeout > TimeSpan.Zero && DateTime.Now.Subtract(obj.LastReturnTime) > Policy.IdleTimeout)
{
    try
    {
        // 重置rdc(昂贵的)
        obj.ResetValue();
    }
    catch
    {
        Return(obj);
        throw;
    }
}

而池子的容量是有限的(MaxPoolSize控制),如果在某个时间内有大量的redis访问,可能导致池满

// 如果木有空闲的rdc 也无法再创建新的rdc(池满)
if (obj == null)
{
    var queueItem = new GetSyncQueueInfo();

    _getSyncQueue.Enqueue(queueItem);
    _getQueue.Enqueue(false);

    if (timeout == null) timeout = Policy.SyncGetTimeout;

    try
    {
        // 等待SyncGetTimeout(默认10s)看看rdc池会不会归还空闲rdc
        if (queueItem.Wait.Wait(timeout.Value))
            obj = queueItem.ReturnValue;
    }
    catch { }

    if (obj == null) obj = queueItem.ReturnValue;
    if (obj == null) lock (queueItem.Lock) queueItem.IsTimeout = (obj = queueItem.ReturnValue) == null;
    if (obj == null) obj = queueItem.ReturnValue;
    if (queueItem.Exception != null) throw queueItem.Exception;

    if (obj == null)
    {
        // 如果仍然获取不到rdc,throw超时
        // 但是该异常会被我们上面看见的AdapterCall捕获住,然后设置池状态为不可用
        Policy.OnGetTimeout();
        if (Policy.IsThrowGetTimeoutException)
            throw new TimeoutException($"【{Policy.Name}】ObjectPool.Get() timeout {timeout.Value.TotalSeconds} seconds, see: https://github.com/dotnetcore/FreeSql/discussions/1081");

        return null;
    }
}

何时归还:

  1. OnGet异常,导致的主动归还(该情况会reset rdc)
    1. 默认情况下使用pooling policy是不会出现error,致使除非return的

    2. // OnGet 默认实现
      if (_pool.IsAvailable)
      {
          // 如果超过60s未使用,或者socket已经断开,尝试ping
          if (DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 || obj.Value.Adapter.GetRedisSocket(null).IsConnected == false)
          {
              try
              {
                CommandPacket cmd = "PING";
                cmd.IsIgnoreAop = true;
                obj.Value.Call(cmd);
             }
              catch
              {
                  obj.ResetValue();
              }
          }
      }
      
  2. rdc销毁时会主动归还(复用根本)
    1. public override IRedisSocket GetRedisSocket(CommandPacket cmd)
      {
          var poolkey = GetIdleBusKey(cmd);
          var pool = _ib.Get(poolkey);
          var cli = pool.Get();
          var rds = cli.Value.Adapter.GetRedisSocket(null);
          // 每次操作完了 主动归还
          var rdsproxy = DefaultRedisSocket.CreateTempProxy(rds, () => pool.Return(cli));
          rdsproxy._poolkey = poolkey;
          rdsproxy._pool = pool;
          return rdsproxy;
      }
      
  3. auto-free策略(可用通过IdleTimeout控制)
    1. 作者说的这个没用

    2. public void AutoFree()
      {
          if (running == false) return;
          if (UnavailableException != null) return;
      
          var list = new List<Object<T>>();
          while (_freeObjects.TryPop(out var obj))
              list.Add(obj);
          foreach (var obj in list)
          {
              if (obj != null && obj.Value == null ||
                  obj != null && Policy.IdleTimeout > TimeSpan.Zero && DateTime.Now.Subtract(obj.LastReturnTime) > Policy.IdleTimeout)
              {
                  if (obj.Value != null)
                  {
                      Return(obj, true);
                      continue;
                  }
              }
              Return(obj);
          }
      }
      

2.2 重现errormessage

排除连接池嫌疑后,目光可以回到引发异常的两行代码了

try
{
    rds.Write(cmd);
    rt = rds.Read(cmd);
}

不管是写还是读,内部实现都调用了Connect方法

if (IsConnected == false) Connect();

// Connect实现
public void Connect()
{
    lock (_connectLock)
    {
        ResetHost(Host);

        EndPoint endpoint = IPAddress.TryParse(_ip, out var tryip) ?
            (EndPoint)new IPEndPoint(tryip, _port) :
            new DnsEndPoint(_ip, _port);

        var localSocket = endpoint.AddressFamily == AddressFamily.InterNetworkV6 ? 
            new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp):
            new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        try
        {
            var asyncResult = localSocket.BeginConnect(endpoint, null, null);
            if (!asyncResult.AsyncWaitHandle.WaitOne(ConnectTimeout, true))
            {
                var endpointString = endpoint.ToString();
                if (endpointString != $"{_ip}:{_port}") endpointString = $"{_ip}:{_port} -> {endpointString}";
                var debugString = "";
                if (endpoint is DnsEndPoint)
                {
                    try { debugString = $", DEBUG: Dns.GetHostEntry({_ip})={Dns.GetHostEntry(_ip)}"; }
                    catch (Exception ex) { debugString = $", DEBUG: {ex.Message}"; }
                }
                throw new TimeoutException($"Connect to redis-server({endpointString}) timeout{debugString}");
            }
            localSocket.EndConnect(asyncResult);
        }
        catch
        {
            ReleaseSocket(localSocket);
            throw;
        }
        //...
    }
}

至此我们已经还原了所有异常堆栈、message。

4 解决方案

修改free redis源码,我们暂时维护了自己的版本,后续可能会pr到free redis。

如果你的项目中已经遇到了这个问题且无法自己解决就切换我们的包吧。

5 总结

  1. free redis连接池设计有问题,大量并发下池满了且rdc没能那么快归还,导致后续的请求没有rdc可用,抛出异常,而free redis对异常的处理非常暴力:将整个rdc池的状态设为不可用(然后在后台通过一个long task thread去扫描,默认是3s),这也就导致了我们看见的高并发下大量的上述异常
  2. Free redis为了节约socket资源,做了一个空闲策略,即:IdleTimeout 时间内rdc都没被用过,rdc会被reset,也就是socket被重建,这个我认为还好,关键因素是第一点

xiaolipro avatar Aug 06 '24 14:08 xiaolipro

并发高连接池不够用的时候,触发的 链接池Timeout 不会导致整个链接池不可用。

并发时归还不及时导致 链接池Timeout,可能是IO问题(网络或硬盘),解决办法可以设置更大的连接池数量,或者从根本解决io问题。

提醒:连接超时,连接池超时,是两个概念。

2881099 avatar Aug 06 '24 15:08 2881099

1 异常堆栈

😭 轨迹中台测试环境异常_Error

cap-msg-id:316993851092066309

System.Exception: 【redis-test.default.svc.cluster.local:6379/6】Status unavailable, waiting for recovery. Connect to redis-server(redis-test.default.svc.cluster.local:6379 -> Unspecified/redis-test.default.svc.cluster.local:6379) timeout, DEBUG: Dns.GetHostEntry(redis-test.default.svc.cluster.local)=System.Net.IPHostEntry

---> System.TimeoutException: Connect to redis-server(redis-test.default.svc.cluster.local:6379 -> Unspecified/redis-test.default.svc.cluster.local:6379) timeout, DEBUG: Dns.GetHostEntry(redis-test.default.svc.cluster.local)=System.Net.IPHostEntry

at FreeRedis.Internal.DefaultRedisSocket.Connect()

at FreeRedis.Internal.DefaultRedisSocket.Write(CommandPacket cmd)

at FreeRedis.RedisClient.SingleInsideAdapter.<>c__DisplayClass5_0`1.b__0()

at FreeRedis.RedisClient.LogCallCtrl[T](CommandPacket cmd, Func`1 func, Boolean aopBefore, Boolean aopAfter)

at FreeRedis.RedisClient.LogCall[T](CommandPacket cmd, Func`1 func)

at FreeRedis.RedisClient.SingleInsideAdapter.AdapterCall[TValue](CommandPacket cmd, Func`2 parse)

at FreeRedis.RedisClient.Call(CommandPacket cmd)

at FreeRedis.Internal.RedisClientPoolPolicy.PrevReheatConnectionPool(ObjectPool`1 pool, Int32 minPoolSize)

--- End of inner exception stack trace ---

at FreeRedis.Internal.ObjectPool.ObjectPool`1.GetFree(Boolean checkAvailable)

at FreeRedis.Internal.ObjectPool.ObjectPool1.Get(Nullable1 timeout)

at FreeRedis.RedisClient.PoolingAdapter.GetRedisSocket(CommandPacket cmd)

at FreeRedis.RedisClient.PoolingAdapter.<>c__DisplayClass10_0`1.<b__0>d.MoveNext()

--- End of stack trace from previous location ---

at FreeRedis.RedisClient.LogCallAsync[T](CommandPacket cmd, Func`1 func)

at SJZY.Track.Application.Services.TrackPush.TrackPushCacheService.GetPushContentConfigsAsync(Int32 subSystemId, Int32 customId) in /var/lib/jenkins/workspace/trackapi-test/src/SJZY.Track.Application/Services/TrackPush/TrackPushCacheService.cs:line 24

at SJZY.Track.Application.EventHandlers.TrackPushEventHandler.ConsumerAsync(TrackPushEvent event) in /var/lib/jenkins/workspace/trackapi-test/src/SJZY.Track.Application/EventHandlers/TrackPushEventHandler.cs:line 43

at lambda_method987(Closure, Object)

at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()

at DotNetCore.CAP.Internal.SubscribeInvoker.ExecuteWithParameterAsync(ObjectMethodExecutor executor, Object class, Object[] parameter)

at DotNetCore.CAP.Internal.SubscribeInvoker.InvokeAsync(ConsumerContext context, CancellationToken cancellationToken)

2 猜测

2.1 连接池问题

2.2 网络问题

3 问题分析

此问题和业务代码木有直接关系,业务代码只是简单访问redis。

从异常来看是网络问题,找了运维看几次也没看出问题,所以只能分析freeredis源码了。

2.1 定位异常

直接定位到freedis核心方法Get,这是我们取得redis client的方法

img

可以看见,正是因为满足UnavailableException != null,才出现了我们看见的

img

我们找到赋值UnavailableException 的地方:将对象池设置为不可用,后续 Get/GetAsync 均会报错,同时启动后台定时检查服务恢复可用

img

反推引用,会发现回到了innert exception的stack底部 AdapterCall

img

2.2 排除连接池

通过上面的定位已经可以排除连接池嫌疑了,因为get rdc的点只有一个:GetRedisSocket。

感兴趣的自己去具体了解一下,这里稍微介绍一下free redis连接池设计:任何redis操作都要从ObjectPool Get一个rdc(RedisClient)出来

// 如果木有空闲rdc 并且 池内rdc小于MaxPoolSize(默认100)
if ((_freeObjects.TryPop(out var obj) == false || obj == null) && _allObjects.Count < Policy.PoolSize)
{
    lock (_allObjectsLock)
        // DCL 如果池内rdc数量仍然小于MaxPoolSize,则添加一个rdc到池子里去
        if (_allObjects.Count < Policy.PoolSize)
            _allObjects.Add(obj = new Object<T> { Pool = this, Id = _allObjects.Count + 1 });
}

// ...

// 如果rdc释放了 或者 rdc已经超过IdleTimeout(默认20s)未接收数据
if (obj != null && obj.Value == null ||
    obj != null && Policy.IdleTimeout > TimeSpan.Zero && DateTime.Now.Subtract(obj.LastReturnTime) > Policy.IdleTimeout)
{
    try
    {
        // 重置rdc(昂贵的)
        obj.ResetValue();
    }
    catch
    {
        Return(obj);
        throw;
    }
}

而池子的容量是有限的(MaxPoolSize控制),如果在某个时间内有大量的redis访问,可能导致池满

// 如果木有空闲的rdc 也无法再创建新的rdc(池满)
if (obj == null)
{
    var queueItem = new GetSyncQueueInfo();

    _getSyncQueue.Enqueue(queueItem);
    _getQueue.Enqueue(false);

    if (timeout == null) timeout = Policy.SyncGetTimeout;

    try
    {
        // 等待SyncGetTimeout(默认10s)看看rdc池会不会归还空闲rdc
        if (queueItem.Wait.Wait(timeout.Value))
            obj = queueItem.ReturnValue;
    }
    catch { }

    if (obj == null) obj = queueItem.ReturnValue;
    if (obj == null) lock (queueItem.Lock) queueItem.IsTimeout = (obj = queueItem.ReturnValue) == null;
    if (obj == null) obj = queueItem.ReturnValue;
    if (queueItem.Exception != null) throw queueItem.Exception;

    if (obj == null)
    {
        // 如果仍然获取不到rdc,throw超时
        // 但是该异常会被我们上面看见的AdapterCall捕获住,然后设置池状态为不可用
        Policy.OnGetTimeout();
        if (Policy.IsThrowGetTimeoutException)
            throw new TimeoutException($"【{Policy.Name}】ObjectPool.Get() timeout {timeout.Value.TotalSeconds} seconds, see: https://github.com/dotnetcore/FreeSql/discussions/1081");

        return null;
    }
}

何时归还:

  1. OnGet异常,导致的主动归还(该情况会reset rdc)

    1. 默认情况下使用pooling policy是不会出现error,致使除非return的

    2. // OnGet 默认实现
      if (_pool.IsAvailable)
      {
          // 如果超过60s未使用,或者socket已经断开,尝试ping
          if (DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 || obj.Value.Adapter.GetRedisSocket(null).IsConnected == false)
          {
              try
              {
                CommandPacket cmd = "PING";
                cmd.IsIgnoreAop = true;
                obj.Value.Call(cmd);
             }
              catch
              {
                  obj.ResetValue();
              }
          }
      }
      
  2. rdc销毁时会主动归还(复用根本)

    1. public override IRedisSocket GetRedisSocket(CommandPacket cmd)
      {
          var poolkey = GetIdleBusKey(cmd);
          var pool = _ib.Get(poolkey);
          var cli = pool.Get();
          var rds = cli.Value.Adapter.GetRedisSocket(null);
          // 每次操作完了 主动归还
          var rdsproxy = DefaultRedisSocket.CreateTempProxy(rds, () => pool.Return(cli));
          rdsproxy._poolkey = poolkey;
          rdsproxy._pool = pool;
          return rdsproxy;
      }
      
  3. auto-free策略(可用通过IdleTimeout控制)

    1. 作者说的这个没用

    2. public void AutoFree()
      {
          if (running == false) return;
          if (UnavailableException != null) return;
      
          var list = new List<Object<T>>();
          while (_freeObjects.TryPop(out var obj))
              list.Add(obj);
          foreach (var obj in list)
          {
              if (obj != null && obj.Value == null ||
                  obj != null && Policy.IdleTimeout > TimeSpan.Zero && DateTime.Now.Subtract(obj.LastReturnTime) > Policy.IdleTimeout)
              {
                  if (obj.Value != null)
                  {
                      Return(obj, true);
                      continue;
                  }
              }
              Return(obj);
          }
      }
      

2.2 重现errormessage

排除连接池嫌疑后,目光可以回到引发异常的两行代码了

try
{
    rds.Write(cmd);
    rt = rds.Read(cmd);
}

不管是写还是读,内部实现都调用了Connect方法

if (IsConnected == false) Connect();

// Connect实现
public void Connect()
{
    lock (_connectLock)
    {
        ResetHost(Host);

        EndPoint endpoint = IPAddress.TryParse(_ip, out var tryip) ?
            (EndPoint)new IPEndPoint(tryip, _port) :
            new DnsEndPoint(_ip, _port);

        var localSocket = endpoint.AddressFamily == AddressFamily.InterNetworkV6 ? 
            new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp):
            new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        try
        {
            var asyncResult = localSocket.BeginConnect(endpoint, null, null);
            if (!asyncResult.AsyncWaitHandle.WaitOne(ConnectTimeout, true))
            {
                var endpointString = endpoint.ToString();
                if (endpointString != $"{_ip}:{_port}") endpointString = $"{_ip}:{_port} -> {endpointString}";
                var debugString = "";
                if (endpoint is DnsEndPoint)
                {
                    try { debugString = $", DEBUG: Dns.GetHostEntry({_ip})={Dns.GetHostEntry(_ip)}"; }
                    catch (Exception ex) { debugString = $", DEBUG: {ex.Message}"; }
                }
                throw new TimeoutException($"Connect to redis-server({endpointString}) timeout{debugString}");
            }
            localSocket.EndConnect(asyncResult);
        }
        catch
        {
            ReleaseSocket(localSocket);
            throw;
        }
        //...
    }
}

至此我们已经还原了所有异常堆栈、message。

4 解决方案

修改free redis源码,我们暂时维护了自己的版本,后续可能会pr到free redis。

如果你的项目中已经遇到了这个问题且无法自己解决就切换我们的包吧。

5 总结

  1. free redis连接池设计有问题,大量并发下池满了且rdc没能那么快归还,导致后续的请求没有rdc可用,抛出异常,而free redis对异常的处理非常暴力:将整个rdc池的状态设为不可用(然后在后台通过一个long task thread去扫描,默认是3s),这也就导致了我们看见的高并发下大量的上述异常
  2. Free redis为了节约socket资源,做了一个空闲策略,即:IdleTimeout 时间内rdc都没被用过,rdc会被reset,也就是socket被重建,这个我认为还好,关键因素是第一点

你们修改后的包在哪里下载

xubinhua888 avatar Aug 10 '24 08:08 xubinhua888

StackExchange.Redis 有这个问题吗

xubinhua888 avatar Aug 26 '24 08:08 xubinhua888

新的包怎么引用,我们有出现dns 解析失败。然后大面积超时

nodyang avatar Aug 27 '24 03:08 nodyang

{"Success":false,"Message":"【redis.time.com:6399/0】Status unavailable, waiting for recovery. Connect to redis-server(redis.time.com:6399-> Unspecifiedredis.time.com:6399) timeout, DEBUG: Dns.GetHostEntry(redis.time.com)=System.Net.IPHostEntry"} [ELAPSED] 并发量大的时候。接口大约停顿2分钟才回复正常

nodyang avatar Aug 27 '24 03:08 nodyang

我们也出现这种情况 请问新包怎么引用? @xiaolipro

ebplayer avatar Sep 19 '24 06:09 ebplayer

我们也出现这种情况 请问新包怎么引用? @xiaolipro

一直也没有回我。

nodyang avatar Oct 05 '24 13:10 nodyang

我们也出现这种情况 请问新包怎么引用? @xiaolipro

解决了吗?今天下午又一次崩溃了。。

nodyang avatar Oct 15 '24 07:10 nodyang

@xiaolipro

nodyang avatar Oct 15 '24 08:10 nodyang

{"Success":false,"Message":"【redis.time.com:6399/0】Status unavailable, waiting for recovery. Connect to redis-server(redis.time.com:6399-> Unspecifiedredis.time.com:6399) timeout, DEBUG: Dns.GetHostEntry(redis.time.com)=System.Net.IPHostEntry"} [ELAPSED] 并发量大的时候。接口大约停顿2分钟才回复正常

后来解决了吗 我们在 高并发下也遇到 类似 问题

RobotJohns avatar Jun 06 '25 05:06 RobotJohns

你们没有多实例部署吗,webAPI hello world 每秒处理速度也才一万个请求,所以你们说的业务并发需求是多少?

2881099 avatar Jun 06 '25 05:06 2881099

八个实例, api qps 4500, 业务处理面 redis 操作就更频繁了: “【xxxxxx.aliyuncs.com:6379/0】Status unavailable, waiting for recovery. Connect to redis-server(xxxxxx.aliyuncs.com:6379 -> Unspecified/xxxxxx.aliyuncs.com:6379) timeout, DEBUG: Dns.GetHostEntry(xxxxxx.aliyuncs.com)=System.Net.IPHostEntry ---> System.TimeoutException: Connect to redis-server(xxxxxx.aliyuncs.com:6379 -> Unspecified/xxxxxx.aliyuncs.com:6379) timeout, DEBUG: Dns.GetHostEntry(xxxxxx.aliyuncs.com)=System.Net.IPHostEntry ”

RobotJohns avatar Jun 06 '25 05:06 RobotJohns

我们也出现这种情况 请问新包怎么引用? @xiaolipro

解决了吗?今天下午又一次崩溃了。。

换newlife了

xubinhua888 avatar Jun 06 '25 05:06 xubinhua888

https://mp.weixin.qq.com/s/94Xt3BbSPDnm_ExsQ8i3Jg 看看是不是和这个有关

2881099 avatar Jun 06 '25 05:06 2881099

应该不是 是最近并发一高 就会出现这个情况

2881099 @.***> 于2025年6月6日周五 13:58写道:

2881099 left a comment (2881099/FreeRedis#199) https://github.com/2881099/FreeRedis/issues/199#issuecomment-2948188905

https://mp.weixin.qq.com/s/94Xt3BbSPDnm_ExsQ8i3Jg 看看是不是和这个有关

— Reply to this email directly, view it on GitHub https://github.com/2881099/FreeRedis/issues/199#issuecomment-2948188905, or unsubscribe https://github.com/notifications/unsubscribe-auth/AI7QTYAAW6RW7YBWEDNE76T3CEUYJAVCNFSM6AAAAAB6XEAH6OVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDSNBYGE4DQOJQGU . You are receiving this because you commented.Message ID: @.***>

RobotJohns avatar Jun 06 '25 06:06 RobotJohns

socket 端确实连接超时了

2881099 avatar Jun 06 '25 06:06 2881099

是不是有这个假设? 链接池大小设置不当,造成短时间需要创建大量 socket连接. 然后创建的太多了 socket 就超时了

RobotJohns avatar Jun 06 '25 08:06 RobotJohns

可信任的服务端,不会瞬间创建几百个socket都不行,因为 pool size 不会太大。

至于上面说的链接池不可用,是另外的问题。

2881099 avatar Jun 06 '25 08:06 2881099

Image

这个和 阿里云 redis 确认过 他们是没有做限制的。那倒是我们代码姿势 需要做调整?

RobotJohns avatar Jun 06 '25 08:06 RobotJohns

那可以尝试其他sdk,如果不用单例模式,每次创建socket性能会非常低。

在局域网或者本地从不会发生这种问题。

2881099 avatar Jun 06 '25 08:06 2881099

错误显示,确定是socket连接超时,同时最大创建数量不会超过max pool size。

2881099 avatar Jun 06 '25 08:06 2881099

同样的阿里云redis, 换了newlife的sdk没有问题,以前用的csrediscore也没有出现过这样的问题

xubinhua888 avatar Jun 06 '25 09:06 xubinhua888

csrediscore 本地缓存了首次 dns 解析的 ip,以后都是用物理 ip 创建 socket。

freeredis 每次都解析 dns 获取最新的 ip,再链接,其他逻辑和 csrediscore 基本一致。

阿里云IP会变,缓存 dns 解析结果可能会有其他问题,当 ip 发生变化时。

2881099 avatar Jun 06 '25 09:06 2881099

阿里云DNS解析,有时非常不稳定,看我上面发的链接。

2881099 avatar Jun 06 '25 09:06 2881099

.net TaskScheduler 高并发延时问题,也值得关注

2881099 avatar Jun 06 '25 09:06 2881099

我看了 NewLifeX 的代码,它有对 DNS 本地做缓存处理,默认5分钟。

2881099 avatar Jun 06 '25 09:06 2881099

发布了 v1.4.0 增加本地 DNS 缓存,防止 dns 解析防止 。

2881099 avatar Jun 06 '25 10:06 2881099

好的 我们更新版本继续测试 后续即使反馈

RobotJohns avatar Jun 06 '25 11:06 RobotJohns

我们经测试发现一个规律: maxpoolsize:3 , minpoolsize:1, 并发 200, 可以复现 试试看

RobotJohns avatar Jun 06 '25 11:06 RobotJohns

最大连接池才3个,咋并发?

2881099 avatar Jun 06 '25 12:06 2881099