You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GEOADD与BRPOP达到一定负载后Docker部署Redis返回连接错误

问题根因

报错是两个问题叠加导致的,和Redis服务端配置无关:

  • 客户端死循环疯狂创建Redis连接:你写的通知拉取逻辑有严重缺陷:select块里加default分支的写法,会导致只要WebSocket连接没断开,循环就会无等待执行BRPOP调用。一旦BRPOP因为连接错误、上下文取消、连接池满等原因快速返回,代码没有任何退避间隔,立刻发起下一次调用,短时间内会创建海量TCP连接。从Redis INFO返回的total_connections_received:1271也能印证这点:正常复用连接池的场景下,客户端到Redis的连接数不会短时间内涨到上千。另外你设置BRPOP超时为0无限阻塞,会长期占用连接不释放,从INFO里的blocked_clients:81能看到,已经有81个连接被阻塞占住,很快就会打满go-redis默认的连接池容量,后续请求只能不断新建TCP连接。
  • 本地进程文件描述符上限过低:你只给Docker里的Redis容器配置了nofile参数,这个配置只对容器内的Redis进程生效,对你Mac本地直接运行的Go进程完全无效。Mac系统默认给单个进程分配的文件描述符软上限只有256,每个WebSocket连接、每个到Redis的TCP连接都要占用一个fd,70个业务连接加上疯狂新建的Redis连接,很快就会耗尽所有可用fd,触发socket: too many open files错误。此时连DNS解析需要读取系统hosts、resolv.conf文件都拿不到fd,就会偶发lookup localhost: no such host的连带报错。
修复方案

1. 修复BRPOP死循环逻辑

删掉错误的default空转轮询写法,给BRPOP设置合理的超时时间,调用出错后加退避间隔,避免无意义的疯狂重试:

go func() {
    defer close(driverDataCh)
    for {
        select {
        case <-closeRead.Done():
            return
        default:
        }
        // 给BRPOP设置30s超时,不要用0无限阻塞
        status, err := config.Redis.DriverPullStatus(ctx, driverID, 30*time.Second)
        if err != nil {
            // 连接关闭、键不存在等正常场景直接继续循环
            if errors.Is(err, context.Canceled) || errors.Is(err, redis.Nil) {
                continue
            }
            // 其他网络错误、服务端错误等1s后再重试,避免打满服务端
            time.Sleep(time.Second)
            continue
        }
        // 写channel的时候也要监听连接关闭信号,避免goroutine泄漏
        select {
        case <-closeRead.Done():
            return
        case driverDataCh <- &DriverData{Status: status.Status, DriverId: status.DriverID, UserId: status.UserID}:
        }
    }
}()

2. 显式配置Redis连接池参数

初始化Redis客户端时不要用默认配置,根据业务场景设置合理的连接池上限,避免无限制创建连接:

client := redis.NewClient(&redis.Options{
    Addr:     fmt.Sprintf("%s:%s", host, port),
    Password: password,
    DB:       0,
    PoolSize: 100, // 最大连接数,按业务并发设置,100足够支撑上千司机同时在线
    IdleTimeout: 5 * time.Minute, // 空闲连接回收时间
    DialTimeout: time.Second, // 建连超时
    ReadTimeout: time.Second, // 读超时
    WriteTimeout: time.Second, // 写超时
})

3. 调高Mac本地进程的文件描述符上限

当前终端临时生效直接执行命令:

ulimit -n 65536

要永久生效的话,把上面这行加到~/.zshrc(如果用zsh)或者~/.bash_profile(如果用bash)末尾,保存后执行source对应配置文件即可。

4. 修复验证

重启服务后压测WebSocket连接,观察Redis INFO输出:connected_clients会稳定在你配置的连接池大小附近,不会持续上涨,total_connections_received增速会大幅下降,之前的两个报错都会消失。

内容的提问来源于stack exchange,提问作者Sami Al-Subhi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 16:39:32