应使用filepath.walk并配合错误处理与跳过逻辑:对os.ispermission(err)等返回nil继续遍历,用time.now().utc()统一判断过期,删除前校验文件类型,避免符号链接循环和权限崩溃。

如何用 os.Walk 安全遍历目录并识别过期文件
直接用 filepath.Walk 或 os.Walk 遍历容易因权限拒绝或符号链接循环崩溃,必须加错误处理和跳过逻辑。真实场景中,/proc、/sys 这类虚拟文件系统常触发 syscall.EACCES,不拦截会导致整个清理中断。
- 传入的
WalkFunc必须判断err != nil,对os.IsPermission(err)和os.IsNotExist(err)返回nil继续遍历,其他错误才向上抛 - 避免递归进入挂载点(如 Docker overlay2 的
merged目录),可用syscall.Statfs检查Statfs.Type是否为0x794c7630(tmpfs)或跳过已知危险路径前缀 -
os.FileInfo.ModTime()返回本地时区时间,但服务器通常设为 UTC —— 判断过期必须统一用time.Now().UTC()计算,否则跨时区部署会误删
os.Remove 删除前为什么必须校验文件类型和权限
监控脚本若直接对 os.Remove 结果不做判断,可能静默失败:比如目标是目录却没加 os.RemoveAll,或文件被其他进程占用(Windows 常见 ERROR_SHARING_VIOLATION),或 sticky bit 目录下无写权限。
- 先用
fi.Mode().IsRegular()确保是普通文件,跳过设备文件、socket、pipe 等非存储型 inode - 调用
os.Remove后检查返回 err:os.IsNotExist(err)可忽略;errors.Is(err, syscall.ERROR_SHARING_VIOLATION)(Windows)或errors.Is(err, syscall.EBUSY)(Linux)应记录并跳过,不重试 - 避免用
os.Chmod(path, 0777)强制解锁 —— 这会破坏原有权限模型,且对只读挂载无效
守护进程如何避免重复启动和资源泄漏
用 os.FindProcess 检 PID 文件不可靠(PID 可能被复用),真正防多实例得靠文件锁。Go 标准库没提供跨平台 flock,得用 syscall.Flock 手动封装,且必须确保进程退出时释放锁。
- 在
/var/run/cleaner.pid创建文件后,立即用syscall.Flock(fd, syscall.LOCK_EX|syscall.LOCK_NB)尝试独占锁,失败则说明已有实例运行 - 锁文件 fd 必须全程保持打开状态 —— 若提前
Close(),锁自动释放,其他实例就能抢入 - 用
signal.Notify捕获os.Interrupt和syscall.SIGTERM,在 defer 中syscall.Flock(fd, syscall.LOCK_UN)解锁并os.Remove(pidfile) - 别依赖
runtime.GC()—— 清理任务本身不产生大量堆对象,频繁调用反而干扰调度
定时触发选 time.Ticker 还是系统 cron
纯 Go 守护进程用 time.Ticker 更可控,但要注意信号中断和时间漂移。系统 cron 启动新进程更简单,但无法共享内存状态(比如上次清理位置),且日志分散难排查。
- 用
ticker := time.NewTicker(2 * time.Hour)后,在 select 中监听ticker.C和sigChan,收到信号立即停止 ticker 并退出 - 避免用
time.Sleep替代 ticker —— 运行耗时超过间隔时,下次执行会堆积,而 ticker 会丢弃错过的 tick - 首次运行建议加随机偏移:
time.After(time.Duration(rand.Int63n(int64(15 * time.Minute)))),防止集群内所有节点同时打满磁盘 IO - 如果清理逻辑可能超时(如遍历 TB 级目录),需用
context.WithTimeout包裹核心函数,超时主动 return,不阻塞 ticker 下一周期
真正的难点不在遍历或删除,而在“什么时候不该删”——比如某个日志文件刚被 tail -f 打开,ModTime 是旧的,但内容还在追加。这类情况得结合 lsof 或 /proc/*/fd 检查句柄,不是单纯看时间戳能解决的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











