Go微服务需显式处理三件事:配置http.Server超时参数、提供裸奔健康检查端点、全程透传context;服务发现须环境变量驱动并设注销超时。

Go 语言写微服务,核心不在于“能不能”,而在于“怎么避免掉进默认行为的坑里”。用 net/http 能跑起来,但离可用的微服务差三件事:服务发现、健康检查、请求上下文透传——这些不是可选项,是上线前必须显式处理的点。
用 gin 快速启动但别跳过 http.Server 配置
很多人直接 gin.Default().Run(":8080") 就开干,结果压测时连接堆积、超时混乱。根本原因是没控制底层 http.Server 的超时和连接生命周期。
-
ReadTimeout和WriteTimeout必须设(比如 5 秒),否则慢客户端会拖垮整个服务 -
IdleTimeout推荐设为 30–60 秒,防止长连接空耗资源 - 别用
gin.Run(),改用http.Server显式启动,方便加Shutdown逻辑
示例关键片段:
srv := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 30 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// 退出时调用 srv.Shutdown(ctx)健康检查端点必须独立于业务路由且不走中间件
Kubernetes 或 Consul 做存活探针时,如果健康检查路径被 JWT 鉴权、日志中间件或链路追踪拦截,就会反复失败重启。它必须是“裸奔”的、零依赖的响应。
立即学习“go语言免费学习笔记(深入)”;
- 路径建议固定为
/healthz,返回200 OK+ 纯文本ok - 不要挂任何中间件(包括 recovery、logger)
- 避免读数据库或调外部服务;真要校验依赖,用缓存结果 + 异步刷新
在 gin 中正确写法:
router.GET("/healthz", func(c *gin.Context) {
c.String(http.StatusOK, "ok")
})
context.Context 要从入口贯穿到 DB 查询,不能只在 handler 里用
微服务里最常被忽略的是取消传播:HTTP 请求被客户端取消,但 goroutine 还在查库、发 HTTP、等锁。根源是 DB 驱动、HTTP 客户端没接上 context。
- 所有
database/sql查询必须用db.QueryContext(ctx, ...),而非db.Query(...) - 调第三方 API 时,用
http.Client的Do(req.WithContext(ctx)) - 自定义 goroutine 启动前,用
ctx = context.WithTimeout(parentCtx, 3*time.Second)加约束
错误示范:rows, _ := db.Query("SELECT ...") —— 这个查询不会响应上游取消。
服务注册别硬编码地址,用环境变量 + 默认 fallback
本地开发、测试、生产环境的服务发现地址完全不同。写死 "consul://localhost:8500" 会导致一打包就炸。
- 读取环境变量如
DISCOVERY_ADDR,未设置时 fallback 到"127.0.0.1:8500" - 注册时服务名用
os.Getenv("SERVICE_NAME"),避免不同实例注册成同一个名字 - Consul 注册必须带
DeregisterCriticalServiceAfter(比如"90s"),否则节点宕机后服务残留
没有服务发现?至少先实现本地 DNS 解析 fallback:net.DefaultResolver.LookupHost(context.Background(), "user-service")。
微服务真正的门槛不在框架选型,而在每个请求生命周期里对 context、error、timeout 的诚实处理——Go 的简洁性容易让人误以为“少写就是健壮”,其实恰恰相反。










