HTTP/JSON 到底慢在哪

微服务之间默认用 HTTP + JSON 通信,简单直观,但规模一上来就有三个问题:JSON 文本体积大、每次请求都要完整解析字段、基于 HTTP/1.1 一问一答连接开销高。一次调用可能只是查个库存,却要传上百字节的字段名、引号和逗号,还要在调用方和服务方反复做 JSON 编解码。

gRPC 是 Google 开源的高性能 RPC 框架,建立在 HTTP/2 之上:用二进制的 Protocol Buffers 序列化,体积比 JSON 小很多、解析快;同一条 HTTP/2 连接上能并发多路复用多个请求,还天然支持双向流。服务内部高并发调用、内部编排,gRPC 通常比 JSON over HTTP 快一个量级。

本文核心知识点
  • IDL用 .proto 文件定义服务与消息,两端据此生成代码。
  • 四种模式一元、服务端流、客户端流、双向流。
  • HTTP/2多路复用让并发请求挤在一条连接上。
  • 超时与重试调用方必须显式设超时,避免级联雪崩。
gRPC 微服务通信配图:蓝色渐变背景中央一个发光的协议契约方块,左右两侧服务节点经双向发光管道连接,管道上流动光点,扁平科技插画
中央方块代表 .proto 定义的契约,两侧节点经 HTTP/2 双向管道高效互通。

Protocol Buffers 与 IDL

gRPC 的核心是先写一个 .proto 文件:用 IDL(接口定义语言)声明有哪些服务、每个方法叫什么、请求和响应消息包含哪些字段。然后用 protoc 工具,根据这份契约自动生成 Java、Go、Python 等各语言的桩代码——调用方和服务方拿的是同一份强类型定义,字段名、类型对齐,不用再手写 DTO 也不用靠文档对齐。

syntax = "proto3";

service OrderService {
  rpc GetOrder (GetOrderReq) returns (Order) {}
}

message GetOrderReq { int64 order_id = 1; }
message Order {
  int64  id     = 1;
  string status = 2;
  double amount = 3;
}

字段后面的数字(=1、=2)是字段编号,序列化时用编号代替字段名,体积小、且改名不影响线上兼容。新增字段用新编号,旧代码读到不认识的编号会自动跳过,这就是 proto3 前向兼容的基础。

四种通信模式

gRPC 不只是普通的“请求响应”,它借 HTTP/2 的流能力支持四种模式:

  • 一元调用:最常用,发一个请求收一个响应,和普通接口一样。
  • 服务端流:客户端发一个请求,服务端持续回传一串消息(如行情推送、日志订阅)。
  • 客户端流:客户端连续发一串消息,最后服务端回一个汇总(如批量上传)。
  • 双向流:两条流同时打开,双方随时互发消息(如实时聊天室、双向同步)。

选哪种看业务:查单、扣库存这类一次性操作用一元;需要“边生产边处理”的场景(如日志采集、实时对账)就用流模式,避免反复建连。

服务端与客户端代码

生成桩代码后,服务端只要实现 .proto 里定义的接口方法,客户端拿到一个 Stub 就能像调本地函数一样调用。下面是一段 Go 风格的服务端实现示意:

// 服务端:实现 OrderServiceServer 接口
type orderServer struct {
  pb.UnimplementedOrderServiceServer
}

func (s *orderServer) GetOrder(ctx context.Context, req *pb.GetOrderReq) (*pb.Order, error) {
  return &pb.Order{Id: req.OrderId, Status: "PAID", Amount: 99.5}, nil
}
// 客户端:建立连接后直接调用
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
client := pb.NewOrderServiceClient(conn)
resp, _ := client.GetOrder(ctx, &pb.GetOrderReq{OrderId: 1001})

注意连接是长连接,建一次连、反复复用,而不是每次调用重新握手。这也是 gRPC 高并发下省资源的关键。

拦截器、超时与错误处理

生产环境绝不能裸调。三件事必须做:

  • 超时:每个调用都要带 context 超时,下游卡住时调用方及时放弃,避免线程被占满引发级联雪崩。
  • 拦截器:用 interceptor 统一加日志、鉴权、链路追踪、指标埋点,不用每个方法手写。
  • 错误模型:gRPC 用 status code(如 NotFound、Unavailable、DeadlineExceeded)表达错误,比 HTTP 状态码更细,客户端可据此决定是否重试。

重试要克制:只对幂等、且明确是网络抖动(Unavailable)的调用重试,否则重复扣款这类非幂等操作会出大问题。

gRPC 与 REST 的取舍

gRPC 性能好,但不是所有场景都该用。内部服务之间(订单、库存、支付互相调用)用 gRPC,体积小、延迟低、契约强;对外暴露给前端、第三方、浏览器的接口仍用 REST/JSON——因为浏览器原生不支持 gRPC 的二进制流,调试也不如 JSON 直观。

常见做法是:前面一层 API 网关对外提供 REST,内部转成 gRPC 调到各微服务,既保留对前端友好的 JSON,又拿到内部高效通信的红利。网关与微服务的关系,可参考本站《API 网关与微服务治理实战》。