子兮子兮
写一个能跑的服务很容易,写一个在生产环境“长期稳定运行”的服务很难。
很多 Go 服务在本地、测试环境表现良好,一上线就开始暴露各种问题:
这些问题往往不是业务复杂导致的,而是基础设计阶段就埋下了隐患。
这篇文章从工程角度,拆解几个「决定 Go 服务是否可靠」的关键点。
一个典型的 Go 服务,至少经历以下阶段:
很多项目只关心第 2 步。
SIGTERM 直接 killctx, stop := signal.NotifyContext(
context.Background(),
os.Interrupt,
syscall.SIGTERM,
)
defer stop()
关键思想:
context 统一管理生命周期ctx.Done()这一步做不好,后面所有“稳定性优化”都是空谈。
Go 的并发模型很强,但也很容易被滥用。
go func() {
for {
doSomething()
}
}()
问题在于:
时间一长,goroutine 数量不可控,问题只会在生产环境暴露。
所有 goroutine,必须满足至少一个条件:
- 生命周期与请求绑定
- 生命周期与服务 context 绑定
例如:
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
doSomething()
}
}
}(ctx)
能被关闭的 goroutine,才是“可维护的 goroutine”。
很多 Go 项目里,错误被悄悄吞掉了:
if err != nil {
log.Error(err)
return
}
这类代码短期没问题,长期非常危险。
if err != nil {
return fmt.Errorf("数据验证失败: %w", err)
}
原则:
这也是 Go 鼓励 error wrapping 的核心原因。
很多服务把配置当成写死的常量:
const Timeout = 3 * time.Second
上线后发现:
只能:改代码 → 打包 → 发版。
配置是系统行为的一部分,而不是代码的一部分。
实践建议:
一个能动态调整配置的服务,才是真正“活着”的服务。
很多服务日志存在两个极端:
fmt.Println至少区分三类日志:
并且:
否则日志只是一堆噪音。
生产事故之后,常听到一句话:
“要是当时有监控就好了。”
但真正有效的监控,必须在系统设计阶段就考虑。
至少需要关注:
不是为了“好看”,而是为了提前发现异常趋势。
一个 Go 服务是否“可靠”,往往不是由某个高级技巧决定的,而是:
这些东西看起来“基础”,但恰恰是:
能把系统从“能用”,推向“能长期用”的关键。
| 内容声明 | |
|---|---|
| 标题: Go 服务在生产环境中稳定运行的几个关键设计点 | |
| 链接: https://zixizixi.cn/go-services-to-run-stably-in-production-environments | 来源: iTanken |
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可,转载请保留此声明。
| |