单体应用演进为微服务是必要动作而非可选,需先解耦硬依赖、隔离数据库、分组路由;用go-zero生成服务后须重构目录与职责;API网关接入需统一Content-Type、路径重写和健康检查;数据层过渡期坚持双写不双读。

单体应用不是“不能用”,而是当它开始拖慢发布节奏、让新人不敢改代码、一次小修要全量回归时,转变就不是选项,而是必须动作。
拆分前先堵住单体里最常被忽视的耦合点
很多人一上来就想切服务,结果发现 user.go 里直接调了 order.Create(),order.go 又引用了 payment.InitConfig() —— 这种硬依赖不清理,后续任何拆分都会在运行时崩掉。
- 先用
go list -f '{{.Deps}}' ./... | grep扫描跨模块 import,识别出高频交叉引用的包 - 把共享逻辑(如通用校验、ID生成、错误码)抽到
pkg/common,禁止反向依赖 - 对数据库访问层做隔离:原单体共用一个
*sql.DB实例?立刻改为每个业务子模块持有自己的db.Conn或通过接口注入,为后续拆库铺路 - HTTP 路由不再集中注册在
main.go,改用按功能分组的router.RegisterUserRoutes()、router.RegisterOrderRoutes(),函数签名统一返回http.Handler
用 go-zero 的 goctl api new 生成服务骨架时的关键取舍
goctl api new user-service 生成的结构看似开箱即用,但直接照搬会埋坑:它默认把所有 handler、logic、model 塞进一个目录,和你原单体的 internal/service 层混在一起,后期难剥离。
- 生成后立刻重命名目录为
service/user,并把logic改为handler(go-zero 的 logic 层实际承担的是 controller 职责,和传统 MVC 的 logic 不同) - 禁止在生成的
handler里写业务逻辑,只做参数校验 + 调用internal/service/user下的真实 service 层 —— 这样你原来的业务代码几乎不用动 - 配置文件
etc/user-api.yaml中的DataSource先指向原单体的数据库连接池,别急着切分 DB,先跑通流程 - 如果原单体用了 Gin 或 Echo,暂时保留其中间件(如 JWT 验证),用
gin.WrapH(userHandler)包一层再挂到新路由,避免认证逻辑重复实现
API 网关接入阶段最容易卡住的三个地方
当把第一个微服务(比如 user-api)接入 API 网关后,请求 502 或超时不是因为服务没起来,而是网关和后端在“悄悄讲不同方言”。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
-
Content-Type不一致:网关默认转发时可能丢掉原请求头,检查网关配置中是否启用passAllHeaders: true,否则application/json可能变成text/plain - 路径重写失效:网关把
/api/v1/users转给user-api时,后端路由仍匹配/api/v1/users—— 解决办法是在user.api定义里把路径写成get /users,让网关负责前缀剥离 - 健康检查失败:go-zero 默认用
/health,但网关探测时加了 Host 头,而你的服务没配Host白名单,直接 403;要么在服务里放开 Host 校验,要么在网关健康检查配置中指定host: "user-api"
数据层过渡期必须接受“双写但不双读”
不要一上来就切数据库。单体共用 MySQL,用户服务刚拆出来就直连自己专属库?那订单服务查用户昵称时会立刻报错 —— 此时没有“完美方案”,只有可控妥协。
- 第一步:用户服务写新库(
user_db),同时通过胶水代码(pkg/legacy/user.go)双写旧库(monolith_db)的users表 - 第二步:所有读操作(包括订单、支付服务里的用户信息查询)仍走旧库,确保兼容性
- 第三步:等双写稳定一周、日志无丢失、监控无延迟后,才允许部分非关键读(如用户头像 URL)走新库
- 特别注意:双写必须用事务或最终一致性方案(如本地消息表 + 消费者),绝不能靠两个
db.Exec简单拼接,否则极易出现数据裂痕
真正的难点不在代码怎么写,而在每次上线前得确认:胶水代码有没有漏字段、网关路由有没有覆盖旧路径、健康检查探针是否还连着老地址 —— 这些细节不盯死,再漂亮的架构图也撑不过第一次真实流量。

















