WriteAPI.Errors()是唯一能感知连接中断的通道,必须用goroutine持续读取该无缓冲channel以捕获DNS失败、TCP拒绝、HTTP错误等;仅Flush后检查一次会漏错,且需根据err.StatusCode和err.Err类型区分临时抖动与永久故障。

WriteAPI.Errors() 是唯一能感知连接中断的通道
InfluxDB 客户端不提供“连接状态回调”或“重连事件钩子”,所谓“宕机重连”在 influxdb-client-go/v2 中实际表现为写入失败后自动重试,但错误不会主动抛出——除非你主动检查 writeAPI.Errors()。它是一个无缓冲 channel,所有后台写入失败(包括 DNS 解析失败、TCP 连接拒绝、HTTP 4xx/5xx、token 过期)都会往里塞 WriteError,但若无人接收,这些 error 就永远卡在 channel 里,静默丢弃。
常见错误现象:WritePoint() 调用成功,Flush() 返回 nil,但数据就是不进 InfluxDB;查日志也无报错;重启 InfluxDB 后数据突然批量出现——这说明 client 在后台默默重试,而你没消费 Errors。
- 必须启动一个 goroutine 持续读取
writeAPI.Errors(),例如:go func() { for err := range writeAPI.Errors() { log.Printf("InfluxDB write error: %+v", err) // 此处可触发告警、降级开关、或记录本地缓存 } }() - 不要只在
Flush()后检查一次:Errors 是持续流,不是一次性快照 - 如果 channel 已满(默认容量 1000),新 error 会被丢弃,所以消费速度要跟上;也可初始化时设
WriteOptions.ErrorBufferLimit
如何区分“临时宕机”和“永久故障”
仅靠单次 WriteError 无法判断是网络抖动还是服务器彻底挂了。关键看 err.Err 的底层错误类型和响应状态码:
-
err.Err是*url.Error且err.Err.Timeout()为 true → 网络超时,大概率临时抖动 -
err.StatusCode == 401或403→ token 失效或权限不足,不是宕机,需人工介入 -
err.StatusCode == 0且err.Err是connection refused或i/o timeout→ 服务不可达,可能是宕机或防火墙拦截 -
err.StatusCode == 429→ 不是宕机,是写入限频,应调低采集频率或升配
实操建议:对 StatusCode == 0 的错误做计数,连续 3 次间隔 Flush() 补传。
立即学习“go语言免费学习笔记(深入)”;
别依赖 client.Ping() 做存活探测
client.Ping() 发的是 HTTP GET /health,它只验证 InfluxDB 进程是否响应,不验证写入链路是否通(比如 bucket 权限缺失、token 过期、org 名错)。更糟的是,它默认无超时,可能卡死 goroutine。
- 如真要用 Ping,必须带 context:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _, err := client.Ping(ctx)
- 但 Ping 成功 ≠ 写入成功,仍需靠
Errors()捕获真实写入失败 - 高频 Ping(如每秒一次)反而增加 server 负担,不如专注做好写入错误处理
本地缓存补传的落地要点
当确认 InfluxDB 不可用时,把点暂存内存或磁盘,等恢复后重放。但要注意几个硬性约束:
- 缓存结构必须保留原始
point.Point对象,不能只存 map[string]interface{}——因为point.SetTime()和精度设置会在序列化时丢失 - 补传前必须重新校验时间戳:若缓存超过 1 小时,
point.Time()可能已过期(InfluxDB 默认拒收早于 now-1h 的点),需重置为当前时间或丢弃 - 补传时用
WriteAPIBlocking避免再进异步队列,否则又绕回 Errors 监听逻辑 - 磁盘缓存路径别写死,用
os.TempDir()+ 唯一前缀,避免多实例冲突
真正难的不是缓存本身,而是判断“何时开始缓存”和“何时停止缓存并切回直写”——这两个开关必须基于 Errors 流的统计趋势,而不是单次错误。


















