服务通信方式
问题
微服务之间如何通信?同步通信和异步通信各有什么适用场景?REST 和 gRPC 怎么选?
答案
通信方式分类
| 维度 | 同步 | 异步 |
|---|---|---|
| 特点 | 请求-响应,调用方等待结果 | 发送消息后不等待,通过回调/轮询获取结果 |
| 协议 | HTTP REST、gRPC、Feign | MQ(Kafka、RocketMQ)、事件总线 |
| 耦合度 | 较高(直接依赖) | 低(通过消息中间件解耦) |
| 适用 | 即时响应、查询操作 | 不需要即时响应、事件通知 |
REST vs gRPC
| 对比 | REST | gRPC |
|---|---|---|
| 协议 | HTTP/1.1(JSON) | HTTP/2(Protobuf) |
| 序列化 | JSON(文本) | Protobuf(二进制) |
| 性能 | 较低 | 高(二进制 + 多路复用) |
| 流式通信 | 不原生支持 | 4 种流模式 |
| 类型安全 | 弱(JSON) | 强(.proto 定义) |
| 浏览器支持 | 原生支持 | 需要 gRPC-Web |
| 生态 | 最广泛 | 快速增长 |
| 适用 | 对外 API、BFF | 内部服务间高性能通信 |
gRPC 服务定义
syntax = "proto3";
service OrderService {
rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
rpc GetOrder (GetOrderRequest) returns (Order);
// 服务端流式
rpc ListOrders (ListOrdersRequest) returns (stream Order);
}
message CreateOrderRequest {
string user_id = 1;
string product_id = 2;
int32 quantity = 3;
}
Spring Cloud OpenFeign
声明式 HTTP 客户端,简化服务间调用。
Feign 客户端
@FeignClient(name = "inventory-service", fallback = InventoryFallback.class)
public interface InventoryClient {
@PostMapping("/api/inventory/deduct")
Result<Boolean> deductStock(@RequestBody DeductRequest request);
@GetMapping("/api/inventory/{productId}")
Result<Integer> getStock(@PathVariable String productId);
}
// 降级处理
@Component
public class InventoryFallback implements InventoryClient {
@Override
public Result<Boolean> deductStock(DeductRequest request) {
return Result.fail("库存服务不可用");
}
@Override
public Result<Integer> getStock(String productId) {
return Result.fail("库存服务不可用");
}
}
异步通信(事件驱动)
领域事件驱动
// 订单服务:发布订单创建事件
@Service
public class OrderService {
@Autowired
private RocketMQTemplate mqTemplate;
@Transactional
public Order createOrder(OrderDTO dto) {
Order order = orderRepository.save(dto.toEntity());
// 发布领域事件
mqTemplate.convertAndSend("order-events",
new OrderCreatedEvent(order.getId(), order.getUserId()));
return order;
}
}
// 积分服务:订阅事件
@Component
@RocketMQMessageListener(topic = "order-events", consumerGroup = "points-group")
public class PointsEventListener implements RocketMQListener<OrderCreatedEvent> {
@Override
public void onMessage(OrderCreatedEvent event) {
pointsService.addPoints(event.getUserId(), 10);
}
}
通信方式选择
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 查询数据 | 同步(Feign/REST) | 需要即时返回结果 |
| 下单扣库存 | 同步 + 分布式事务 | 强一致性要求 |
| 订单→通知 | 异步(MQ) | 不影响主流程 |
| 订单→积分 | 异步(MQ) | 最终一致即可 |
| 内部高频调用 | gRPC | 高性能、低延迟 |
| 对外 API | REST | 通用性强 |
常见面试问题
Q1: 微服务之间怎么调用?
答案:
两种主要方式:
- 同步调用:OpenFeign(声明式 REST)、gRPC。适用于需要即时响应的场景
- 异步消息:通过 MQ 发布/订阅事件。适用于不需要即时响应、解耦的场景
选择原则:能异步就异步(减少耦合和级联故障),需要即时响应才用同步。
Q2: 服务调用失败怎么办?
答案:
- 超时控制:设置合理的超时时间(如 3 秒),避免无限等待
- 重试:幂等接口可以配置重试(如 Feign 默认不重试,需手动配置)
- 熔断降级:使用 Sentinel 或 Resilience4j,快速失败并返回兜底数据
- 异步补偿:同步调用失败时发消息异步重试
Q3: 如何减少服务间调用次数?
答案:
- BFF 聚合层:前端需要的数据在 BFF 层聚合,减少前端多次调用
- 数据冗余:将高频查询的数据冗余到本服务(通过事件同步)
- 批量查询:避免循环调用,改为批量接口
- 缓存:本地缓存 + Redis 缓存减少远程调用