开发用 air 而非 endless,因 endless 专用于生产环境优雅重启;air 支持递归监控、需配 .air.toml 的 root 和 build_cmd;endless 依赖 sigusr2 且需替换 listenandserve;配置热加载须用 fsnotify + sync.rwmutex 原子替换。

开发阶段用 fresh 或 air 就够了,别碰 endless —— 它不是给开发环境用的,强行套用反而容易卡在信号处理或文件描述符继承上。
开发期热重载:用 air 监控整个项目目录
很多人卡在 fresh 只监听当前目录,改 internal/ 下的代码不生效。根本原因是它默认只 watch 当前工作目录下的 .go 文件,而 Go 项目结构通常分层很深。
-
air默认支持递归扫描,且配置更直观;装完直接运行air就能覆盖cmd/、internal/、pkg/全部路径 - 关键配置项必须显式写进
.air.toml:root = "."(否则从子目录启动会漏监控)、build_cmd = "go build -o ./tmp/main ./cmd/server" - 避免踩坑:如果项目用了
go.work或多模块,air的build_cmd必须指定完整构建路径,不能只写go build - Windows 下若提示
exec: "sh": executable file not found,把build_cmd改成go build -o ./tmp/main ./cmd/server,去掉 shell 调用
生产环境零停机更新:endless 不是“热更新”,而是优雅重启
endless 的本质是 fork 新进程 + 传递监听 socket + 等待旧连接关闭,它不 reload 代码,也不 hot-swap 函数——你改完代码后仍需重新编译生成新二进制,再发 SIGUSR2 触发切换。
- 旧进程不会立刻退出,而是继续处理已 accept 的连接,直到超时(默认 30s)或主动调用
endless.Close() - 新进程启动失败时,旧进程继续服务,这是它比裸调
http.Server.Shutdown()更可靠的地方 - 必须用
endless.ListenAndServe()替代router.Run(),且不能和gin.Engine.Run()混用,否则端口绑定冲突 - 注意信号权限:Linux 下需确保进程有权限接收
SIGUSR2,容器中常因init进程接管信号而失效,建议加--init启动或换用tini
配置热加载:别依赖框架,用 fsnotify + 原子替换
Gin 本身不提供配置热加载能力,所谓“Gin 支持热更新”是误传。真正可行的是监听配置文件变更后,安全地替换内存中的配置实例。
- 不要直接修改全局变量,用
sync.RWMutex包裹配置结构体,读用RLock,写用Lock - 监听
fsnotify的WriteEvent时,先解析新配置到临时结构体,验证通过后再原子 swap,避免中间态错误 - INI/YAML/TOML 文件被编辑器保存时可能触发多次事件(如先写临时文件再 rename),务必过滤掉
.tmp、~结尾的路径 - HTTP handler 中每次读配置都应走
RUnlock后的副本,而不是持有锁读取,否则高并发下会阻塞请求
真正的热更新没有银弹:开发期靠 air 快速反馈,生产期靠 endless 控制进程生命周期,配置靠自己搭监听+原子更新——三者职责分明,混用反而增加不可控路径。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











