文件写入失败后应仅对瞬态i/o错误(如eagain、eintr、ebusy)重试,配合上下文取消和总耗时限制;确定性错误(如enospc、errpermission)不可重试;推荐固定延迟50ms、最多3次、总耗时≤200ms。

文件写入失败后不该无差别重试——os.WriteFile 或 file.Write 返回的错误里,syscall.ENOSPC(磁盘满)、os.ErrPermission、syscall.EACCES 这类错误重试一百次也没用;真正值得重试的只有瞬态 I/O 错误,比如 syscall.EAGAIN、syscall.EINTR、syscall.EBUSY,且必须配合上下文取消和总耗时限制。
哪些错误值得重试?必须用 errors.Is 判断
靠 err.Error() 包含 “timeout” 或 “busy” 字样来判断是错的,字符串匹配不可靠,且不同系统返回不一致。正确做法是用标准库的类型断言:
-
errors.Is(err, syscall.EAGAIN):资源暂不可用,常见于 NFS 或某些挂载点抖动 -
errors.Is(err, syscall.EINTR):系统调用被信号中断,可立即重试,无需退避 -
errors.Is(err, syscall.EBUSY):设备忙,比如文件正被另一进程独占写入(Windows)或底层存储临时阻塞 -
errors.Is(err, syscall.ENOENT)仅当目标是“刚创建的父目录下的文件”时才考虑重试(需先os.Stat确认父目录存在),否则跳过 - 明确跳过:
os.ErrPermission、syscall.ENOSPC、syscall.EROFS、os.ErrNotExist(路径中父目录不存在)——这些是确定性错误,重试无效
重试策略选固定间隔,不是指数退避
文件 I/O 的瞬态故障(如锁释放、NFS 缓存刷新、内核队列清空)通常在毫秒级恢复,指数退避反而拉长故障感知时间,还容易错过恢复窗口。实操建议:
- 初始延迟设
50 * time.Millisecond,最多重试 3 次,总耗时 ≤200 * time.Millisecond - 每次重试前必须
select等待ctx.Done(),防止上下文取消后 goroutine 卡在time.Sleep中 - 不要复用同一个
backoff.BackOff实例处理多个文件写入——不同路径、不同设备状态独立,共享退避计数器会导致行为错乱 - 若用
github.com/cenkalti/backoff/v4,显式构造ExponentialBackOff并调backoff.WithJitter(),但注意它默认MaxElapsedTime=10s,远超文件场景需要,务必覆盖
并发写同一文件时,重试前必须抢锁
多个 goroutine 同时写一个日志文件或状态文件时,重试逻辑若不和同步机制对齐,会掩盖竞态问题,甚至导致数据错乱。关键动作:
- 写入前先尝试获取排他锁:
syscall.Flock(file.Fd(), syscall.LOCK_EX|syscall.LOCK_NB) - 若锁失败(
syscall.EWOULDBLOCK),说明有其他协程正持有锁,此时应立即重试,不 sleep —— 锁竞争本身就是瞬态的 - 写入成功后,立刻
syscall.Flock(file.Fd(), syscall.LOCK_UN)释放,避免阻塞后续操作 - 若写入失败后
os.Stat发现文件大小突变(比如被其他进程截断),说明状态已不可信,应放弃重试并返回带上下文的错误,例如fmt.Errorf("file %s size changed during write: %w", path, err)
别指望 recover() 拦截写入失败
os.WriteFile、file.Write、io.Copy 这些函数失败时都走 error 返回路径,不会触发 panic。recover() 对它们完全无效。真正该做的:
- 把所有写入操作包在
if err != nil分支里显式处理,而不是寄希望于 defer + recover - 对可能被中断的长期写入(如日志追加),用
os/signal监听syscall.SIGINT和syscall.SIGTERM,收到信号后主动file.Close()并退出 goroutine - 写入前确保父目录存在(
os.MkdirAll),避免因目录缺失导致的os.ErrNotExist被误判为可重试错误
最常被忽略的一点:重试只解决“写不进去”,不解决“写错了”。如果写入内容本身依赖外部状态(比如时间戳、序列号),重试可能把旧值重复写入,得靠业务层保证幂等,而不是靠 I/O 层重试兜底。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











