重构应以解决交付瓶颈、扩容限制或协作摩擦为目标,而非盲目追求微服务;优先抽取internal/shared模块,避免伪微服务返工;配置、日志、错误、数据库需统一抽象;首个服务宜选高内聚低依赖模块如Connect或Message;服务间通信按场景选HTTP/gRPC/消息队列;接受最终一致性,用幂等、补偿、重试保障数据一致;务必全局替换shared基础组件。

重构不是为了微服务而微服务,而是为了解决单体里已经卡住的交付节奏、扩容瓶颈或团队协作摩擦。如果你的单体还能快速迭代、稳定发布、新人两周能上手改功能,那现在就拆,大概率是给自己挖坑。
先抽 internal/shared,再动服务拆分
90% 的 Go 微服务项目在拆分后三个月就开始“伪微服务”返工:每个服务都自己写 loadConfig()、自己拼 log.With().Str("service", "xxx")、自己实现 /healthz 路由。这不是架构升级,是配置和日志的分布式重复劳动。
-
config模块必须支持 fallback(本地 YAML + 远程 Etcd),带变更通知和版本快照,否则配置一改全挂 -
logger必须封装zap.SugaredLogger并注入trace_id字段,禁止任何服务直接 importzap -
errors要统一返回结构:errors.NewBadRequest("invalid user_id")一类带 HTTP 状态码和业务前缀的构造函数 -
db不是简单封装gorm,而是定义DBTX接口 + 统一连接池参数 + 自动迁移开关(生产环境默认关)
第一个服务别拆 auth,优先切消息或设备管理
把 JWT 签发、OAuth2 回调、短信验证码全塞进 auth-service,结果它既慢(依赖短信网关)、又重(要查用户 profile)、还难测(第三方回调不可控)。这不是解耦,是把所有脏活打包甩锅。
- 真正适合早期拆出的服务,是高内聚、低外部依赖、边界清晰的模块,比如 GIM 架构里的
Connect(长连接管理)或Message(消息存储与转发) - 这类服务不依赖用户完整信息,只处理连接状态或消息元数据,可独立部署、压测、扩缩容
- 拆完立刻能验证:用
curl直连新服务接口,不经过原单体路由,确认流量隔离有效
服务间通信别默认选 gRPC,先看调用模式
gRPC 适合高频、低延迟、内部强契约的调用,比如 Logic 服务调 Message 服务发消息;但它不适合需要穿透公网、兼容第三方系统或频繁变更接口的场景。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 对外暴露的 API(如给前端或 App 调用)仍走 RESTful HTTP,别强行套 gRPC gateway
- 异步事件(如“用户注册成功”后触发发欢迎邮件)必须走消息队列,用
pkg/mq/mq.go封装,避免同步阻塞 - 跨语言或需调试的调用(比如 Java 服务调 Go 服务),优先用 JSON over HTTP,协议成本比 protobuf 低得多
- 所有 gRPC 接口必须用
protocol/proto/logic/message.int.proto这类明确路径定义,禁止 inline 定义
数据一致性不能靠“强一致”,得接受最终一致
单体里一个事务 update 用户表 + insert 订单表,拆成两个服务后,想用分布式事务硬保 ACID,基本等于放弃可用性。真实生产环境靠的是补偿+重试+幂等,不是两阶段提交。
- 用户注册成功后,
User服务发UserRegisteredEvent到消息队列,Profile服务消费并建默认档案 —— 中间可能有秒级延迟,但不会丢 - 所有写操作接口必须带幂等 key(如
X-Request-ID),下游服务收到重复 event 能安全忽略 - 关键链路(如支付回调)加本地事务 + 补偿任务表,失败后由定时 job 扫描重试,而不是依赖跨服务回滚
最常被跳过的动作,不是写 proto 或配 Etcd,而是把 internal/shared 的 logger 和 errors 在所有旧代码里 grep 替换掉 —— 这步没做完,后续所有服务日志对不上、错误码乱飞、trace 断链,监控就形同虚设。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










