gRPC vs REST vs GraphQL:微服务通信协议的完整选型指南
2026/4/30大约 6 分钟
gRPC vs REST vs GraphQL:微服务通信协议的完整选型指南
适读人群:架构师、技术负责人、需要在微服务间选择通信协议的开发者 | 阅读时长:约18分钟
开篇故事
在一次架构评审上,有人提出要用 GraphQL 替换所有内部 REST 接口,理由是"可以减少过度拉取"。另一个人说应该全用 gRPC,理由是"性能好"。
我当时说:这两个原因都对,但结论都不对。选择通信协议不是选一个"最好的",而是选"最适合这个场景的"。
花了一个下午,我们梳理了公司所有的接口调用场景,最终的方案是:内部服务间高频调用用 gRPC,对外 API 用 REST,移动端聚合接口考虑 GraphQL。
今天把这套决策框架和完整的代码示例整理出来。
一、三种协议核心对比
1.1 核心特性对比
| 特性 | REST | gRPC | GraphQL |
|---|---|---|---|
| 传输协议 | HTTP/1.1(主流) | HTTP/2 | HTTP/1.1 或 2 |
| 数据格式 | JSON(文本) | Protobuf(二进制) | JSON(文本) |
| 类型系统 | 无(靠文档) | 强类型(.proto) | 强类型(Schema) |
| 性能 | 中 | 高(二进制+多路复用) | 中(有N+1问题) |
| 调试难度 | 易(curl/Postman) | 较难(需工具) | 中(GraphiQL) |
| 浏览器支持 | 原生 | 需要grpc-web | 原生 |
| 双向流 | 不支持 | 原生支持 | 订阅支持 |
| 适合场景 | 外部API、CRUD | 内部服务间调用 | 移动端/BFF |
二、REST:Spring Boot 完整实现
REST 大家都很熟悉,这里只强调几个容易忽视的设计原则:
/**
* RESTful API 设计规范示例
* URL 代表资源,HTTP Method 代表操作
*/
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
// GET /orders → 查询列表
@GetMapping
public Page<OrderDTO> listOrders(OrderQueryRequest request) { ... }
// GET /orders/{id} → 查询单个
@GetMapping("/{id}")
public OrderDTO getOrder(@PathVariable Long id) { ... }
// POST /orders → 创建
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public OrderDTO createOrder(@RequestBody CreateOrderRequest request) { ... }
// PUT /orders/{id} → 完整更新(幂等)
@PutMapping("/{id}")
public OrderDTO updateOrder(@PathVariable Long id, @RequestBody UpdateOrderRequest req) { ... }
// PATCH /orders/{id} → 部分更新
@PatchMapping("/{id}")
public OrderDTO patchOrder(@PathVariable Long id, @RequestBody Map<String, Object> updates) { ... }
// DELETE /orders/{id} → 删除(幂等)
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteOrder(@PathVariable Long id) { ... }
// POST /orders/{id}/cancel → 动作(不能用动词的情况下)
@PostMapping("/{id}/cancel")
public OrderDTO cancelOrder(@PathVariable Long id, @RequestBody CancelRequest req) { ... }
}三、gRPC:Spring Boot 完整实现
3.1 定义 Protocol Buffers 契约
src/main/proto/order.proto:
syntax = "proto3";
package com.laozhang.grpc;
option java_package = "com.laozhang.grpc";
option java_outer_classname = "OrderProto";
option java_multiple_files = true;
// 请求消息
message GetOrderRequest {
int64 order_id = 1;
}
// 响应消息
message OrderResponse {
int64 id = 1;
int64 user_id = 2;
string status = 3;
double amount = 4;
string created_at = 5;
repeated OrderItem items = 6;
}
message OrderItem {
int64 product_id = 1;
string product_name = 2;
int32 quantity = 3;
double price = 4;
}
message CreateOrderRequest {
int64 user_id = 1;
repeated OrderItem items = 2;
string delivery_address = 3;
}
// 服务定义
service OrderService {
// 一元调用(最常见)
rpc GetOrder(GetOrderRequest) returns (OrderResponse);
rpc CreateOrder(CreateOrderRequest) returns (OrderResponse);
// 服务端流(查询订单列表)
rpc ListOrders(ListOrdersRequest) returns (stream OrderResponse);
// 双向流(实时订单状态推送)
rpc WatchOrderStatus(stream WatchRequest) returns (stream OrderStatusUpdate);
}3.2 Maven 插件配置
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.60.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>3.3 gRPC 服务端实现
package com.laozhang.grpc.server;
import com.laozhang.grpc.*;
import io.grpc.stub.StreamObserver;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.service.GrpcService;
/**
* gRPC 服务实现
* @GrpcService 是 grpc-spring-boot-starter 提供的注解
*/
@Slf4j
@GrpcService
@RequiredArgsConstructor
public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {
private final com.laozhang.service.OrderService orderService;
@Override
public void getOrder(GetOrderRequest request, StreamObserver<OrderResponse> responseObserver) {
log.info("[gRPC] getOrder orderId={}", request.getOrderId());
try {
Order order = orderService.getById(request.getOrderId());
OrderResponse response = toProto(order);
responseObserver.onNext(response); // 发送响应
responseObserver.onCompleted(); // 完成
} catch (Exception e) {
log.error("[gRPC] getOrder 失败", e);
responseObserver.onError(e); // 发送错误
}
}
@Override
public void listOrders(ListOrdersRequest request, StreamObserver<OrderResponse> responseObserver) {
// 服务端流:逐条发送
orderService.listByUserId(request.getUserId()).forEach(order -> {
responseObserver.onNext(toProto(order));
});
responseObserver.onCompleted();
}
private OrderResponse toProto(Order order) {
return OrderResponse.newBuilder()
.setId(order.getId())
.setUserId(order.getUserId())
.setStatus(order.getStatus().name())
.setAmount(order.getAmount().doubleValue())
.build();
}
}3.4 gRPC 客户端使用
package com.laozhang.grpc.client;
import com.laozhang.grpc.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class OrderGrpcClient {
// @GrpcClient 注入 gRPC stub,指定在配置文件里定义的服务名
@GrpcClient("order-service")
private OrderServiceGrpc.OrderServiceBlockingStub orderStub;
public OrderResponse getOrder(Long orderId) {
GetOrderRequest request = GetOrderRequest.newBuilder()
.setOrderId(orderId)
.build();
return orderStub.getOrder(request);
}
}application.yml 客户端配置:
grpc:
client:
order-service:
address: 'discovery:///order-service' # 通过服务发现
negotiation-type: plaintext
deadline: 5000 # 超时 5000ms四、GraphQL:Spring Boot 完整实现
4.1 Schema 定义
src/main/resources/graphql/schema.graphqls:
type Query {
order(id: ID!): Order
orders(userId: ID!, page: Int = 0, size: Int = 10): OrderPage
user(id: ID!): User
}
type Mutation {
createOrder(input: CreateOrderInput!): Order
cancelOrder(id: ID!): Order
}
type Subscription {
orderStatusChanged(orderId: ID!): OrderStatusEvent
}
type Order {
id: ID!
status: OrderStatus!
amount: Float!
user: User # 关联查询(N+1 需要 DataLoader)
items: [OrderItem!]!
createdAt: String!
}
type User {
id: ID!
name: String!
email: String!
orders: [Order] # 关联查询
}
type OrderItem {
productId: ID!
productName: String!
quantity: Int!
price: Float!
}
type OrderPage {
content: [Order!]!
totalElements: Int!
totalPages: Int!
}
input CreateOrderInput {
userId: ID!
items: [OrderItemInput!]!
deliveryAddress: String!
}
input OrderItemInput {
productId: ID!
quantity: Int!
}
enum OrderStatus {
PENDING
PAID
SHIPPED
DELIVERED
CANCELLED
}4.2 Spring GraphQL 控制器
package com.laozhang.graphql.controller;
import com.laozhang.graphql.model.*;
import lombok.RequiredArgsConstructor;
import org.springframework.graphql.data.method.annotation.*;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
@Controller
@RequiredArgsConstructor
public class OrderGraphQLController {
private final OrderService orderService;
private final UserService userService;
@QueryMapping
public Order order(@Argument Long id) {
return orderService.getById(id);
}
@QueryMapping
public OrderPage orders(@Argument Long userId,
@Argument int page,
@Argument int size) {
return orderService.listByUserId(userId, page, size);
}
@MutationMapping
public Order createOrder(@Argument CreateOrderInput input) {
return orderService.create(input);
}
/**
* 关联字段解析:Order.user
* 使用 @BatchMapping 解决 N+1 问题
*/
@BatchMapping
public Map<Order, User> user(List<Order> orders) {
// 批量查询,一次查询所有 userId 对应的 User
List<Long> userIds = orders.stream()
.map(Order::getUserId)
.distinct()
.toList();
Map<Long, User> userMap = userService.findByIds(userIds)
.stream()
.collect(Collectors.toMap(User::getId, u -> u));
return orders.stream()
.collect(Collectors.toMap(o -> o, o -> userMap.get(o.getUserId())));
}
/**
* GraphQL 订阅:实时推送订单状态
*/
@SubscriptionMapping
public Flux<OrderStatusEvent> orderStatusChanged(@Argument Long orderId) {
return orderService.watchStatus(orderId);
}
}五、选型决策矩阵
| 场景 | 推荐协议 | 理由 |
|---|---|---|
| 对外公开 API(第三方调用) | REST | 标准化,工具生态成熟,开发者友好 |
| 内部微服务间高频调用 | gRPC | 性能好,强类型,多语言支持好 |
| 移动端/前端聚合 BFF | GraphQL | 减少过度拉取,客户端按需定制 |
| 实时推送/流式数据 | gRPC 流式 或 WebSocket | 原生流支持 |
| 简单 CRUD 接口 | REST | 成本低,上手快 |
| 多语言异构系统 | gRPC | .proto 契约是天然语言无关规范 |
六、踩坑实录
坑1:GraphQL 的 N+1 问题
症状:查询 100 个订单,每个订单都查一次用户,共发出 101 次数据库请求。
解决:使用 @BatchMapping(Spring GraphQL 内置 DataLoader 机制),把 N+1 变成 1 次批量查询。
坑2:gRPC 的 deadline 不设置导致请求堆积
症状:下游服务变慢,上游 gRPC 请求无限等待,线程耗尽。
解决:每个 gRPC 调用都设置 deadline:
OrderResponse response = orderStub
.withDeadlineAfter(5, TimeUnit.SECONDS)
.getOrder(request);坑3:REST 设计 URL 时用了动词
这是 RESTful 设计最常见的错误:
❌ /getOrder、/createOrder、/deleteOrder
✅ GET /orders/{id}、POST /orders、DELETE /orders/{id}七、总结
没有"最好的"通信协议,只有"最适合的":
- REST:门槛最低,生态最成熟,适合外部接口
- gRPC:性能最好,强类型保障,适合内部高频调用
- GraphQL:灵活性最高,适合聚合层和前端驱动的场景
在实际项目里,三种协议可以共存:外部用 REST,内部用 gRPC,移动端 BFF 用 GraphQL。
