服务重启后定时任务丢失,因元数据仅存内存;应将task definition、lastRunAt、nextRunAt等存SQLite,启动时加载重建。gocron需在s.Cron().Do()后立即写库,s.Start()前遍历enabled=1记录恢复任务,Do函数须为命名函数。

服务重启后定时任务状态丢失,本质是把任务元数据存在了内存里。只要把 task definition 和 lastRunAt、nextRunAt 这类关键字段存到磁盘,并在启动时重新加载,就能解决。
用 gocron + SQLite 恢复任务定义
gocron 默认所有任务都在内存中,s.Start() 前没注册的任务就彻底消失。要让它“记住”自己干过什么,得在每次添加任务时,把任务配置写进数据库;启动时再从库中读出来,逐个调用 s.Cron() 或 s.Every() 重建。
- 表结构至少包含:
id(主键)、cron_expr(如"0 * * * *")、job_func(函数名或标识符)、args(JSON 字符串)、last_run_at、next_run_at、enabled - 写入时机:调用
s.Cron().Do(...)后,立刻用db.Exec("INSERT ...")存一条记录 - 恢复逻辑必须放在
s.Start()之前:遍历表中enabled = 1的行,解析cron_expr和args,再调用对应函数注册任务 - 注意
Do()的函数不能是闭包或带局部变量的匿名函数——序列化后无法还原,应统一用命名函数名字符串 + 参数传递
time.AfterFunc 无法持久化,别硬套
time.AfterFunc 创建的是单次 goroutine,生命周期绑定当前进程。哪怕你把它对应的 delayUntil 时间戳存进 BoltDB,进程一挂,那个 goroutine 就永远消失了,不会自动复活。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
- 它只适合“本次运行期间保证执行一次”的场景,比如 HTTP 请求超时清理、临时资源释放
- 若你真想用延时通知且要求重启不丢,必须放弃
AfterFunc,改用带调度循环的方案:从 BoltDB 查出所有isSent = false AND delayUntil 的任务,批量触发,并更新 <code>isSent = true - 别试图给
AfterFunc加 wrapper 封装成“可恢复”,这只会掩盖问题——goroutine 本身不可序列化,强行保存地址或闭包引用毫无意义
BoltDB 写入后必须 Commit,否则重启就读不到
很多人以为调用 tx.Put() 就完事了,其实没 tx.Commit(),数据根本没落盘。BoltDB 是 memory-mapped 文件,未提交的事务只存在于 mmap buffer 中,进程崩溃或 kill -9 会直接丢弃。
立即学习“go语言免费学习笔记(深入)”;
- 每个写操作必须包裹在
db.Update(func(tx *bolt.Tx) error {...})里,这是唯一安全方式 - 不要用
db.View()做写操作,它只读,调Put会 panic - 批量插入任务时,别为每条记录开一个事务——性能差且易失败;应在一个事务里循环
Put,全部成功才Commit - 如果任务状态更新(如标记
isSent = true)和发送逻辑不在同一事务里,可能出现“发了但没记”或“记了但没发”的不一致
真正难的不是存数据,而是让“调度时间点”和“存储状态”严格对齐。比如一个任务本该在 14:00 执行,但进程在 13:59 崩溃,重启后必须能准确识别出这条任务已过期并立即执行——这要求你在加载时做 delayUntil 判断,而不是只看 <code>isSent 字段。

















