os.Create不能用于flock加锁,因其默认以os.O_WRONLY|os.O_CREATE|os.O_TRUNC打开文件,缺乏读权限,导致多个进程获得独立fd、flock失效;必须用os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)显式声明读写权限。

文件队列不是“退而求其次”的妥协,而是特定场景下更可控的选择——只要避开 flock 误用、序列化静默失败、重复消费这三类典型陷阱,单机多进程协同就能稳定跑满磁盘 I/O。
为什么 os.Create 不能用于 flock 加锁
Go 标准库的 flock 在只写模式下行为不可靠,而 os.Create 默认打开方式是 os.O_WRONLY | os.O_CREATE | os.O_TRUNC,不带读权限。多个进程用 os.Create 打开同一路径,会各自获得独立句柄,flock 锁的是句柄而非路径,导致锁失效。
- 必须用
os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)显式声明读写权限 - 加锁前检查文件是否存在且可读写,避免因权限或路径问题静默失败
- 不要复用已关闭的文件描述符——
syscall.Flock对已关闭 fd 的调用可能返回成功但实际无效
JSON 解码必须走结构体 + Validate(),不能用 interface{}
直接 json.Unmarshal 到 interface{} 看似灵活,但字段缺失、类型错位、空字符串当数字等错误全被吞掉,任务进队列就“消失”,日志里只有一行 nil pointer dereference。
- 定义明确结构体(如
Task),所有字段带json:tag - 解码后立即调用
.Validate(),校验ID非空、At不过期等业务约束 -
Args字段若需动态结构,先解到map[string]json.RawMessage,再按需反序列化,避免一次性全解引发 panic
消费者如何靠原子重命名防重复和漏消费
文件队列没有 ACK 协议,靠文件系统原子性模拟确认:任务处理成功后,把 pending/xxx.json 移动到 done/xxx.json。这个 mv 操作在同分区下是原子的,失败则原文件仍在 pending/ 下可重试。
立即学习“go语言免费学习笔记(深入)”;
- 移动前确保目标目录存在,
os.MkdirAll("done", 0755)不能省略 - 不要用
os.Rename跨设备移动——它会退化为 copy+delete,失败时残留副本 - 移动后立刻
os.Remove("pending/xxx.json")是错的;正确做法就是os.Rename到 done 目录,失败则保留原文件继续轮询 - 守护程序定期扫描
pending/下超时未处理的文件(比如 >5 分钟),触发告警或人工介入
真正难的不是第一次把任务写进文件,而是当进程 crash、磁盘满、权限突变时,队列还能从断点续上——所有状态变更都得落在文件系统语义内,不能依赖内存或临时变量。


















