重连逻辑须封装于独立goroutine中,避免主流程阻塞;需配合select+context控制生命周期,并在每次重试前用time.sleep实现指数退避。

重连逻辑必须封装在独立 goroutine 中
直接在主流程里循环 dial 会导致阻塞,尤其当目标地址不可达时 net.Dial 默认超时很长(几十秒),整个调用卡死。必须把连接尝试放到 go func() 里,并配合 select + context 控制生命周期。
- 每次重试前用
time.Sleep指数退避,比如第 n 次重试前等待time.Second (即 1s、2s、4s…) - 用
context.WithTimeout包裹每次net.Dial,避免单次尝试拖太久,建议设为 3–5 秒 - 不要用
for {}死循环,要监听外部取消信号,比如服务 shutdown 或用户中断
判断“网络断开”不能只靠 err != nil
很多初学者以为只要 conn == nil || err != nil 就该重连,但实际中常见伪失败:DNS 解析失败、TCP SYN 超时、TLS 握手失败、甚至 io.EOF(已建立连接但对方关闭)。这些错误类型需要区别对待:
-
net.OpError且Err是syscall.ECONNREFUSED或syscall.ETIMEDOUT→ 可重试 -
net.DNSError(如域名无法解析)→ 通常不该立即重试,可加短延迟后试一次 -
io.EOF或tls alert类错误 → 表示连接曾建立过但已断开,属于“断开后重连”场景,应清空旧 conn 后重试 - 其他非网络类错误(如
crypto/x509校验失败)→ 属于配置问题,重试无意义
重试次数和总耗时必须可配置
硬编码 3 次或 10 秒容易出问题:内网服务可能 200ms 就恢复,而跨公网服务可能需等 30 秒。接口设计上至少暴露两个参数:
-
maxRetries int:最大重试次数(不含首次尝试,即总共最多尝试maxRetries + 1次) -
totalTimeout time.Duration:从第一次 dial 开始到彻底放弃的总时间上限 - 可选:
backoffFunc func(int) time.Duration,方便自定义退避策略(比如加 jitter 避免雪崩)
注意:总超时必须覆盖所有 dial + sleep 时间,否则可能在 sleep 中就被 context 取消。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
返回值要区分“连接成功”和“重试失败”
别只返回 *net.Conn 和 error —— 这会让调用方无法判断是首次连接成功,还是重试后才连上。建议返回结构体:
type ConnResult struct {
Conn net.Conn
Attempt int // 第几次尝试成功(1 表示首次,2 表示重试一次后成功)
Err error
}
这样上层能做针对性处理:比如首次失败时打 warning 日志,重试 N 次才成功时打 info 日志,超时则打 error 并触发告警。
真正的难点不在写 for 循环,而在如何让重试行为对业务透明、不掩盖真实失败原因、且不因重试本身引入新问题(比如资源泄漏、goroutine 泄漏、日志刷屏)。每个重试分支都得有明确的 exit 条件,而不是靠“试够次数就停”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










