3微服务架构设计模式

进程间通信

服务间如何对话

同步通信异步通信RESTgRPC

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第3章:进程间通信(Inter-Process Communication)

一、导读

1.1 本章学习目标

微服务拆分之后,服务之间的通信成为架构设计的核心问题。第3章系统介绍了微服务间的两种通信模式——同步通信和异步通信,以及支撑通信的基础设施——API 网关和服务发现。Richardson 通过对比 REST、gRPC、消息队列等技术的适用场景,帮助读者建立通信方案的选型框架。

本章的核心学习目标包括:

  • 理解同步与异步通信的本质差异:什么时候用同步?什么时候用异步?
  • 掌握 API 网关模式:路由、聚合、认证、限流、协议转换。
  • 理解服务发现机制:客户端发现 vs 服务端发现,注册中心的选型。
  • 掌握 REST API 设计原则:资源建模、HTTP 方法语义、幂等性设计。
  • 了解 gRPC 的适用场景:高性能内部通信、流式传输、强类型约束。
  • 理解通信可靠性保障:重试、熔断、超时、限流等容错机制。

1.2 为什么本章如此重要

在单体架构中,模块之间的方法调用是进程内的——快速、可靠、确定性。但在微服务架构中,服务之间的通信是跨进程的——涉及网络传输,可能超时、失败、延迟波动。Richardson 明确指出:微服务架构中 70% 的复杂性来自服务间通信。

本章的重要性在于它建立了微服务通信的完整知识体系。从通信模式(同步 vs 异步)到通信协议(REST vs gRPC vs 消息队列),从服务发现(注册中心)到容错机制(熔断器、重试),读者将掌握构建可靠微服务通信所需的全部知识。

1.3 核心问题引导

同步通信和异步通信各有什么优缺点?如何选择?

API 网关应该承担哪些职责?它不应该做什么?

客户端发现和服务端发现的区别是什么?各自的适用场景?

如何设计幂等的 REST API?为什么幂等性对微服务如此重要?

熔断器模式如何防止级联故障?

gRPC 相比 REST 有什么优势?在什么场景下应该使用 gRPC?


二、核心概念详解

2.1 通信模式概览

2.1.1 同步通信

同步通信要求调用方等待响应。常见的同步通信方式包括:

  • HTTP/REST:最通用的方式,基于文本(JSON),跨语言支持好。
  • gRPC:Google 开源的高性能 RPC 框架,基于 Protocol Buffers(二进制),支持流式传输。
  • GraphQL:客户端驱动的查询语言,适合前端灵活查询。
// 同步通信的特点:
// 优点:
// 1. 简单直观(请求-响应模型)
// 2. 实时获取结果
// 3. 错误处理直接
//
// 缺点:
// 1. 调用方和被调用方必须同时可用(耦合时间)
// 2. 网络延迟直接影响响应时间
// 3. 级联故障风险(下游服务故障导致上游服务阻塞)
//
// 适用场景:
// - 需要实时获取结果的查询操作
// - 简单的 CRUD 操作
// - 前端到后端的通信

2.1.2 异步通信

异步通信中,发送方不需要等待响应。常见的异步通信方式包括:

  • 消息队列:点对点模型,消息被一个消费者处理(如 RabbitMQ、AWS SQS)。
  • 发布/订阅:主题模型,消息被多个订阅者接收(如 Kafka、RabbitMQ Topics)。
  • 事件驱动:服务发布领域事件,其他服务订阅并响应。
// 异步通信的特点:
// 优点:
// 1. 发送方和接收方解耦(不需要同时可用)
// 2. 天然支持削峰填谷(消息队列缓冲突发流量)
// 3. 支持扇出(一个事件触发多个消费者)
//
// 缺点:
// 1. 无法实时获取结果
// 2. 需要处理消息重复、消息顺序等问题
// 3. 调试和监控更复杂
//
// 适用场景:
// - 不需要实时结果的操作(如发送通知)
// - 流量波动大的场景(削峰填谷)
// - 需要扇出的场景(一个事件触发多个操作)

2.2 API 网关模式

2.2.1 为什么需要 API 网关

在微服务架构中,客户端直接调用微服务会面临多个问题:

  • 客户端需要知道每个服务的地址
  • 多个服务调用需要多次网络请求
  • 认证、限流等横切关注点需要在每个服务中实现
  • 服务可能使用不同的协议

API 网关作为客户端和后端服务之间的中介,统一解决这些问题。

// 没有 API 网关:
// [Client] → [order-service]
//         → [payment-service]
//         → [restaurant-service]
//         → [delivery-service]
// 问题:客户端需要知道所有服务的地址,多次调用

// 有 API 网关:
// [Client] → [API Gateway] → [order-service]
//                          → [payment-service]
//                          → [restaurant-service]
//                          → [delivery-service]
// 优势:客户端只需知道网关地址,网关负责路由和聚合

2.2.2 API 网关的核心职责

// API 网关的职责:
// 1. 路由:根据请求路径将请求转发到对应的服务
// 2. 聚合:将多个服务的响应聚合为一个响应
// 3. 认证:验证 JWT Token、OAuth Token
// 4. 限流:限制每个客户端的请求频率
// 5. 协议转换:HTTP → gRPC,HTTP → WebSocket
// 6. 缓存:缓存常见查询的响应
// 7. 日志和监控:记录所有请求的日志

// API 网关不应该做的事:
// 1. 业务逻辑处理(网关只做路由和横切关注点)
// 2. 数据转换(应该由 BFF 或后端服务处理)
// 3. 数据库访问(网关不直接访问数据库)

2.2.3 BFF 模式

Richardson 还介绍了 BFF(Backend for Frontend)模式——为不同的客户端(Web、Mobile、Desktop)提供专门的 API 网关。

// BFF 模式:
// [Web Browser] → [Web BFF]     → [后端微服务]
// [iOS App]     → [Mobile BFF]  → [后端微服务]
// [Android App] → [Mobile BFF]  → [后端微服务]
//
// 优势:
// - Web BFF 返回完整的 HTML 或大量 JSON
// - Mobile BFF 返回精简的 JSON(节省流量)
// - 每个 BFF 针对客户端特点优化响应格式

2.3 服务发现

2.3.1 客户端发现 vs 服务端发现

微服务的实例地址是动态的(容器化部署中尤其如此),需要服务发现机制来定位服务实例。

// 客户端发现模式:
// 1. 服务启动时注册到注册中心(Consul/Eureka)
// 2. 客户端从注册中心获取服务实例列表
// 3. 客户端使用负载均衡算法选择一个实例
// 4. 客户端直接调用该实例
//
// [Client] → [Registry] 查询 order-service 的实例列表
// [Client] → [order-service instance 1] 直接调用
//
// 优点:客户端可以选择最优实例
// 缺点:客户端需要实现负载均衡逻辑

// 服务端发现模式:
// 1. 服务启动时注册到注册中心
// 2. 客户端请求发送到负载均衡器
// 3. 负载均衡器从注册中心获取实例列表
// 4. 负载均衡器选择一个实例转发请求
//
// [Client] → [Load Balancer] → [Registry] 查询实例列表
//                            → [order-service instance 2]
//
// 优点:客户端不需要知道服务发现细节
// 缺点:多一跳网络延迟

2.3.2 注册中心选型

注册中心一致性模型健康检查多数据中心适用场景
ConsulCP(强一致)支持支持多数据中心、K8s 集成
EurekaAP(高可用)支持不支持Spring Cloud 生态
etcdCP(强一致)支持支持Kubernetes 内置
ZooKeeperCP(强一致)支持支持老项目、Hadoop 生态
NacosAP/CP 可切换支持支持阿里巴巴生态

2.4 REST API 设计

2.4.1 REST 成熟度模型

Richardson 引用了 Leonard Richardson 的 REST 成熟度模型:

// Level 0: The Swamp of POX
// 使用 HTTP 作为传输机制,但没有使用 REST 语义
// POST /api/getOrder    { "id": 123 }
// POST /api/createOrder { "customerId": "456", ... }

// Level 1: Resources
// 每个实体有一个 URI
// GET /api/orders/123
// POST /api/orders

// Level 2: HTTP Verbs
// 使用 HTTP 方法的语义
// GET    /api/orders/123   → 查询
// POST   /api/orders       → 创建
// PUT    /api/orders/123   → 全量更新
// PATCH  /api/orders/123   → 部分更新
// DELETE /api/orders/123   → 删除

// Level 3: HATEOAS(超媒体驱动)
// 响应中包含可操作的链接
// {
//   "id": 123,
//   "status": "CREATED",
//   "_links": {
//     "self": { "href": "/api/orders/123" },
//     "cancel": { "href": "/api/orders/123/cancel", "method": "POST" },
//     "pay": { "href": "/api/orders/123/pay", "method": "POST" }
//   }
// }

2.4.2 幂等性设计

幂等性是指同一个操作执行多次和执行一次的效果相同。在微服务中,幂等性至关重要——网络不可靠,客户端可能需要重试请求。

// HTTP 方法的幂等性:
// GET    → 天然幂等(查询不影响状态)
// PUT    → 天然幂等(全量更新,多次执行结果相同)
// DELETE → 天然幂等(删除多次和删除一次效果相同)
// POST   → 非幂等(创建多次会创建多个资源)
// PATCH  → 取决于具体操作

// POST 的幂等性设计:
// 方案1:使用 Idempotency-Key
POST /api/orders
Idempotency-Key: req-abc-123
Content-Type: application/json
{ "customerId": "456", "items": [...] }

// 服务端处理:
// 1. 检查 Idempotency-Key 是否已处理
// 2. 如果已处理 → 返回之前的响应
// 3. 如果未处理 → 处理请求,保存结果和 Key
// 4. 返回响应

// 方案2:使用业务唯一键
// 如订单号 = 用户ID + 时间戳 + 随机数
// 数据库唯一约束保证不会重复创建

2.5 容错机制

2.5.1 超时与重试

// 超时设置原则:
// 1. 总是设置超时(没有超时的调用是危险的)
// 2. 超时时间应该略大于 P99 延迟
// 3. 不同操作设置不同的超时

// 重试策略:
// 1. 只对幂等操作重试(GET、PUT、DELETE)
// 2. 使用指数退避(Exponential Backoff)
// 3. 设置最大重试次数
// 4. 区分可重试异常(超时、503)和不可重试异常(400、404)

// 指数退避示例:
// 第1次重试:等待 100ms
// 第2次重试:等待 200ms
// 第3次重试:等待 400ms
// 第4次重试:等待 800ms
// 最大重试次数:3次

2.5.2 熔断器模式

熔断器模式防止级联故障——当下游服务故障时,快速失败而不是长时间等待。

// 熔断器的三种状态:
// CLOSED(关闭)→ 正常状态,请求正常通过
// OPEN(打开)  → 故障状态,请求直接返回错误(快速失败)
// HALF_OPEN(半开)→ 探测状态,允许少量请求通过探测是否恢复

// 状态转换:
// CLOSED → OPEN:失败率超过阈值(如 50%)
// OPEN → HALF_OPEN:等待超时时间后(如 30秒)
// HALF_OPEN → CLOSED:探测成功
// HALF_OPEN → OPEN:探测失败

// Resilience4j 配置示例:
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)           // 失败率阈值 50%
    .waitDurationInOpenState(Duration.ofSeconds(30))  // 打开等待时间
    .slidingWindowSize(10)              // 滑动窗口大小
    .minimumNumberOfCalls(5)            // 最少调用次数
    .build();

三、实战案例

3.1 案例一:API 网关配置(Kong)

yaml
# Kong API 网关配置
services:
  - name: order-service
    url: http://order-service:8080
    routes:
      - name: order-routes
        paths: ["/api/orders"]
        methods: ["GET", "POST", "PUT", "DELETE"]
    plugins:
      - name: rate-limiting
        config:
          minute: 100
          policy: redis
      - name: jwt
        config:
          key_claim_name: iss

  - name: payment-service
    url: http://payment-service:8080
    routes:
      - name: payment-routes
        paths: ["/api/payments"]
    plugins:
      - name: rate-limiting
        config:
          minute: 30

  - name: restaurant-service
    url: http://restaurant-service:8080
    routes:
      - name: restaurant-routes
        paths: ["/api/restaurants"]

plugins:
  - name: cors
    config:
      origins: ["https://ftgo.com"]
      methods: ["GET", "POST", "PUT", "DELETE"]
  - name: request-transformer
    config:
      add:
        headers: ["X-Gateway-Version:1.0"]

3.2 案例二:gRPC 服务定义与调用

protobuf
// order_service.proto
syntax = "proto3";

package ftgo.order;

service OrderService {
  rpc CreateOrder(CreateOrderRequest) returns (Order);
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse);
  rpc UpdateOrderStatus(UpdateStatusRequest) returns (Order);
  rpc WatchOrderStatus(WatchRequest) returns (stream OrderStatusEvent);
}

message CreateOrderRequest {
  string customer_id = 1;
  string restaurant_id = 2;
  repeated OrderItem items = 3;
  string delivery_address = 4;
}

message OrderItem {
  string menu_item_id = 1;
  int32 quantity = 2;
  string special_instructions = 3;
}

message Order {
  string id = 1;
  string customer_id = 2;
  string restaurant_id = 3;
  repeated OrderItem items = 4;
  string status = 5;
  double total_amount = 6;
  int64 created_at = 7;
}

3.3 案例三:服务注册与发现(Consul + Spring Boot)

java
// application.yml
// spring:
//   cloud:
//     consul:
//       host: consul-server
//       port: 8500
//       discovery:
//         service-name: order-service
//         health-check-interval: 10s
//         health-check-path: /actuator/health

// OrderServiceApplication.java
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

// 使用 Feign 客户端调用其他服务
@FeignClient(name = "restaurant-service",
    fallbackFactory = RestaurantClientFallback.class)
public interface RestaurantClient {
    @GetMapping("/api/restaurants/{id}")
    Restaurant getRestaurant(@PathVariable("id") String id);
}

// 降级处理
@Component
public class RestaurantClientFallback implements FallbackFactory<RestaurantClient> {
    @Override
    public RestaurantClient create(Throwable cause) {
        return id -> Restaurant.defaultRestaurant(id);
    }
}

四、本章小结

本章系统讲解了微服务间的进程间通信:

核心知识点:

  • 同步通信(REST/gRPC)适合需要实时结果的场景,异步通信(消息队列)适合解耦和削峰。
  • API 网关统一处理路由、认证、限流、协议转换,但不应包含业务逻辑。
  • 服务发现分为客户端发现和服务端发现,注册中心(Consul/Eureka/etcd)是核心组件。
  • REST API 设计遵循资源建模、HTTP 方法语义、幂等性原则。
  • 容错机制(超时、重试、熔断器)是微服务通信的必备组件。

五、思考与练习

思考题

在你的项目中,哪些服务间通信适合用同步方式?哪些适合用异步方式?

API 网关和 BFF 模式应该如何配合使用?

熔断器的参数(失败率阈值、等待时间)应该如何确定?

练习题

使用 Kong 或 Spring Cloud Gateway 搭建 API 网关,配置路由、限流和认证。

实现一个 gRPC 服务,包含一元调用和服务端流式调用。

设计一个幂等的 POST API,使用 Idempotency-Key 防止重复创建。

3/12进程间通
0%