第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 注册中心选型
| 注册中心 | 一致性模型 | 健康检查 | 多数据中心 | 适用场景 |
|---|---|---|---|---|
| Consul | CP(强一致) | 支持 | 支持 | 多数据中心、K8s 集成 |
| Eureka | AP(高可用) | 支持 | 不支持 | Spring Cloud 生态 |
| etcd | CP(强一致) | 支持 | 支持 | Kubernetes 内置 |
| ZooKeeper | CP(强一致) | 支持 | 支持 | 老项目、Hadoop 生态 |
| Nacos | AP/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)
# 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 服务定义与调用
// 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)
// 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 防止重复创建。