Go微服务需从go mod init起严格遵循规范:服务边界须单一职责、接口RESTful且带版本、通信显式超时、context全程传递、依赖锁死并封装SDK、日志透传request_id,否则三个月内必现依赖冲突、追踪失效等问题。

Go微服务不是“写完能跑就行”,而是从 go mod init 第一行开始就决定后续能否平稳演进。不按规范组织的项目,三个月后基本都会卡在依赖冲突、日志无法追溯、接口版本混乱或熔断失效上。
服务边界怎么划才不会后期返工
划错边界的典型症状是:一个服务同时调用用户库、订单库、支付网关,还顺手发邮件——这已经不是微服务,是披着Go外衣的单体碎片。
- 每个服务必须有且仅有一个业务动词主语,比如
order-service只处理“创建订单、查询订单状态、取消订单”,不碰“用户认证”或“库存扣减” - 路径参数必须直接映射领域实体ID,例如
/orders/{order_id}合法,/api/v1/getOrder?id=xxx违反RESTful且破坏可缓存性 - 跨服务数据引用禁止用SQL JOIN或直接查对方数据库,必须走定义好的HTTP接口或事件订阅,例如库存变更通过
inventory.updated事件通知订单服务 - 服务间通信超时必须显式设值,
http.Client不配Timeout或Context.WithTimeout,等于默认挂起30秒以上,会拖垮整个调用链
接口定义为什么必须带版本号且不能只靠URL
只用 /v1/users 这种路径版本,看似简单,实际会让客户端升级变成一场灾难:前端发请求时根本不知道自己用的是 v1 还是 v2,网关也无法做灰度路由。
- 推荐双版本策略:
Accept: application/json; version=v2做主控,/users路径保持不变;同时保留/v2/users供老客户端临时兼容 -
user.get:v2这类命名(来自Go后端接口定义规范)只适用于内部RPC协议,HTTP对外暴露必须用标准HTTP头或路径,否则OpenAPI工具无法识别 - 新增字段必须向后兼容,删除字段必须先标记
deprecated: true并保留至少两个大版本,否则gRPC或Protobuf生成的客户端会panic - 错误码不能只靠HTTP状态码,
400要附带具体业务码如{"code": 40001, "message": "缺少必填参数 user_id"},否则前端无法做精细化提示
goroutine泄漏和context传递最容易被忽略的三个点
90% 的线上内存增长和 goroutine 泄漏,都源于没把 context.Context 当成函数第一参数来设计。
- 所有对外HTTP调用、数据库查询、RPC请求,必须传入带超时的
ctx,例如db.QueryRowContext(ctx, sql, args...),而不是db.QueryRow(sql, args...) - 启动新 goroutine 时,绝不能直接用外部
ctx,必须用ctx = context.WithCancel(parentCtx)或context.WithTimeout显式派生,否则父ctx cancel后子goroutine还在跑 - 中间件里修改
context.WithValue后,必须确保下游handler真用了它——很多开发者塞了user_id进ctx,但handler里直接取r.URL.Query().Get("uid"),导致ctx传递形同虚设
go.mod 和依赖管理不是“go mod tidy一下就完事”
生产环境出问题,80% 源于 go.mod 里混进了 // indirect 依赖却没锁死版本,或者用了 replace 指向本地路径但CI没同步。
-
go mod vendor必须进CI流程,禁止依赖 GOPROXY 在构建时动态拉包,否则某天上游删库你就发布失败 - 禁止在
go.mod里写replace github.com/some/lib => ./local-fix,正确做法是 fork 后提交修复,再replace到你的fork地址,并附PR链接说明原因 - 第三方SDK(如阿里云OSS、微信支付)必须封装一层薄适配层,把它们的 error 类型转成你项目的统一
errors.Code枚举,否则一旦SDK升级改了error结构,你的全局错误捕获就全崩
最常被跳过的其实是日志上下文透传——从入口HTTP handler开始,每个goroutine都要带上 request_id,但没人检查中间件是否真把 ctx 传给了DB驱动或RPC client。这点一漏,问题定位时间直接翻三倍。


















