:Go 连 Redis 半夜总报 EOF?连接池 5 参数与 redisx 封装)
我不会起名字322· 后端 / 算法 / 数据库 技术栈 力扣 Hot100 Go 项目 Redis MySQL文章目录Redis 从 0 到 1二Go 连 Redis 半夜总报 EOF连接池 5 参数与 redisx 封装一、故障现象白天正常半夜批量 EOF二、连接池 5 个关键参数三、Go 实操redisx 封装全系列复用收藏点 1四、为什么 EOF 常在半夜出现三类断连场景对照五、命令与配置清单收藏点 2六、服务端配置建议七、总结Redis 从 0 到 1二Go 连 Redis 半夜总报 EOF连接池 5 参数与 redisx 封装上一篇《Redis 从 0 到 1一安装环境配置 Redis——3 种方式跑通 10 项配置 Go 第一个 PING》我们用 3 种方式装好了 Redis 7并用 Go 发出了第一个 PING链接https://blog.csdn.net/2501_92769340/article/details/167399349 。本篇解决下一个问题把redis.NewClient默认参数直接搬上生产为什么总在半夜低峰期批量报 EOF一、故障现象白天正常半夜批量 EOF一个真实的现象服务白天几千 QPS 稳如老狗凌晨三四点低峰期反而批量报EOF或read: connection reset by peer过几分钟自己恢复。根因链条是这样的go-redis 默认PoolSize 10 × GOMAXPROCS池子里的空闲连接默认不会被主动清理但服务端 redis.conf 里timeout 3005 分钟会把空闲连接单方面断开。半夜流量低连接在池里闲置超过 300 秒被服务端悄悄 kill早上一来请求客户端拿到一个尸体连接一读写就是 EOF。这不是 bug是客户端池参数与服务端空闲超时不匹配。解决它只需要把 5 个参数配对。二、连接池 5 个关键参数参数默认值作用建议PoolSize10×CPU 核池最大连接数按QPS × 单命令耗时(秒)估算常见 20~100MinIdleConns0保底空闲连接设 PoolSize 的 1/4避免高峰现建连接MaxIdleConns0不限制空闲连接上限低峰期释放多余连接建议 ≈ PoolSize/2ConnMaxIdleTime30min空闲多久主动关必须小于服务端 timeout如 60sConnMaxLifetime0不限制连接最大寿命30min~1h防 NAT/LB 静默断连配平规则一句话客户端主动关连接的速度要比服务端 kill 得快。服务端timeout 300客户端ConnMaxIdleTime设 60~120 秒尸体连接就不再出现。三、Go 实操redisx 封装全系列复用收藏点 1下面这个redisx包是整个系列后面 35 篇共用的入口一次写好// redisx/redisx.gopackageredisximport(contextfmttimegithub.com/redis/go-redis/v9)// Config 连接配置只暴露需要调的参数typeConfigstruct{AddrstringPasswordstringDBintPoolSizeintMinIdleConnsint}// NewClient 创建带池化管理、超时、重试、健康检查的客户端funcNewClient(cfg Config)(*redis.Client,error){rdb:redis.NewClient(redis.Options{Addr:cfg.Addr,Password:cfg.Password,DB:cfg.DB,// —— 池化与服务端 timeout 配平杜绝尸体连接 ——PoolSize:cfg.PoolSize,MinIdleConns:cfg.MinIdleConns,MaxIdleConns:cfg.PoolSize/2,ConnMaxIdleTime:90*time.Second,// 服务端 timeout 300ConnMaxLifetime:30*time.Minute,// —— 超时任何一次调用都不许无限等 ——DialTimeout:3*time.Second,ReadTimeout:2*time.Second,WriteTimeout:2*time.Second,PoolTimeout:3*time.Second,// 从池里取连接的最长等待// —— 重试只对幂等读有意义写操作重试要谨慎第 28 篇细讲——MaxRetries:2,MinRetryBackoff:100*time.Millisecond,MaxRetryBackoff:1*time.Second,})// 启动即健康检查失败直接返回错误不让坏客户端流窜到业务里ctx,cancel:context.WithTimeout(context.Background(),3*time.Second)defercancel()iferr:rdb.Ping(ctx).Err();err!nil{returnnil,fmt.Errorf(redis 健康检查失败: %w,err)}returnrdb,nil}// PoolStats 暴露池指标第 25/34 篇的排查会用到funcPoolStats(rdb*redis.Client)string{s:rdb.PoolStats()returnfmt.Sprintf(hits%d misses%d timeouts%d total%d idle%d stale%d,s.Hits,s.Misses,s.Timeouts,s.TotalConns,s.IdleConns,s.StaleConns)}使用方业务代码里就这么写packagemainimport(contextfmttimeredis-0to1/redisx)funcmain(){rdb,err:redisx.NewClient(redisx.Config{Addr:127.0.0.1:6379,Password:redis123,PoolSize:20,MinIdleConns:5,})iferr!nil{panic(err)}deferrdb.Close()ctx,cancel:context.WithTimeout(context.Background(),2*time.Second)defercancel()iferr:rdb.Set(ctx,hello,redis-0to1,time.Minute).Err();err!nil{panic(fmt.Sprintf(SET 失败: %v,err))}val,err:rdb.Get(ctx,hello).Result()iferr!nil{panic(err)}fmt.Println(val,|,redisx.PoolStats(rdb))}四、为什么 EOF 常在半夜出现三类断连场景对照断连场景触发方时间特征解法服务端空闲超时redis.conftimeout低峰期半夜ConnMaxIdleTime timeoutLB/NAT 静默断连中间设备长连接数小时后ConnMaxLifetime 应用层心跳Redis 重启/切主服务端运维窗口重试 哨兵客户端第 9 篇五、命令与配置清单收藏点 2命令/配置作用CONFIG GET timeout看服务端空闲断开秒数0 不断开CONFIG GET maxclients服务端连接上限默认 10000INFO clients当前连接数、被拒绝连接数CLIENT LIST逐个看连接来源 IP 与空闲时长第 34 篇核心武器CLIENT SETNAME给连接起名排查时一眼认出是哪个服务给连接起名的好习惯go-redis 里一行搞定rdb.Do(context.Background(),CLIENT,SETNAME,order-service-pod-1)六、服务端配置建议# redis.conf —— 与客户端配平的两项 timeout 300 # 空闲 300 秒断开客户端 ConnMaxIdleTime 要比它小 tcp-keepalive 60 # TCP 层保活帮 NAT 环境续命 maxclients 10000 # 连接上限第 34 篇会用到七、总结半夜 EOF 的根因服务端timeout杀空闲连接客户端池却留着尸体配平原则ConnMaxIdleTime 服务端timeoutConnMaxLifetime防中间设备PoolSize 别拍脑袋按QPS × 单命令耗时估算第 25 篇给计算公式redisx 封装四件套池化配平、超时兜底、有限重试、启动健康检查——全系列复用。下一篇预告连接稳了该干活了——下一篇《五大结构 20 个最常用命令用 Go 各写一个真实用法》我们把 String/Hash/List/Set/ZSet 放进缓存对象、购物车、关注列表、排行榜 5 个真实场景里逐个过一遍。