应只对明确的瞬态错误重试,如 syscall.EBUSY、EAGAIN、ENOENT(文件刚删)、EINTR;跳过权限不足、父目录不存在等确定性错误;需用 errors.Is 或类型断言判断,配合固定延迟(50ms)、最多3次、总耗时≤200ms,并响应上下文取消。

文件读写失败时,重试不是加个 for 循环 + time.Sleep 就完事——多数情况下会漏掉设备忙、权限变化、NFS挂载抖动等真实错误,反而把临时问题拖成超时或 panic。
os.Open/os.Create 失败后该不该重试?
只对明确的瞬态错误重试,比如 syscall.EBUSY(设备忙)、syscall.EAGAIN(资源暂不可用)、syscall.ENOENT(路径存在但文件刚被删);跳过 os.ErrPermission、os.ErrNotExist(父目录不存在)这类确定性错误。
- 检查错误类型必须用
errors.Is或类型断言,不能靠err.Error()字符串匹配 -
os.Open遇到syscall.EINTR可直接重试(系统调用被信号中断),无需退避 - 如果目标是配置文件或日志轮转场景,
os.ErrNotExist有时值得重试(等待守护进程创建),但需配合MaxElapsedTime限制总等待时间
os.Rename 跨设备移动失败怎么 fallback?
当 os.Rename 返回 syscall.EXDEV(Linux/macOS)或 ERROR_NOT_SAME_DEVICE(Windows),说明源和目标不在同一文件系统,必须降级为 copy + remove,不能重试 Rename。
- 先用
os.Stat检查源是否存在,若不存在直接返回backoff.Permanent(err) - copy 过程中也要加重试:对
io.Copy的syscall.EIO、syscall.EBADF等可恢复错误重试 - copy 完成后,用
os.Remove删除源文件;若Remove失败,需记录警告但不阻断流程(已 copy 成功,原文件残留属低风险)
重试间隔选固定值还是指数退避?
文件 I/O 场景下,固定间隔(如 100ms)比指数退避更合理——磁盘队列、NFS 缓存刷新、锁释放通常在毫秒级完成,指数增长反而拉长故障感知时间。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
立即学习“go语言免费学习笔记(深入)”;
- 初始延迟设
50 * time.Millisecond,最多重试 3 次,总耗时控制在 200ms 内 - 避免用
time.Sleep直接阻塞:每次延迟前select等待ctx.Done(),防止上下文取消后 goroutine 卡住 - 别复用同一个
backoff.BackOff实例做多次文件操作——不同文件句柄、路径、设备状态独立,共享退避策略会互相干扰
并发读写同一个文件时重试要注意什么?
多个 goroutine 同时操作同一文件(如日志追加),重试逻辑必须和锁/原子操作对齐,否则会掩盖竞态问题。
- 重试前先尝试获取文件锁(
syscall.Flock),失败则立即重试,不 sleep —— 锁竞争是瞬态的 - 写入失败后若发现文件大小突变(
os.Stat对比上次),说明有其他进程截断或覆盖,应放弃重试并报错 - 对
*os.File的WriteAt或Seek失败,优先检查是否超出文件系统配额(syscall.ENOSPC),这类错误重试无效
真正难处理的不是“怎么重试”,而是判断“该不该重试”——文件系统错误语义模糊,EACCES 在 NFS 上可能是缓存未同步,在本地磁盘上就是权限不足。实际编码时,先看 strace 或 dmesg 日志确认错误源头,再决定是否纳入重试白名单。

















