纲要
- 引言
- 微服务拆分原则
- 拆分粒度:不是越细越好
- 拆分维度与实践
- 模块维度
- 功能维度
- 读写维度
- AOP 维度
- 分层维度
- 服务化:从拆到"微服务"
- 服务化定义与特征
- 服务 vs 组件 vs 子系统
- 通信机制:服务的"神经"
- RPC:内部服务调用
- API 网关:对外统一的"门面"
- 消息队列:异步与解耦
- 无状态设计:弹性伸缩的基石
- 有状态带来的扩展困境
- 共享型状态:集中存储,服务无状态
- 流程型状态:事件驱动替代直接调用
- Go‑Zero 实践示例
- 基于领域拆分的项目结构
- 定义 RPC 服务与 API 网关
- 引入消息队列解耦订单与物流
- 实现无状态的用户认证
- 结语
引言
构建微服务系统时,我们很快会面临一连串核心决策:应该把系统拆得多细?拆分出来的单元如何真正成为"服务"而非组件?服务之间如何高效、可靠地通信?怎样避免服务实例承载状态而牺牲弹性? 这些问题若不加以回答,微服务化可能带来的不是敏捷,而是一团乱麻。
本文将结合 Go‑Zero 框架,系统地梳理微服务落地的四个核心要素:拆分原则 、服务化 、通信机制 以及 无状态设计。 我们不仅讨论理论,还会提供可直接参考的代码片段和架构图示,帮助你的团队在工程中做出更优选择。
微服务拆分原则
拆分粒度:不是越细越好
一个直观的误区是"服务越细,维护性越好"。 理论上,极细的拆分可以让每个服务专注单一责任,但其副作用常常被低估:
- 运维复杂度攀升:单体应用只需一台机器,拆分出 3 个服务可能就需要 3 台机器,加上容器编排、监控、日志聚合,运维成本指数级增加。
- 团队规模压力:每个独立的服务需要独立的演进和维护,这要求更高的团队人数和协作成本。
- 业务复杂度叠加:分布式事务、网络延迟、数据一致性等分布式系统固有难题会被放大。
因此,拆分应当适度合理,以团队能力、业务复杂度、访问量作为校准粒度的依据:
| 考量因素 | 描述 |
|---|---|
| 团队人力 | 是否有足够的人手和技能维护拆分后的服务? |
| 业务复杂度 | 业务边界是否清晰?领域模型能否独立? |
| 访问量与性能需求 | 是否需要对热点业务或高读写模块独立伸缩? |
拆分维度与实践
拆分不是"拍脑袋"行为,常见的拆分维度可分为五类,其中前三种最为常用。
模块维度
将同一类业务功能聚合为一个模块,并以此为单位拆出独立服务。这是最基本的拆分方式。例如一个电商系统初始可拆分为:
- 商品服务
- 订单服务
- 用户服务
在 Go‑Zero 项目中,你可能看到这样的初始目录划分:
dir
service/
├── product/
├── order/
└── user/
每个目录下会包含各自的 API 定义、RPC 定义和业务逻辑。
功能维度
聚焦于模块内某个复杂度高或具有特殊访问模式的功能,将其单独拆出。典型的如:
- 从订单模块中拆分出购物车服务 、支付服务 、秒杀服务。
秒杀服务因其巨大的瞬时流量和独立的库存逻辑,非常适合独立为一个服务,避免影响正常订单流程。
读写维度
当某些实体的读写负载差异悬殊时,可采用 CQRS 思路将读模型和写模型分离。例如商品搜索服务(读压力大)和商品更新服务(写压力相对小)可以独立部署,优化不同方向的资源分配。
#mermaid-svg-Ghj28LU9N5XKxHaB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ghj28LU9N5XKxHaB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ghj28LU9N5XKxHaB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ghj28LU9N5XKxHaB .error-icon{fill:#552222;}#mermaid-svg-Ghj28LU9N5XKxHaB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ghj28LU9N5XKxHaB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ghj28LU9N5XKxHaB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ghj28LU9N5XKxHaB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ghj28LU9N5XKxHaB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ghj28LU9N5XKxHaB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ghj28LU9N5XKxHaB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ghj28LU9N5XKxHaB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ghj28LU9N5XKxHaB .marker.cross{stroke:#333333;}#mermaid-svg-Ghj28LU9N5XKxHaB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ghj28LU9N5XKxHaB p{margin:0;}#mermaid-svg-Ghj28LU9N5XKxHaB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ghj28LU9N5XKxHaB .cluster-label text{fill:#333;}#mermaid-svg-Ghj28LU9N5XKxHaB .cluster-label span{color:#333;}#mermaid-svg-Ghj28LU9N5XKxHaB .cluster-label span p{background-color:transparent;}#mermaid-svg-Ghj28LU9N5XKxHaB .label text,#mermaid-svg-Ghj28LU9N5XKxHaB span{fill:#333;color:#333;}#mermaid-svg-Ghj28LU9N5XKxHaB .node rect,#mermaid-svg-Ghj28LU9N5XKxHaB .node circle,#mermaid-svg-Ghj28LU9N5XKxHaB .node ellipse,#mermaid-svg-Ghj28LU9N5XKxHaB .node polygon,#mermaid-svg-Ghj28LU9N5XKxHaB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ghj28LU9N5XKxHaB .rough-node .label text,#mermaid-svg-Ghj28LU9N5XKxHaB .node .label text,#mermaid-svg-Ghj28LU9N5XKxHaB .image-shape .label,#mermaid-svg-Ghj28LU9N5XKxHaB .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ghj28LU9N5XKxHaB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ghj28LU9N5XKxHaB .rough-node .label,#mermaid-svg-Ghj28LU9N5XKxHaB .node .label,#mermaid-svg-Ghj28LU9N5XKxHaB .image-shape .label,#mermaid-svg-Ghj28LU9N5XKxHaB .icon-shape .label{text-align:center;}#mermaid-svg-Ghj28LU9N5XKxHaB .node.clickable{cursor:pointer;}#mermaid-svg-Ghj28LU9N5XKxHaB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ghj28LU9N5XKxHaB .arrowheadPath{fill:#333333;}#mermaid-svg-Ghj28LU9N5XKxHaB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ghj28LU9N5XKxHaB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ghj28LU9N5XKxHaB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ghj28LU9N5XKxHaB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ghj28LU9N5XKxHaB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ghj28LU9N5XKxHaB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ghj28LU9N5XKxHaB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ghj28LU9N5XKxHaB .cluster text{fill:#333;}#mermaid-svg-Ghj28LU9N5XKxHaB .cluster span{color:#333;}#mermaid-svg-Ghj28LU9N5XKxHaB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ghj28LU9N5XKxHaB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ghj28LU9N5XKxHaB rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ghj28LU9N5XKxHaB .icon-shape,#mermaid-svg-Ghj28LU9N5XKxHaB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ghj28LU9N5XKxHaB .icon-shape p,#mermaid-svg-Ghj28LU9N5XKxHaB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ghj28LU9N5XKxHaB .icon-shape .label rect,#mermaid-svg-Ghj28LU9N5XKxHaB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ghj28LU9N5XKxHaB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ghj28LU9N5XKxHaB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ghj28LU9N5XKxHaB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 高并发读取
更新
异步同步
商品搜索服务 读
搜索引擎/缓存
商品管理服务 写
数据库
AOP 维度
面向切面编程关注点,比如权限校验、日志、限流等横切逻辑,可以将它们集中到一个独立的"切面服务",供其他服务通过拦截器或网关统一调用,避免代码重复。
分层维度
按照代码架构层次进行拆分,例如独立出控制层 (Controller) 、业务逻辑层 (Logic) 和数据访问层 (Repository)。不过在实际微服务中,这种分层通常体现在服务内部,跨服务的分层拆分相对少见,更多是前三类维度的补充。
服务化:从拆到"微服务"
服务化定义与特征
将一个大单体拆成若干部分,这些部分不一定就是真正的"微服务",它们可能只是组件 或子系统 。服务化指将应用拆分为一系列独立进程的服务,每个服务具备特定业务能力,并通过轻量级通信协议协作。
服务化的四个核心特征:
- 单一职责:每个服务专注完成某一类明确功能。
- 独立部署:可单独构建、发布、运行,不依赖其他服务的代码库。
- 轻量级通信:通过 RPC、HTTP、消息队列等实现服务间交互。
- 数据自治:每个服务通常拥有自己的存储,不共享数据库(理想情况)。
服务 vs 组件 vs 子系统
| 对比维度 | 服务 (Service) | 组件 (Component/Library) | 子系统 (Sub-system) |
|---|---|---|---|
| 运行形式 | 独立进程,可独立部署 | 项目内依赖,随主程序运行 | 独立系统,可能由多个服务组成 |
| 业务完整度 | 单一业务能力 | 功能单元,可复用 | 完整的业务垂直领域 |
| 升级影响 | 内部升级对外无感知(接口不变) | 升级可能导致调用方重新编译 | 子系统内部变更对外隔离 |
| 交互方式 | 网络协议 (RPC/HTTP/MQ) | 进程内方法调用 | 对外暴露有限接口 |
| 耦合性 | 松耦合,但有业务依赖 | 紧耦合,代码级依赖 | 松耦合,业务关联低 |
关键区别:拆分出的服务可以独立部署;而组件只是被编译进一个应用里,生命周期和主程序绑定;子系统则是一套独立完整的"小系统",内部故障基本不波及其他子系统。微服务虽然分开部署,但从业务角度看仍然统一属于同一个大项目,彼此之间存在较强的业务依赖关系,一个服务宕机会直接影响整体链路。
通信机制:服务的"神经"
在微服务中,最常见的三种通信方式是 RPC 、RESTful API 和 消息队列 (MQ)。它们各司其职。
#mermaid-svg-g79wKhvHsKKkDXg3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-g79wKhvHsKKkDXg3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-g79wKhvHsKKkDXg3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-g79wKhvHsKKkDXg3 .error-icon{fill:#552222;}#mermaid-svg-g79wKhvHsKKkDXg3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-g79wKhvHsKKkDXg3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-g79wKhvHsKKkDXg3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-g79wKhvHsKKkDXg3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-g79wKhvHsKKkDXg3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-g79wKhvHsKKkDXg3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-g79wKhvHsKKkDXg3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-g79wKhvHsKKkDXg3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-g79wKhvHsKKkDXg3 .marker.cross{stroke:#333333;}#mermaid-svg-g79wKhvHsKKkDXg3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-g79wKhvHsKKkDXg3 p{margin:0;}#mermaid-svg-g79wKhvHsKKkDXg3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-g79wKhvHsKKkDXg3 .cluster-label text{fill:#333;}#mermaid-svg-g79wKhvHsKKkDXg3 .cluster-label span{color:#333;}#mermaid-svg-g79wKhvHsKKkDXg3 .cluster-label span p{background-color:transparent;}#mermaid-svg-g79wKhvHsKKkDXg3 .label text,#mermaid-svg-g79wKhvHsKKkDXg3 span{fill:#333;color:#333;}#mermaid-svg-g79wKhvHsKKkDXg3 .node rect,#mermaid-svg-g79wKhvHsKKkDXg3 .node circle,#mermaid-svg-g79wKhvHsKKkDXg3 .node ellipse,#mermaid-svg-g79wKhvHsKKkDXg3 .node polygon,#mermaid-svg-g79wKhvHsKKkDXg3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-g79wKhvHsKKkDXg3 .rough-node .label text,#mermaid-svg-g79wKhvHsKKkDXg3 .node .label text,#mermaid-svg-g79wKhvHsKKkDXg3 .image-shape .label,#mermaid-svg-g79wKhvHsKKkDXg3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-g79wKhvHsKKkDXg3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-g79wKhvHsKKkDXg3 .rough-node .label,#mermaid-svg-g79wKhvHsKKkDXg3 .node .label,#mermaid-svg-g79wKhvHsKKkDXg3 .image-shape .label,#mermaid-svg-g79wKhvHsKKkDXg3 .icon-shape .label{text-align:center;}#mermaid-svg-g79wKhvHsKKkDXg3 .node.clickable{cursor:pointer;}#mermaid-svg-g79wKhvHsKKkDXg3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-g79wKhvHsKKkDXg3 .arrowheadPath{fill:#333333;}#mermaid-svg-g79wKhvHsKKkDXg3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-g79wKhvHsKKkDXg3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-g79wKhvHsKKkDXg3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g79wKhvHsKKkDXg3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-g79wKhvHsKKkDXg3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g79wKhvHsKKkDXg3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-g79wKhvHsKKkDXg3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-g79wKhvHsKKkDXg3 .cluster text{fill:#333;}#mermaid-svg-g79wKhvHsKKkDXg3 .cluster span{color:#333;}#mermaid-svg-g79wKhvHsKKkDXg3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-g79wKhvHsKKkDXg3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-g79wKhvHsKKkDXg3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-g79wKhvHsKKkDXg3 .icon-shape,#mermaid-svg-g79wKhvHsKKkDXg3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g79wKhvHsKKkDXg3 .icon-shape p,#mermaid-svg-g79wKhvHsKKkDXg3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-g79wKhvHsKKkDXg3 .icon-shape .label rect,#mermaid-svg-g79wKhvHsKKkDXg3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g79wKhvHsKKkDXg3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-g79wKhvHsKKkDXg3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-g79wKhvHsKKkDXg3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} REST API
RPC
RPC
RPC
发消息
订阅
用户
网关
订单服务
商品服务
消息队列
物流服务
RPC:内部服务调用
RPC (Remote Procedure Call) 让调用远程服务像调用本地函数一样简单。在内部服务密集交互的场景下,RPC 是首选。Go‑Zero 内置基于 gRPC 的 RPC 框架,支持服务发现、超时控制、负载均衡等。
RPC 的特点:
- 实时性高:同步调用,立即返回结果。
- 强类型契约:通过 protobuf 定义接口,减少沟通成本。
- 深度调用链问题:如果链路过长(A→B→C→D),会增加整体延迟和耦合度。
API 网关:对外统一的"门面"
对外部客户端(浏览器、App),暴露一堆独立的服务端点并不友好。引入 API 网关后,客户端只与网关通信,网关将请求路由到对应内部服务。网关与后端服务之间通常仍采用 RPC 通信,因为服务启动后已经用 RPC 协议暴露了处理请求的能力。
在 Go‑Zero 中,goctl 可以快速生成网关的代码骨架,网关本质是一个对外 HTTP 服务,内部通过 RPC 客户端调用其余微服务。
消息队列:异步与解耦
并非所有交互都需要同步等待。例如"下单后发短信通知"、"更新物流状态"等非核心实时任务,使用消息队列可以:
- 削峰填谷:应对突发流量。
- 解耦服务:订单服务发布事件,物流服务订阅,双方无需直接依赖。
- 提高可靠性:消息持久化,避免服务宕机导致数据丢失。
RPC vs 消息队列:
| 维度 | RPC | 消息队列 |
|---|---|---|
| 实时性 | 高,同步等待 | 低,异步处理 |
| 耦合度 | 较高,调用方依赖被调用方 | 较低,发布/订阅解耦 |
| 适用场景 | 实时查询、需要立即响应的命令 | 异步任务、事件通知、最终一致性 |
| 复杂度 | 网络调用、超时、重试 | 消息顺序、重复消费、幂等性 |
实际项目中,通常混合使用:核心业务流程用 RPC,旁路或后续动作由消息队列承担。
无状态设计:弹性伸缩的基石
有状态带来的扩展困境
状态:如果一个数据需要被多个服务共享才能完成一笔业务,这个数据就是"状态"。当一个服务依赖本地存储的状态来处理请求时,它便成了"有状态服务"。典型场景如基于 session 的用户认证:
- 用户登录,用户服务在本地内存/文件保存 session。
- 后续请求(如查询订单)到达,订单服务需要验证用户身份,必须再去调用用户服务查 session。
- 此时用户服务承载了所有服务的依赖,成为瓶颈;若用户服务宕机,整个系统登录态失效。
UserSvc OrderSvc Client UserSvc OrderSvc Client #mermaid-svg-5Tnd801TCV8YND6Y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5Tnd801TCV8YND6Y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5Tnd801TCV8YND6Y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5Tnd801TCV8YND6Y .error-icon{fill:#552222;}#mermaid-svg-5Tnd801TCV8YND6Y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5Tnd801TCV8YND6Y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5Tnd801TCV8YND6Y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5Tnd801TCV8YND6Y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5Tnd801TCV8YND6Y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5Tnd801TCV8YND6Y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5Tnd801TCV8YND6Y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5Tnd801TCV8YND6Y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5Tnd801TCV8YND6Y .marker.cross{stroke:#333333;}#mermaid-svg-5Tnd801TCV8YND6Y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5Tnd801TCV8YND6Y p{margin:0;}#mermaid-svg-5Tnd801TCV8YND6Y .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5Tnd801TCV8YND6Y text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-5Tnd801TCV8YND6Y .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5Tnd801TCV8YND6Y .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-5Tnd801TCV8YND6Y .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-5Tnd801TCV8YND6Y .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-5Tnd801TCV8YND6Y #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-5Tnd801TCV8YND6Y .sequenceNumber{fill:white;}#mermaid-svg-5Tnd801TCV8YND6Y #sequencenumber{fill:#333;}#mermaid-svg-5Tnd801TCV8YND6Y #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-5Tnd801TCV8YND6Y .messageText{fill:#333;stroke:none;}#mermaid-svg-5Tnd801TCV8YND6Y .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5Tnd801TCV8YND6Y .labelText,#mermaid-svg-5Tnd801TCV8YND6Y .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-5Tnd801TCV8YND6Y .loopText,#mermaid-svg-5Tnd801TCV8YND6Y .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-5Tnd801TCV8YND6Y .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5Tnd801TCV8YND6Y .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-5Tnd801TCV8YND6Y .noteText,#mermaid-svg-5Tnd801TCV8YND6Y .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-5Tnd801TCV8YND6Y .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5Tnd801TCV8YND6Y .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5Tnd801TCV8YND6Y .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5Tnd801TCV8YND6Y .actorPopupMenu{position:absolute;}#mermaid-svg-5Tnd801TCV8YND6Y .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-5Tnd801TCV8YND6Y .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5Tnd801TCV8YND6Y .actor-man circle,#mermaid-svg-5Tnd801TCV8YND6Y line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-5Tnd801TCV8YND6Y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 持有状态,成为瓶颈 查询订单验证 token/session验证结果订单数据
有状态服务在伸缩、灰度发布、故障恢复等方面都有显著缺陷。
共享型状态:集中存储,服务无状态
共享型状态 指状态仅由某个服务维护,其他服务只读取、不修改。典型解决之道是外部化存储:将 session 移入分布式缓存(如 Redis),所有服务从 Redis 校验登录态,用户服务本身不再持有状态。
#mermaid-svg-GgpXfuUzbss5nWKy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GgpXfuUzbss5nWKy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GgpXfuUzbss5nWKy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GgpXfuUzbss5nWKy .error-icon{fill:#552222;}#mermaid-svg-GgpXfuUzbss5nWKy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GgpXfuUzbss5nWKy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GgpXfuUzbss5nWKy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GgpXfuUzbss5nWKy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GgpXfuUzbss5nWKy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GgpXfuUzbss5nWKy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GgpXfuUzbss5nWKy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GgpXfuUzbss5nWKy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GgpXfuUzbss5nWKy .marker.cross{stroke:#333333;}#mermaid-svg-GgpXfuUzbss5nWKy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GgpXfuUzbss5nWKy p{margin:0;}#mermaid-svg-GgpXfuUzbss5nWKy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GgpXfuUzbss5nWKy .cluster-label text{fill:#333;}#mermaid-svg-GgpXfuUzbss5nWKy .cluster-label span{color:#333;}#mermaid-svg-GgpXfuUzbss5nWKy .cluster-label span p{background-color:transparent;}#mermaid-svg-GgpXfuUzbss5nWKy .label text,#mermaid-svg-GgpXfuUzbss5nWKy span{fill:#333;color:#333;}#mermaid-svg-GgpXfuUzbss5nWKy .node rect,#mermaid-svg-GgpXfuUzbss5nWKy .node circle,#mermaid-svg-GgpXfuUzbss5nWKy .node ellipse,#mermaid-svg-GgpXfuUzbss5nWKy .node polygon,#mermaid-svg-GgpXfuUzbss5nWKy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GgpXfuUzbss5nWKy .rough-node .label text,#mermaid-svg-GgpXfuUzbss5nWKy .node .label text,#mermaid-svg-GgpXfuUzbss5nWKy .image-shape .label,#mermaid-svg-GgpXfuUzbss5nWKy .icon-shape .label{text-anchor:middle;}#mermaid-svg-GgpXfuUzbss5nWKy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GgpXfuUzbss5nWKy .rough-node .label,#mermaid-svg-GgpXfuUzbss5nWKy .node .label,#mermaid-svg-GgpXfuUzbss5nWKy .image-shape .label,#mermaid-svg-GgpXfuUzbss5nWKy .icon-shape .label{text-align:center;}#mermaid-svg-GgpXfuUzbss5nWKy .node.clickable{cursor:pointer;}#mermaid-svg-GgpXfuUzbss5nWKy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GgpXfuUzbss5nWKy .arrowheadPath{fill:#333333;}#mermaid-svg-GgpXfuUzbss5nWKy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GgpXfuUzbss5nWKy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GgpXfuUzbss5nWKy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GgpXfuUzbss5nWKy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GgpXfuUzbss5nWKy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GgpXfuUzbss5nWKy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GgpXfuUzbss5nWKy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GgpXfuUzbss5nWKy .cluster text{fill:#333;}#mermaid-svg-GgpXfuUzbss5nWKy .cluster span{color:#333;}#mermaid-svg-GgpXfuUzbss5nWKy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GgpXfuUzbss5nWKy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GgpXfuUzbss5nWKy rect.text{fill:none;stroke-width:0;}#mermaid-svg-GgpXfuUzbss5nWKy .icon-shape,#mermaid-svg-GgpXfuUzbss5nWKy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GgpXfuUzbss5nWKy .icon-shape p,#mermaid-svg-GgpXfuUzbss5nWKy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GgpXfuUzbss5nWKy .icon-shape .label rect,#mermaid-svg-GgpXfuUzbss5nWKy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GgpXfuUzbss5nWKy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GgpXfuUzbss5nWKy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GgpXfuUzbss5nWKy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 无状态
读写
读取
读取
用户服务
Redis
订单服务
商品服务
有状态
存储
用户服务
Session 本地
订单服务
商品服务
经过改造,用户服务变成无状态,可以随时水平伸缩,宕机一台实例不会导致登录态丢失。
流程型状态:事件驱动替代直接调用
流程型状态伴随业务流程的推进而变化,如订单的"待支付→支付成功→待发货→已发货→已签收"。在朴素实现中,支付服务在收到支付回调后,直接通过 RPC 通知订单服务修改状态,再让订单服务调用物流服务开始发货。这导致支付服务依赖订单服务,订单服务又依赖物流服务。
通过引入消息队列解耦:
Logistics Order MQ Payment User Logistics Order MQ Payment User #mermaid-svg-bQF0rJ33dNvZmqEP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bQF0rJ33dNvZmqEP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bQF0rJ33dNvZmqEP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bQF0rJ33dNvZmqEP .error-icon{fill:#552222;}#mermaid-svg-bQF0rJ33dNvZmqEP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bQF0rJ33dNvZmqEP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bQF0rJ33dNvZmqEP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bQF0rJ33dNvZmqEP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bQF0rJ33dNvZmqEP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bQF0rJ33dNvZmqEP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bQF0rJ33dNvZmqEP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bQF0rJ33dNvZmqEP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bQF0rJ33dNvZmqEP .marker.cross{stroke:#333333;}#mermaid-svg-bQF0rJ33dNvZmqEP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bQF0rJ33dNvZmqEP p{margin:0;}#mermaid-svg-bQF0rJ33dNvZmqEP .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bQF0rJ33dNvZmqEP text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-bQF0rJ33dNvZmqEP .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-bQF0rJ33dNvZmqEP .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-bQF0rJ33dNvZmqEP .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-bQF0rJ33dNvZmqEP .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-bQF0rJ33dNvZmqEP #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-bQF0rJ33dNvZmqEP .sequenceNumber{fill:white;}#mermaid-svg-bQF0rJ33dNvZmqEP #sequencenumber{fill:#333;}#mermaid-svg-bQF0rJ33dNvZmqEP #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-bQF0rJ33dNvZmqEP .messageText{fill:#333;stroke:none;}#mermaid-svg-bQF0rJ33dNvZmqEP .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bQF0rJ33dNvZmqEP .labelText,#mermaid-svg-bQF0rJ33dNvZmqEP .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-bQF0rJ33dNvZmqEP .loopText,#mermaid-svg-bQF0rJ33dNvZmqEP .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-bQF0rJ33dNvZmqEP .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-bQF0rJ33dNvZmqEP .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-bQF0rJ33dNvZmqEP .noteText,#mermaid-svg-bQF0rJ33dNvZmqEP .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-bQF0rJ33dNvZmqEP .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bQF0rJ33dNvZmqEP .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bQF0rJ33dNvZmqEP .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bQF0rJ33dNvZmqEP .actorPopupMenu{position:absolute;}#mermaid-svg-bQF0rJ33dNvZmqEP .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-bQF0rJ33dNvZmqEP .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bQF0rJ33dNvZmqEP .actor-man circle,#mermaid-svg-bQF0rJ33dNvZmqEP line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-bQF0rJ33dNvZmqEP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Payment 不再直接调用 Order 完成支付发布支付成功事件消费事件,修改订单状态发布订单待发货事件消费事件,创建运单
支付服务只需将事件投递到 MQ,不再关心谁去消费。订单服务与物流服务之间也通过事件驱动,极大降低耦合。 当然,完全无状态是一种理想,实践中可以根据业务容忍度在局部保留适当状态,但在微服务之间应尽量保持无状态。
Go‑Zero 实践示例
下面以简化的电商系统为例,展示如何用 Go‑Zero 落地上述思想。
基于领域拆分的项目结构
dir
monorepo/
├── api/ # 对外 API 定义(.api 文件)
│ └── gateway.api
├── backend/ # 内部 RPC 服务
│ ├── user/
│ │ ├── user.proto
│ │ └── ...
│ ├── order/
│ └── product/
├── common/ # 公共库、中间件
├── go.mod
└── go.sum
通过 goctl 工具,可以由 .api 和 .proto 文件生成完整的服务代码框架。
定义 RPC 服务与 API 网关
用户服务 proto 定义 backend/user/user.proto:
protobuf
syntax = "proto3";
package user;
service User {
rpc Login(LoginRequest) returns (LoginResponse);
rpc VerifyToken(VerifyRequest) returns (VerifyResponse);
}
message LoginRequest {
string username = 1;
string password = 2;
}
message LoginResponse {
string token = 1;
}
message VerifyRequest {
string token = 1;
}
message VerifyResponse {
bool is_valid = 1;
string user_id = 2;
}
网关 API 定义 api/gateway.api:
go
type (
LoginReq {
Username string `json:"username"`
Password string `json:"password"`
}
LoginResp {
Token string `json:"token"`
}
OrderListReq {
UserId string `json:"userId"`
}
)
service gateway-api {
@handler Login
post /api/user/login(LoginReq) returns (LoginResp)
@handler OrderList
get /api/order/list(OrderListReq) returns (OrderListResp) // 省略
}
网关内部会生成 RPC 客户端调用用户服务 VerifyToken 以验证请求是否合法。
引入消息队列解耦订单与物流
在订单服务完成支付状态更新后,发布事件到消息队列。Go‑Zero 内置对 go-queue 组件的支持,可快速接入 Kafka、RabbitMQ 等。
订单服务发布事件:
go
// 在订单支付成功逻辑中
func (l *PayOrderLogic) notifyOrderPaid(orderId string) error {
event := &OrderPaidEvent{
OrderId: orderId,
PaidAt: time.Now().Unix(),
}
payload, _ := json.Marshal(event)
return l.svcCtx.OrderPublish.Publish(payload)
}
物流服务消费事件:
go
func (l *ConsumeOrderEvent) Consume(ctx context.Context, key, value string) error {
var event OrderPaidEvent
if err := json.Unmarshal([]byte(value), &event); err != nil {
return err
}
// 创建发货单逻辑
return l.svcCtx.Logistic.CreateShipment(ctx, event.OrderId)
}
这样,订单服务不再显式调用物流服务,两者通过 OrderPaid 事件松散耦合。
实现无状态的用户认证
用户登录成功后,服务生成 JWT Token 或自定义 Token,并存入 Redis(分布式缓存),不保存在服务实例内存中。其他服务直接从 Redis 验证 Token。
go
// 登录后存储 token 到 Redis
func (l *LoginLogic) saveToken(userId, token string) error {
expire := time.Hour * 24 * 7
return l.svcCtx.RedisClient.Setex(token, userId, int(expire/time.Second))
}
// 验证中间件或拦截器
func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
userId, err := m.redis.Get(token)
if err != nil || userId == "" {
http.Error(w, "unauthorized", 401)
return
}
next(w, r.WithContext(context.WithValue(r.Context(), "userId", userId)))
}
}
每一个服务实例在验证请求时都依赖 Redis,自身不保存任何登录态,从而实现用户服务的完全无状态化。
结语
微服务体系并非一堆服务的简单堆积,而是围绕合理拆分、服务化、通信机制、无状态设计等核心要素的系统性工程。结合 Go‑Zero 框架提供的工具与最佳实践,我们可以较为平滑地构建出符合这些原则的系统。
- 拆分时综合考量团队、业务与流量,多采用模块、功能、读写等实用维度;
- 确认拆分出的单元已成为独立部署的服务,而非组件或子系统;
- 根据场景搭配 RPC、API 网关和消息队列,避免单点瓶颈和过度耦合;
- 将状态外移到分布式缓存,用事件驱动替代长链路 RPC 调用,让服务可以大胆伸缩。
希望本文能为你理清微服务落地的关键脉络。欢迎在实际项目中继续探索 Go‑Zero 带来的生产力提升。