grpc vs http

grpc vs http

性能差异

gRPC 的一元调用(Unary RPC)与 HTTP GET 请求的性能差异主要体现在吞吐量、延迟、资源消耗三个维度,核心源于协议设计和序列化机制的不同。

一、核心性能差异机制

维度 gRPC 一元调用 HTTP GET 请求
序列化格式 Protocol Buffers(二进制,体积小、解析快) JSON/XML(文本,体积大、解析慢)
传输协议 HTTP/2(多路复用、头部压缩) 多为 HTTP/1.1(无多路复用,头部开销大)
连接模型 单连接复用多个请求 / 响应 多连接或长连接(易受队头阻塞影响)

二、实测性能差距(典型场景)

1. 吞吐量(QPS)

gRPC 的吞吐量通常是 HTTP GET 的 3~5 倍

  • 例如,在处理小数据量(如 1KB 以下)的高频请求时,gRPC 的 QPS 可达 8 万 +/ 秒,而 HTTP/1.1 + JSON 的 QPS 通常在 2 万 / 秒左右。
  • 核心原因:Protobuf 二进制编码减少数据传输量,HTTP/2 多路复用降低连接开销。

2. 延迟

gRPC 的平均延迟比 HTTP GET 低 **40%~70%**。

  • 例如,传输 10KB 数据时,gRPC 延迟约 1.1ms,而 HTTP GET 延迟约 5.2ms。
  • 核心原因:Protobuf 序列化 / 反序列化速度快(比 JSON 快 2~5 倍),HTTP/2 头部压缩减少网络传输时间。

3. 资源消耗

gRPC 的 CPU 和内存占用比 HTTP GET 低 **30%~50%**。

  • 例如,在高并发场景下,gRPC 服务的 CPU 使用率约 42%,而 HTTP 服务可能达到 65%。
  • 核心原因:二进制编码和多路复用减少了序列化、连接管理的资源开销。

三、场景化差异说明

场景 gRPC 优势 HTTP GET 优势
高并发微服务通信 吞吐量提升 3~5 倍,适合内部服务频繁调用 生态成熟,调试工具丰富(如 Postman)
大数据量传输(如文件) 延迟降低 40%~60%,二进制编码减少带宽占用 文本格式可读性强,适合简单数据传输
弱网络环境(如公网) 头部压缩和连接复用减少 30%~50% 响应时间 兼容性好,浏览器原生支持

性能敏感的内部微服务通信场景中,gRPC 一元调用的吞吐量、延迟、资源消耗全面优于 HTTP GET 请求,提升幅度可达 3~5 倍。但如果是对外暴露的简单 API调试场景,HTTP GET 因生态成熟度更具优势。实际选型需结合业务需求(如性能优先级、跨语言支持、调试成本)综合判断。

grpc不需要重复建立连接吗?

在内部系统的 gRPC 调用中,不需要重复建立连接,因为 gRPC 基于 HTTP/2 协议,支持连接复用,能在单个 TCP 连接上处理大量请求,这也是其性能优势的关键之一。

一、gRPC 连接复用的原理

gRPC 基于 HTTP/2 协议,而 HTTP/2 的核心特性之一就是多路复用

  • 单个 TCP 连接:客户端与服务端建立一次 TCP 连接后,可在该连接上并行发送 / 接收多个请求和响应(每个请求对应一个 HTTP/2 流)。
  • 长连接保持:默认情况下,gRPC 客户端会保持 TCP 连接的活跃状态(除非长时间无通信或显式关闭),后续所有 gRPC 调用都会复用该连接。

二、内部系统中 gRPC 连接的实际表现

对于内部系统的 “一次一次” 调用(即多次独立的 RPC 调用),gRPC 的处理方式是:

  1. 首次调用:客户端与服务端建立 TCP 连接(包含 TLS 握手、HTTP/2 协议协商等),耗时约 100~500ms(取决于网络环境)。
  2. 后续调用:直接复用已建立的 TCP 连接,通过 HTTP/2 多路复用发送请求,无需重复建立连接,单次请求的额外开销几乎可以忽略(仅需传输请求数据和处理 HTTP/2 流)。

三、对比 HTTP/1.1 的优势

HTTP/1.1 通常为短连接(每次请求需建立新 TCP 连接,或依赖 Keep-Alive 保持有限时间),而 gRPC 基于 HTTP/2 的长连接复用可显著减少连接建立的开销:

  • 连接建立耗时:TCP 三次握手 + TLS 握手约占单次请求耗时的 30%~50%(尤其是内部网络延迟低时,该开销占比更明显)。
  • gRPC 规避了这一问题:只需一次连接建立,后续所有调用均复用该连接,使单次请求的有效数据传输占比大幅提升。

四、代码验证(Java 客户端示例)

gRPC 客户端的 ManagedChannel 会自动管理连接复用,无需手动干预:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建一次 Channel(连接),后续所有调用复用该连接
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9090)
.usePlaintext() // 内部系统可禁用 TLS
.build();

// 多次调用(复用同一个 Channel)
HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);
for (int i = 0; i < 100; i++) {
HelloResponse response = stub.sayHello(HelloRequest.newBuilder().setName("User" + i).build());
System.out.println(response.getMessage());
}

channel.shutdown(); // 最后关闭连接

上述代码中,100 次调用均复用同一个 ManagedChannel 对应的 TCP 连接,无需重复建立。

总结

在内部系统的频繁调用场景中,gRPC 天然支持连接复用,无需为每次调用重复建立连接,这是其相比 HTTP/1.1 短连接架构的核心性能优势之一。对于内部系统的高并发、低延迟需求,gRPC 的连接复用机制能进一步放大其吞吐量和响应速度的优势。

HelloServiceBlockingStub创建时机对比

1
2
3
4
5
6
7
8
9
10
11
12
// 多次调用(复用同一个 Channel)
HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);
for (int i = 0; i < 100; i++) {
HelloResponse response = stub.sayHello(HelloRequest.newBuilder().setName("User" + i).build());
System.out.println(response.getMessage());
}

for (int i = 0; i < 100; i++) {
HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);
HelloResponse response = stub.sayHello(HelloRequest.newBuilder().setName("User" + i).build());
System.out.println(response.getMessage());
}

这两种方式的核心差异在于 HelloServiceBlockingStub 的创建时机,但从功能和性能角度看,实际差异极小,主要体现在代码规范和微小的对象创建开销上。

具体差异对比

维度 方式一(外部创建 Stub) 方式二(循环内创建 Stub)
Stub 创建次数 1 次(在循环外创建,复用 100 次) 100 次(每次循环都新建一个 Stub,用完即弃)
连接复用 复用同一个 ManagedChannel,连接无差异 同样复用同一个 ManagedChannel,连接无差异
功能正确性 完全正确,所有调用正常执行 完全正确,所有调用正常执行
性能开销 几乎无额外开销(仅创建 1 个 Stub 对象) 有微小开销(创建 100 个 Stub 对象,GC 会回收)
代码规范性 更优(Stub 是轻量对象,复用更符合设计意图) 稍差(重复创建相同作用的对象,略显冗余)

核心原理:Stub 是轻量代理对象

gRPC 的 Stub(如 HelloServiceBlockingStub)本质是代理对象,它不持有 TCP 连接,仅依赖 ManagedChannel 与服务端通信。

  • ManagedChannel 才是持有 TCP 连接、管理连接池的核心组件,两种方式都复用同一个 channel,因此连接层面完全一致,不会因为 Stub 创建次数不同而影响连接复用。
  • Stub 的创建成本极低(只是封装了 channel 和一些配置,如超时、拦截器等),即使循环内创建 100 次,对性能的影响也微乎其微(除非循环次数达到百万级,才可能产生可观测的差异)。

推荐方式:方式一(外部创建 Stub)

虽然两种方式都能正常工作,但方式一更符合 gRPC 的设计意图:

  • Stub 是 “无状态” 的轻量对象,一旦创建可重复使用(除非需要修改配置,如超时、拦截器)。
  • 减少不必要的对象创建,代码更简洁,也更易于维护(例如后续需要为 Stub 添加超时设置,只需在一处修改)。

优化后的代码示例(显式配置 Stub 特性):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9090)
.usePlaintext()
.build();

// 一次创建 Stub,可统一配置(如超时)
HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel)
.withDeadlineAfter(3, TimeUnit.SECONDS); // 统一设置超时

for (int i = 0; i < 100; i++) {
HelloResponse response = stub.sayHello(HelloRequest.newBuilder().setName("User" + i).build());
System.out.println(response.getMessage());
}

channel.shutdown();

总结

两种方式的功能和连接复用逻辑完全一致,差异仅在于 Stub 的创建次数和代码规范性。实际开发中推荐方式一(外部创建 Stub 并复用),既减少冗余,也便于统一配置 Stub 的特性(如超时、拦截器)。