微服务架构 Microservices Architecture
概念
微服务架构将单体应用拆分为一组小型、独立部署的服务,每个服务围绕特定业务能力构建,拥有独立的进程和数据存储,通过轻量级通信机制(HTTP / gRPC / 消息队列)相互协作。
单体架构的演进困境
┌─────────────────────────────────────────┐
│ 单体应用 (Monolith) │
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │用户模块 │ │订单模块 │ │支付模块 │ │
│ └───┬────┘ └───┬────┘ └───┬────┘ │
│ │ │ │ │
│ ┌───┴──────────┴──────────┴───┐ │
│ │ 共享数据库 │ │
│ └─────────────────────────────┘ │
│ │
│ 打包为一个 WAR/JAR → 部署到一台服务器 │
└─────────────────────────────────────────┘当团队和业务规模增长后,单体架构会遇到以下问题:
- 部署耦合: 修改一行代码需要重新部署整个应用
- 技术栈绑定: 所有模块必须使用相同的语言和框架
- 扩展受限: 无法对某个模块单独扩容,只能整体扩容
- 协作冲突: 多人开发同一代码库,分支合并冲突频繁
核心原理
1. 三大核心原则
| 原则 | 含义 |
|---|---|
| 单一职责 | 每个服务只负责一个明确的业务能力(用户、订单、支付各自独立) |
| 去中心化 | 各团队自主选技术栈;每个服务拥有独立数据库,不共享底层存储 |
| 独立部署 | 每个服务可独立开发、测试、部署、扩缩容,无需协调其他服务 |
2. 服务拆分原则
DDD 限界上下文映射
领域驱动设计(DDD)是指导服务拆分的核心方法论:以**限界上下文(Bounded Context)**为边界,每个限界上下文对应一个微服务。
电商系统限界上下文划分:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 用户上下文 │ │ 订单上下文 │ │ 支付上下文 │
│ │ │ │ │ │
│ User │ │ Order │ │ Payment │
│ Profile │ │ OrderItem │ │ Transaction │
│ Address │ │ Discount │ │ Refund │
└──────────────┘ └──────────────┘ └──────────────┘
↕ 通过 API 交互,不共享数据库
┌──────────────┐ ┌──────────────┐
│ 库存上下文 │ │ 物流上下文 │
│ │ │ │
│ Product │ │ Shipment │
│ SKU │ │ Carrier │
│ Stock │ │ Tracking │
└──────────────┘ └──────────────┘拆分信号(何时应该拆分)
- 团队规模增长: 单个团队超过 8-10 人,沟通协调成本急剧上升
- 独立发布需求: 某个模块需要高频独立上线,但受其他模块耦合阻碍
- 差异化扩容: 某个功能(如搜索、推荐)需要独立扩容,其他模块不需要
- 技术异构需求: 特定功能需要用不同语言或框架实现
常见反模式
- 按技术层拆分(错误): 把 Controller 层、Service 层、DAO 层各自拆成一个服务——这只是分布式单体,并非微服务
- 拆分粒度过细: 每个方法一个服务,导致服务间调用比本地调用还多,网络开销和分布式事务成本远高于收益
迁移路径:绞杀者模式(Strangler Fig Pattern)
阶段一: 阶段二: 阶段三:
单体应用 引入代理层 逐步迁移
┌──────────┐ ┌──────────┐ ┌──────────┐
│ │ │ Proxy │ │ Proxy │
│ Monolith│ │ /users → │──新服务→ │ /users → │──用户服务
│ - 用户 │ → │ /orders→ │──原单体→ │ /orders→ │──订单服务
│ - 订单 │ │ /pay → │──原单体→ │ /pay → │──支付服务
│ - 支付 │ └──────────┘ └──────────┘
└──────────┘ 单体仍然运行 单体逐渐缩小直至废弃核心思路: 新功能全部在新服务中开发,用代理层(API 网关)路由请求,逐步将旧功能从单体迁移到新服务,单体像被绞杀的植物一样逐渐萎缩。
3. 服务通信
通信方式对比
| 特性 | REST (HTTP/JSON) | gRPC (HTTP/2 + Protobuf) | 消息队列 |
|---|---|---|---|
| 通信方式 | 同步请求/响应 | 同步请求/响应 | 异步消息 |
| 序列化 | JSON(文本) | Protobuf(二进制) | 自定义 |
| 性能 | 一般 | 高(二进制传输、多路复用) | 取决于 MQ |
| 跨语言 | 好(HTTP 标准) | 好(.proto 生成多语言代码) | 好 |
| 流式传输 | 不原生支持 | 支持(双向流、服务端流、客户端流) | 天然支持 |
| 浏览器兼容 | 好 | 需要 gRPC-Web 代理 | 不直接支持 |
| 适用场景 | 对外 API、前后端通信 | 服务间高性能内部通信 | 异步解耦、事件驱动 |
选型建议:
- 对外 API / 前端调用 → REST
- 内部服务间高频调用 → gRPC
- 异步、事件驱动的场景 → 消息队列
REST 示例
// Spring Boot REST Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
UserDTO user = userService.findById(id);
return ResponseEntity.ok(user);
}
}
// 其他服务调用(RestTemplate / WebClient)
UserDTO user = restTemplate.getForObject(
"http://user-service/api/users/{id}", UserDTO.class, userId);gRPC 示例
// user.proto
service UserService {
rpc GetUser(GetUserRequest) returns (UserResponse);
rpc ListUsers(ListUsersRequest) returns (stream UserResponse); // 服务端流
}
message GetUserRequest {
int64 id = 1;
}
message UserResponse {
int64 id = 1;
string name = 2;
string email = 3;
}// gRPC 客户端调用
UserResponse user = userServiceStub.getUser(
GetUserRequest.newBuilder().setId(userId).build()
);4. 服务发现
微服务实例动态变化(扩缩容、故障重启),调用方需要知道目标服务的实际地址。
客户端发现 (Client-side Discovery)
┌──────────────┐
│ 服务注册中心 │
│ (Registry) │
└──────┬───────┘
▲ │
1.注册 │ │ 2.查询服务实例列表
│ ▼
┌──────────┐ ┌──────────┐
│ 服务实例 │ │ 调用方 │
│ A-1, A-2 │◄─────│ (Client) │ 3. 客户端负载均衡,直接调用
└──────────┘ └──────────┘代表:Eureka + Ribbon、Nacos + Spring Cloud LoadBalancer
服务端发现 (Server-side Discovery)
┌──────────────┐
│ 服务注册中心 │
│ (Registry) │
└──────┬───────┘
│ 2.查询实例
▼
┌──────────┐ ┌──────────────┐ ┌──────────┐
│ 调用方 │─────►│ 负载均衡器 │─────►│ 服务实例 │
│ (Client) │ │ (LB/Gateway) │ │ A-1, A-2 │
└──────────┘ └──────────────┘ └──────────┘
1.请求 3.转发请求代表:Kubernetes Service、Consul + Envoy
主流注册中心对比
| 特性 | Eureka | Nacos | Consul |
|---|---|---|---|
| 一致性协议 | AP(最终一致) | CP/AP 可切换 | CP(Raft) |
| 健康检查 | 客户端心跳 | 客户端心跳 + 服务端探测 | 多种(HTTP/TCP/gRPC/脚本) |
| 配置管理 | 不支持 | 支持(内置配置中心) | 支持(KV 存储) |
| 生态 | Spring Cloud Netflix | Spring Cloud Alibaba | 跨语言,K8s 友好 |
| 维护状态 | Netflix 停止维护 | 阿里巴巴活跃维护 | HashiCorp 活跃维护 |
5. API 网关
API 网关是微服务架构的统一入口,承担请求路由、身份认证、限流熔断等横切关注点。
┌─────────────────┐
│ API Gateway │
│ │
客户端 ──── HTTPS ────►│ 路由 (Routing) │──► 用户服务
│ 认证 (Auth) │──► 订单服务
│ 限流 (Rate Limit) │──► 支付服务
│ 日志 (Logging) │──► 商品服务
│ 监控 (Metrics) │
└─────────────────┘| 功能 | 说明 |
|---|---|
| 路由转发 | 根据 URL 路径将请求路由到对应的后端服务 |
| 身份认证 | 统一鉴权(JWT 验签、OAuth 2.0),后端服务无需重复鉴权 |
| 限流熔断 | 在网关层对请求进行限流和熔断,保护后端服务 |
| 协议转换 | 对外 REST,对内 gRPC;或 HTTP → WebSocket |
| 请求聚合 | 将多个后端服务的响应聚合为一个响应返回给客户端 |
| 灰度发布 | 按用户、流量比例路由到不同版本的服务 |
BFF 模式 (Backend For Frontend)
针对不同客户端(Web、App、小程序)的差异化需求,为每种客户端提供专属的 API 层:
┌───────┐ ┌───────┐ ┌───────┐
│ Web │ │ App │ │ 小程序 │
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│BFF-Web│ │BFF-App│ │BFF-Mini│ ← 各 BFF 按客户端需求裁剪数据
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
└──────────┼──────────┘
▼
微服务集群优势: 各端的接口格式和数据粒度可以独立优化,避免通用 API 的"一刀切"问题。
6. 分布式事务
微服务架构中,一个业务操作可能跨多个服务和数据库,传统的单机事务(ACID)无法直接适用。
2PC(两阶段提交)
┌──────────────┐
│ 事务协调者 (TC) │
└──────┬───────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 参与者 A │ │ 参与者 B │ │ 参与者 C │
└─────────┘ └─────────┘ └─────────┘
阶段一 (Prepare):TC → 所有参与者: "准备好了吗?" 参与者执行事务但不提交
阶段二 (Commit/Rollback):全部 YES → Commit;任一 NO → Rollback缺点: 同步阻塞、单点故障(协调者宕机)、数据不一致风险。
TCC(Try-Confirm-Cancel)
Try(预留资源) Confirm(确认提交) Cancel(回滚释放)
账户 A: 冻结 100 元 → 扣减 100 元 / 解冻 100 元
账户 B: 预增 100 元 → 确认增加 100 元 / 取消预增// TCC 接口定义
public interface AccountTccService {
// Try: 冻结金额
@TwoPhaseBusinessAction(name = "deduct",
commitMethod = "confirm", rollbackMethod = "cancel")
boolean tryDeduct(BusinessActionContext ctx,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
// Confirm: 扣减冻结金额
boolean confirm(BusinessActionContext ctx);
// Cancel: 解冻金额
boolean cancel(BusinessActionContext ctx);
}优点: 性能好(无全局锁),灵活性高。缺点: 业务侵入性强,每个服务需要实现三个接口。
Saga 模式
正向流程: T1(创建订单) → T2(扣减库存) → T3(扣减余额) → 成功
补偿流程(T3 失败时):C2(恢复库存) → C1(取消订单)- 编排式(Choreography): 每个服务监听事件并触发下一步,无中心协调者
- 协调式(Orchestration): 由一个 Saga 协调者统一编排事务流程
分布式事务方案对比
| 方案 | 一致性 | 性能 | 业务侵入性 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强一致 | 低(同步阻塞) | 低 | 数据库层面的分布式事务 |
| TCC | 最终一致 | 高 | 高(需实现三个接口) | 金融、支付等对一致性要求高的场景 |
| Saga | 最终一致 | 高 | 中(需实现补偿操作) | 长事务、跨多个服务的业务流程 |
| 本地消息表 | 最终一致 | 高 | 中 | 异步可靠消息,如通知、积分 |
7. 可观测性三大支柱
微服务将单体拆散成几十甚至上百个服务后,"程序出问题了,但不知道问题在哪个服务"是最常见的运维痛点。可观测性(Observability)通过三大支柱解决这个问题。
可观测性三大支柱
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 分布式追踪 │ │ 指标 │ │ 日志 │
│ (Tracing) │ │ (Metrics) │ │ (Logging) │
│ │ │ │ │ │
│ "请求经过了 │ │ "系统现在运行 │ │ "出错时发生了 │
│ 哪些服务?" │ │ 得怎么样?" │ │ 什么事?" │
│ │ │ │ │ │
│ Jaeger │ │ Prometheus │ │ Elasticsearch│
│ Zipkin │ │ Grafana │ │ Logstash │
│ SkyWalking │ │ RED/USE 方法 │ │ Kibana (ELK) │
└──────────────┘ └──────────────┘ └──────────────┘
统一标准:OpenTelemetry(OTEL)分布式追踪(Distributed Tracing)
核心概念:
- TraceID: 一次完整请求的唯一标识,贯穿所有服务
- SpanID: 单个服务处理片段的标识;多个 Span 组成一棵调用树(Trace)
- OpenTelemetry(OTEL): CNCF 主导的追踪标准,统一 API / SDK / 数据格式,避免厂商锁定
用户请求(TraceID: abc123)
│
├── [Span 1] API 网关 0ms - 120ms
│ ├── [Span 2] 用户服务 5ms - 20ms
│ └── [Span 3] 订单服务 25ms - 110ms
│ ├── [Span 4] 库存服务 30ms - 60ms
│ └── [Span 5] 支付服务 65ms - 105ms ← 瓶颈在这里主流工具对比:
| 工具 | 特点 | 适用场景 |
|---|---|---|
| Jaeger | CNCF 毕业项目,原生 OTEL 支持,部署轻量 | 云原生、K8s 环境 |
| Zipkin | Twitter 开源,社区成熟,资源占用小 | 中小规模,快速上手 |
| SkyWalking | Apache 项目,Java 生态友好,支持多语言 Agent | 国内 Java 微服务团队 |
指标监控(Metrics)
Prometheus + Grafana 是事实上的标准组合:
微服务实例 → Prometheus(拉取/推送指标)→ 存储时序数据
→ Grafana(可视化仪表盘)
→ AlertManager(告警)RED 方法(针对面向请求的服务):
| 指标 | 含义 | 示例 |
|---|---|---|
| Rate(速率) | 每秒处理请求数(QPS/RPS) | http_requests_total |
| Errors(错误率) | 失败请求占比 | http_requests_total{status="5xx"} |
| Duration(延迟) | 请求响应时间(P50/P95/P99) | http_request_duration_seconds |
USE 方法(针对基础设施资源):
| 指标 | 含义 |
|---|---|
| Utilization(使用率) | CPU 使用率、内存占用 |
| Saturation(饱和度) | 队列积压、线程池等待数 |
| Errors(错误数) | 磁盘 I/O 错误、网络丢包 |
关键业务指标:
- 接口 P99 延迟(慢请求感知)
- 服务错误率(5xx 比例)
- 数据库连接池使用率
- 消息队列积压数(消费延迟)
- JVM GC 停顿时间(Java 服务)
日志(Logging)
ELK / EFK 栈:
微服务日志输出
│
▼
Filebeat / Fluentd(采集)
│
▼
Logstash(过滤、解析、转换)
│
▼
Elasticsearch(存储、索引)
│
▼
Kibana(查询、可视化)结构化日志(Structured Logging)最佳实践:
// 不推荐:纯文本日志,难以检索
logger.info("用户 123 下单失败,原因:库存不足");
// 推荐:结构化 JSON 日志,包含 TraceID 关联
logger.info("order.create.failed",
Map.of(
"traceId", MDC.get("traceId"), // 与 Span 关联
"userId", 123,
"reason", "INSUFFICIENT_STOCK",
"productId", 456
)
);
// 输出: {"timestamp":"...","level":"INFO","event":"order.create.failed",
// "traceId":"abc123","userId":123,"reason":"INSUFFICIENT_STOCK","productId":456}关键实践:
- 日志中必须包含 TraceID / SpanID,实现日志与追踪的关联(Correlation)
- 使用结构化 JSON 格式,便于 Elasticsearch 建索引和查询
- 日志级别规范:ERROR(需告警)/ WARN(潜在问题)/ INFO(关键业务事件)/ DEBUG(本地调试)
8. 容器与编排
微服务的价值只有在能够快速、可靠地部署时才能真正发挥出来,容器化和 Kubernetes 编排是实现这一目标的基础设施。
Docker 基础
| 概念 | 说明 |
|---|---|
| 镜像(Image) | 只读的应用打包模板,包含代码、运行时、依赖(通过 Dockerfile 构建) |
| 容器(Container) | 镜像运行时的实例,相互隔离,可启停 |
| 网络模式 | bridge(默认,容器间互通)/ host(共享宿主机网络)/ none(无网络)/ overlay(跨主机) |
# 典型 Java 微服务 Dockerfile
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/user-service.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]Kubernetes 核心概念
K8s 集群结构
┌────────────────────────────────────────────────────┐
│ Namespace │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Deployment │ │ Service │ │
│ │ ┌────────┐ │ │ │ │
│ │ │ Pod │ ReplicaSet│ │ ClusterIP / NodePort│ │
│ │ │ [容器] │ │ │ / LoadBalancer │ │
│ │ └────────┘ │ │ │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ │
│ │ Ingress │ ← 集群外部流量入口 │
│ │ 路由规则(域名/路径) │ │
│ └──────────────────────┘ │
└────────────────────────────────────────────────────┘| 资源 | 作用 |
|---|---|
| Pod | K8s 最小调度单元,包含一个或多个容器,共享网络和存储 |
| Deployment | 管理 Pod 副本数、滚动升级、回滚 |
| Service | 为一组 Pod 提供稳定的虚拟 IP 和 DNS 名称(负载均衡) |
| Ingress | 集群外部流量入口,基于域名/路径路由到不同 Service |
| ConfigMap / Secret | 外部化配置与敏感信息 |
| HPA | Horizontal Pod Autoscaler,根据 CPU/内存/自定义指标自动扩缩容 |
Pod 生命周期与健康检查
Pod 生命周期:
Pending → Running → Succeeded / Failed / Unknown
三种探针:
┌────────────────┬──────────────────────────────────────┐
│ Liveness Probe │ 检测容器是否存活;失败则重启容器 │
│ │ 用于:死锁、OOM 等导致服务无响应的场景 │
├────────────────┼──────────────────────────────────────┤
│ Readiness Probe│ 检测容器是否就绪接收流量;失败则从 Service │
│ │ 的 Endpoints 中移除,不再转发请求 │
├────────────────┼──────────────────────────────────────┤
│ Startup Probe │ 容器启动阶段专用;避免慢启动应用被 │
│ │ Liveness Probe 提前杀掉 │
└────────────────┴──────────────────────────────────────┘# 健康检查示例
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 59. Service Mesh(服务网格)
Service Mesh 将服务间通信的治理逻辑从业务代码中剥离,下沉到基础设施层(Sidecar 代理)。
┌──────────────────────────────────────────────┐
│ Pod A │
│ ┌──────────┐ ┌─────────────────────────┐ │
│ │ 业务服务 A │────│ Sidecar Proxy (Envoy) │ │
│ └──────────┘ └────────────┬────────────┘ │
└───────────────────────────────┼───────────────┘
│ mTLS / 负载均衡 / 限流
│
┌───────────────────────────────┼───────────────┐
│ Pod B │ │
│ ┌──────────┐ ┌────────────┴────────────┐ │
│ │ 业务服务 B │────│ Sidecar Proxy (Envoy) │ │
│ └──────────┘ └─────────────────────────┘ │
└──────────────────────────────────────────────┘
控制平面 (Istio Control Plane):
┌────────┐ ┌────────┐ ┌────────┐
│ Istiod │ │ 配置下发 │ │ 证书管理 │
└────────┘ └────────┘ └────────┘核心能力:
- 流量管理: 负载均衡、流量分割(灰度发布)、故障注入
- 安全: mTLS 加密通信、访问控制策略
- 可观测性: 分布式追踪、指标采集、日志采集
优势: 业务代码零侵入,通信治理逻辑统一管理。 代价: 增加运维复杂度和资源开销(每个 Pod 多一个 Sidecar 容器)。
10. CAP 定理
分布式系统中,以下三个属性最多只能同时满足两个:
| 属性 | 说明 |
|---|---|
| C(Consistency,一致性) | 所有节点在同一时刻看到相同的数据 |
| A(Availability,可用性) | 每个请求都能在合理时间内获得非错误的响应 |
| P(Partition Tolerance,分区容错) | 网络分区(节点间通信中断)时系统仍能继续工作 |
在分布式系统中,网络分区(P)是不可避免的,因此实际选择是 CP 还是 AP:
| 类型 | 说明 | 代表系统 |
|---|---|---|
| CP | 网络分区时保证一致性,可能拒绝服务 | ZooKeeper、etcd、HBase、Redis Cluster |
| AP | 网络分区时保证可用性,数据可能暂时不一致 | Eureka、Cassandra、DynamoDB、DNS |
实际工程中的权衡:
- 金融场景(转账、扣款)→ 倾向 CP,数据一致性优先
- 电商场景(商品信息、用户画像)→ 倾向 AP,可用性优先,接受最终一致
- 大多数系统采用 BASE 理论(基本可用 + 软状态 + 最终一致),在 CAP 之间做平衡
技术选型与对比
单体 vs 微服务
| 维度 | 单体架构 | 微服务架构 |
|---|---|---|
| 部署方式 | 整体打包部署 | 各服务独立部署 |
| 技术栈 | 统一 | 可异构(Java / Go / Python 混用) |
| 扩展性 | 整体扩容 | 按需对单个服务扩容 |
| 故障隔离 | 一个模块故障影响全局 | 单个服务故障不影响其他服务 |
| 开发效率 | 初期快,后期急剧下降 | 初期慢(基础设施建设),后期高效 |
| 运维复杂度 | 低 | 高(需要容器化、编排、监控等基础设施) |
| 数据管理 | 单一数据库 | 每个服务独立数据库(Database per Service) |
| 适用团队 | 小团队(< 10 人) | 中大型团队(多个独立小组) |
微服务不是银弹。小型项目盲目引入微服务,会引入过多的分布式系统复杂度,得不偿失。
服务网格选型
| 工具 | 特点 | 适用场景 |
|---|---|---|
| Istio + Envoy | 功能最全,社区最活跃 | 大规模 K8s 生产环境 |
| Linkerd | 轻量,资源占用低,Rust 实现 | 资源敏感场景 |
| Consul Connect | 与 Consul 服务发现深度集成 | 已用 Consul 的团队 |
实战案例:单体迁移微服务
以一个电商单体系统为例,演示分阶段迁移路径。
阶段一:识别限界上下文
分析单体内部模块依赖:
用户模块 ──────────── 订单模块
│ │
│ ├── 库存模块
│ │
└── 支付模块 ─────────┘
识别高内聚、低耦合的业务边界 → 确定拆分优先级:
优先拆分:库存服务(独立扩容需求 + 边界清晰)阶段二:提取第一个服务
步骤:
1. 新建独立代码仓库 inventory-service
2. 单体中的库存模块代码迁移到新服务
3. 新服务有自己的数据库(inventory_db)
4. 单体通过 HTTP 调用新服务,不再直接访问原库存表
5. 双写 + 数据迁移,灰度切流验证
单体(仍运行)
│
├── 用户模块
├── 订单模块 ──── HTTP ────► 库存服务(新)
└── 支付模块阶段三:引入 API 网关与服务发现
外部流量 → API 网关(Kong / Spring Cloud Gateway)
│
├──► 库存服务(注册到 Nacos)
└──► 单体(仍处理其他请求)
单体内部服务调用开始通过注册中心发现服务地址阶段四:逐步提取剩余服务
优先级排序(按业务价值 + 拆分难度):
1. 库存服务 ✓ 已完成
2. 用户服务 → 拆分(边界清晰,无复杂依赖)
3. 支付服务 → 拆分(边界清晰,但需要严格的分布式事务)
4. 订单服务 → 最后拆分(依赖最多,最复杂)
最终形态:
API 网关 → [用户服务][订单服务][库存服务][支付服务]
各服务独立数据库,通过消息队列进行异步解耦关键经验:
- 每次只拆一个服务,验证稳定后再拆下一个
- 数据迁移期间保持双写,用对比监控发现不一致
- 不要大爆炸式重写,绞杀者模式是最低风险的迁移方式
面试常问 & 怎么答
Q1: 分布式事务方案对比
2PC(两阶段提交):
- 阶段一(Prepare):协调者询问所有参与者是否可以提交,参与者执行事务但不提交
- 阶段二(Commit):全部同意则提交,任一拒绝则回滚
- 缺点:同步阻塞,性能差;协调者单点故障;阶段二可能出现部分提交导致数据不一致
TCC(Try-Confirm-Cancel):
- Try:预留资源(如冻结余额);Confirm:确认提交;Cancel:回滚释放
- 优点:性能好,适合金融场景。缺点:业务侵入性强,每个服务需实现三个接口
Saga:
- 将长事务拆为多个本地事务,每个本地事务有对应的补偿操作
- 适合跨多个服务的长流程,业务侵入性适中
面试回答要点: 大多数场景不需要强一致,优先选择 Saga(通用性好)或 TCC(金融场景),避免使用 2PC(性能差且有单点故障风险)。
Q2: CAP 定理与 CP/AP 区别
CAP 定理指出: 分布式系统不可能同时满足一致性(C)、可用性(A)、分区容错性(P)。由于网络分区不可避免,实际选择是 CP 还是 AP。
CP 系统示例 -- ZooKeeper:
- 当 Leader 节点与 Follower 发生网络分区时,少数派节点拒绝提供服务(牺牲可用性)
- 适用于:分布式锁、配置中心、Leader 选举
AP 系统示例 -- Eureka:
- 当节点间网络分区时,每个节点仍然接受注册和查询(保证可用性)
- 各节点间的数据可能暂时不一致(牺牲一致性)
实际工程中 大多数系统采用 BASE 理论(基本可用 + 软状态 + 最终一致),在一致性和可用性之间做程度折中。
Q3: 微服务通信方式选择
| 方式 | 适用场景 | 不适用场景 |
|---|---|---|
| REST | 对外 API、前后端通信、跨组织集成 | 内部高频调用(性能不够) |
| gRPC | 内部服务间高频同步调用(性能高 5-10x) | 需要浏览器直接访问的 API |
| 消息队列 | 异步处理、事件驱动、削峰填谷 | 需要实时响应的场景 |
回答要点: 实际项目通常混合使用——对外 REST、内部 gRPC、异步事件用消息队列。
Q4: 微服务的优缺点?什么时候不应该用微服务?
优点:
- 独立部署:各服务可以独立上线,发布周期互不阻塞
- 独立扩容:高负载服务单独扩容,成本可控
- 技术异构:各团队可以选择最适合的语言和框架
- 故障隔离:单个服务崩溃不会拖垮整个系统
缺点:
- 分布式系统复杂度:网络延迟、部分失败、数据一致性等问题无法回避
- 运维成本高:需要容器化、编排平台、服务发现、分布式追踪等完整基础设施
- 开发复杂度上升:本地联调需要启多个服务,分布式事务比单机事务难一个量级
什么时候不应该用微服务:
- 团队规模小(< 10 人): 构建和维护微服务基础设施的成本大于收益,单体更合适
- 业务边界不清晰: 强行拆分导致频繁跨服务调用和分布式事务,得不偿失
- 产品早期(PMF 前): 需求变化频繁,微服务的服务边界会不断重划,维护成本极高
- 团队缺乏分布式系统经验: 没有做好基础设施建设就引入微服务,会导致线上问题频发
面试回答模板: "微服务解决的是规模化的组织和技术问题,前提是团队规模足够大、业务边界足够清晰、基础设施足够成熟。对于小团队或早期产品,单体架构加合理的模块化设计往往是更好的选择。"
Q5: 如何做服务拆分?拆分粒度怎么把握?
拆分方法:
以 DDD 限界上下文为边界: 找到业务中天然存在的"语言边界"——同一个词在不同上下文有不同含义,就说明存在边界。例如"订单"在订单域和支付域的含义和字段不同。
观察团队协作摩擦: 频繁等待其他团队合并代码、频繁协调发布计划,说明应该在这里划分边界,让各团队独立拥有代码库。
识别差异化扩容需求: 搜索服务、推荐服务计算密集,需要单独扩容;用户服务 QPS 稳定,不需要频繁扩缩容——这是拆分的信号。
粒度把握原则:
- 不要按技术层拆分(Controller 服务 / Service 服务 / DAO 服务 = 错误)
- 服务应围绕业务能力,而非功能(用户服务、订单服务,而非读服务、写服务)
- 拆分后,跨服务调用应远少于服务内调用;如果拆后跨服务调用反而增多,说明拆错了
- 两个团队 = 两个服务的上限;一个团队维护太多服务也会失控
面试回答模板: "拆分没有绝对标准,核心是'高内聚、低耦合'。我会从 DDD 限界上下文入手识别业务边界,结合团队结构(Conway's Law:系统架构趋向于组织架构)、独立发布需求和扩容差异来决定拆分边界。粒度的判断标准是:拆分后,服务内调用远大于跨服务调用;每个服务有独立的业务语义,可以用一句话描述它的职责。"
看到什么就先想到这类
| 看到 / 问到 | 第一反应 |
|---|---|
| 单体应用变慢 / 部署困难 | 微服务拆分 → DDD 限界上下文识别边界 → 绞杀者模式迁移 |
| 服务间调用追踪 / 慢请求定位 | 分布式追踪(Jaeger / Zipkin / SkyWalking)+ TraceID 串联 |
| K8s 部署 / 扩缩容 / 健康检查 | Pod / Deployment / Service / HPA + Liveness & Readiness Probe |
| 零侵入服务治理 / 灰度发布 | Service Mesh(Istio + Envoy Sidecar) |
| 多端差异化 API(Web / App / 小程序) | BFF 模式(每个客户端一个专属 API 层) |
| 系统出问题但不知在哪个服务 | 可观测性三大支柱:Tracing + Metrics(RED/USE)+ Logging(ELK) |
| 跨服务的业务操作需要事务 | 优先 Saga(通用)或 TCC(金融),避免 2PC |
| 服务实例动态扩缩容 / 需要服务地址 | 服务发现(Nacos / Consul / K8s Service) |
| 什么时候不应该用微服务 | 团队小、业务边界不清、产品早期、缺乏分布式基础设施 |
常见误区
易错点
- 微服务不是越小越好: 拆分粒度过细会导致服务间调用频繁、分布式事务复杂、运维成本高。应按业务领域(DDD 限界上下文)合理拆分
- 分布式事务不一定需要强一致: 很多场景只需最终一致性。优先考虑 Saga + 补偿 或 本地消息表,避免使用 2PC
- 服务通信不要只用 REST: 内部服务间高频调用更适合 gRPC(性能高 5-10 倍),异步场景适合消息队列
- CAP 不是三选二: 网络分区(P)在分布式系统中不可避免,实际选择是 CP 还是 AP。而且 CAP 的 C/A 定义非常严格,实际系统通常在两者之间做程度折中
- API 网关不要承担业务逻辑: 网关只做路由、鉴权、限流等横切关注点,业务逻辑应放在后端服务中
延伸阅读
- Martin Fowler - Microservices
- Chris Richardson - Microservices Patterns
- Istio 官方文档
- OpenTelemetry 官方文档
- 《微服务设计模式》(Chris Richardson 著)
- 《凤凰架构》- 周志明