为什么越来越多人用gRPC?

前言

有些小伙伴在工作中可能遇到过这样的情况:系统上线后流量涨上来了,服务之间的调用越来越多,响应时间开始飘忽不定。

你打开监控一看,明明业务逻辑没变,数据库查询也加了索引,但接口就是越来越慢。

这不是代码写得不好,而是服务间通信的"路"太窄了

前阵子有小伙伴反馈说,他们的架构是典型的Spring Cloud微服务,服务间调用全是REST API + JSON。

压测的时候QPS跑到800左右就开始大面积超时,CPU先扛不住了------不是业务代码的问题,是序列化和HTTP连接的开销把CPU吃满了。

后来他们做了一个实验:把核心链路里两个高频调用的服务改成gRPC通信。

同样的业务逻辑、同样的机器配置,QPS直接翻了将近3倍,响应时间降到了原来的三分之一。

其实Spring Boot 4.1.0已经提供了对 gRPC 的官方支持。

gRPC正在从"小众框架"变成越来越多公司的"默认选择"。

今天这篇专门跟大家一起聊聊gRPC,希望对你会有所帮助。

更多项目实战在Java突击队网:susan.net.cn/project

二、REST到底慢在哪?

在聊gRPC之前,我们先搞清楚一个问题------REST API + JSON到底慢在哪?

第一,HTTP/1.1的"一请求一连接"限制。

REST API通常跑在HTTP/1.1上。

每个请求都需要建立一个新的TCP连接,经历三次握手。

请求处理完,连接要么关闭,要么保持有限的Keep-Alive。

高并发下,频繁建立和销毁连接的开销非常大。

第二,JSON是文本协议,又大又慢。

JSON是人类可读的文本格式,每个字段名都要重复传输。

一个对象里如果有10个字段,每个请求都要把这10个字段名再传一遍。

数据量一大,网络传输就成了瓶颈。

第三,序列化/反序列化开销大。

JSON的解析是文本解析,要把字符串拆成Token、构建对象树、再映射到Java对象。这个过程比二进制解析慢得多。

第四,没有连接复用。

每个请求都是独立的,无法在一个连接上同时处理多个请求。

这些问题在低并发时几乎感觉不到,但一旦流量上来,每个问题都会被无限放大。

三、gRPC凭什么快?

gRPC的核心优势,源自它底层两个关键技术。

3.1 HTTP/2:连接复用,干掉握手开销

gRPC跑在HTTP/2上,跟HTTP/1.1有本质区别:

特性 HTTP/1.1 HTTP/2
连接模型 每个请求一个连接 一个连接多路复用
连接建立 3次握手/请求 1次握手/会话
数据传输 文本 二进制帧
并发请求 串行/有限并行 真正的并行
服务端推送

HTTP/2最核心的能力是多路复用------在一个TCP连接上同时处理成百上千个请求,互不阻塞。

3.2 Protobuf:比JSON小60%,快5倍

gRPC默认使用Protocol Buffers(Protobuf) 作为序列化协议。

Protobuf是二进制的,不需要把字段名转成字符串传输,只传字段编号和值。

实测数据:Protobuf序列化后的体积比JSON减少60%-80%,序列化速度提升3-5倍

下面这个对比很直观:

指标 REST + JSON gRPC + Protobuf
数据体积 基准 减少60%-80%
序列化速度 基准 提升3-5倍
连接建立 3RTT 1RTT
请求延迟 8-12ms 2-3ms
吞吐量 450 req/s 1200 req/s

连接建立上gRPC优势也很明显。

HTTP/1.1每次请求需要3次握手(3RTT),而gRPC通过HTTP/2多路复用只需要1次握手(1RTT),后续请求走同一个连接。

"一次握手,多次使用" 在高并发场景下,这个差距会被无限放大。

四、一张图看懂gRPC的整体架构

在深入代码之前,我们先建立一个整体认知。

从这张图可以看到,gRPC的架构中契约层(Proto文件)是核心

它定义了服务接口和数据结构,然后通过代码生成工具生成客户端和服务端的骨架代码。

客户端和服务端都基于同一份Proto文件生成代码,保证了类型安全跨语言一致性

五、一个完整的gRPC服务

光说理论不够,我们来看代码。

5.1 第一步:定义Proto文件

protobuf 复制代码
syntax = "proto3";

package com.example.grpc;

service UserService {
    rpc GetUser (UserRequest) returns (UserResponse);
    rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
}

message UserRequest {
    int64 id = 1;
}

message ListUsersRequest {
    int32 page = 1;
    int32 size = 2;
}

message UserResponse {
    int64 id = 1;
    string name = 2;
    string email = 3;
    int32 age = 4;
}

5.2 第二步:添加依赖

xml 复制代码
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-netty-shaded</artifactId>
    <version>1.68.0</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-protobuf</artifactId>
    <version>1.68.0</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-stub</artifactId>
    <version>1.68.0</version>
</dependency>

5.3 第三步:实现服务端

java 复制代码
@GrpcService
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
    
    @Override
    public void getUser(UserRequest request, 
                        StreamObserver<UserResponse> responseObserver) {
        // 模拟业务逻辑
        long userId = request.getId();
        UserResponse response = UserResponse.newBuilder()
            .setId(userId)
            .setName("用户" + userId)
            .setEmail("user" + userId + "@example.com")
            .setAge(25)
            .build();
        
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

5.4 第四步:配置服务端

yaml 复制代码
grpc:
  server:
    port: 9090

5.5 第五步:客户端调用

java 复制代码
public class UserClient {
    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder
            .forAddress("localhost", 9090)
            .usePlaintext()
            .build();
        
        UserServiceGrpc.UserServiceBlockingStub stub = 
            UserServiceGrpc.newBlockingStub(channel);
        
        UserRequest request = UserRequest.newBuilder()
            .setId(1001L)
            .build();
        
        UserResponse response = stub.getUser(request);
        System.out.println("用户信息:" + response.getName());
    }
}

六、Spring Boot 4.1.0 官方 gRPC 支持

有些小伙伴可能会说:"gRPC 理论我懂了,但 Spring Boot 里到底怎么用?"

以前想在 Spring Boot 里用 gRPC,基本要靠第三方 starter,或者自己手动配 Server、Channel、拦截器、异常处理。项目一复杂,就开始东拼西凑,排查起来头大。

Spring Boot 4.1.0 把 gRPC 自动配置纳进来了,服务端和客户端都能走 Spring Boot 的自动装配逻辑。

你只需要把 gRPC 服务注册成 Spring Bean,Boot 就能帮你发现它,再把服务挂到 gRPC Server 上。

下面我用一个完整的实战案例,带你从头跑通一个 gRPC 服务。

6.1 在Spring Initializr中创建项目

打开 Spring Initializr,直接勾选 gRPC 依赖。

生成项目并解压,用 IDE 打开即可。

6.2 添加依赖

Spring Boot 4.1 提供了四个官方的 gRPC starter,版本由 Spring Boot 的 BOM 统一管理:

xml 复制代码
<!-- 服务端依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-grpc-server</artifactId>
</dependency>

<!-- 客户端依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-grpc-client</artifactId>
</dependency>

<!-- 测试依赖(服务端) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-grpc-server-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- 测试依赖(客户端) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-grpc-client-test</artifactId>
    <scope>test</scope>
</dependency>

依赖说明 :Spring Boot 4.1 的 starter 是官方出品,不再是第三方 starter。

spring-grpc 项目作为编程模型提供 @GrpcService@ImportGrpcClientsGrpcChannelFactory,会通过传递依赖引入。

不需要在 build 中显式声明 spring-grpc

6.3 定义 Proto 文件

src/main/proto/ 目录下创建 hello.proto

protobuf 复制代码
syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.example.demo.proto";  // 改成你自己的包名
option java_outer_classname = "HelloWorldProto";

service Simple {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
    rpc StreamHello (HelloRequest) returns (stream HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

注意 :一定要把 java_package 改成你自己项目的包名。

6.4 生成 Stub 代码

执行 Maven 构建:

bash 复制代码
./mvnw clean package

生成的文件在以下目录:

  • Maven:target/generated-sources/protobuf/grpc-javatarget/generated-sources/protobuf/java
  • Gradle:build/generated/source/proto/main/grpcbuild/generated/source/proto/main/java

在 IntelliJ IDEA 中,右键这两个文件夹 → Mark Directory AsGenerated Source Root

6.5 实现服务端

创建一个服务实现类,继承生成的基类,并加上 @GrpcService 注解:

java 复制代码
import io.grpc.stub.StreamObserver;
import org.springframework.grpc.server.service.GrpcService;
import com.example.demo.proto.SimpleGrpc;
import com.example.demo.proto.HelloRequest;
import com.example.demo.proto.HelloReply;

@GrpcService
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {

    @Override
    public void sayHello(HelloRequest request,
                         StreamObserver<HelloReply> responseObserver) {
        String name = request.getName();
        HelloReply reply = HelloReply.newBuilder()
            .setMessage("Hello ==> " + name)
            .build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }

    @Override
    public void streamHello(HelloRequest request,
                            StreamObserver<HelloReply> responseObserver) {
        // 流式返回多个消息
        for (int i = 0; i < 5; i++) {
            HelloReply reply = HelloReply.newBuilder()
                .setMessage("Hello " + request.getName() + " (message " + i + ")")
                .build();
            responseObserver.onNext(reply);
        }
        responseObserver.onCompleted();
    }
}

@GrpcService 的作用和 @Service@RestController 类似,任何 Spring 开发者都会感到熟悉。

6.6 配置服务端

application.yml 中配置:

yaml 复制代码
spring:
  grpc:
    server:
      port: 9090

默认就是 9090 端口,用的是 Netty 传输。反射默认注册,用 grpcurl 配合 -plaintext 就能直接测试。

6.7 启动服务端

直接运行 Spring Boot 主类。启动后,gRPC 服务器会在 9090 端口上自动启动。

grpcurl 测试一下:

bash 复制代码
grpcurl -plaintext -d '{"name":"World"}' localhost:9090 Simple/SayHello

预期返回:

json 复制代码
{
  "message": "Hello ==> World"
}

6.8 实现客户端

Step 1:添加客户端依赖

在客户端的 pom.xml 中添加:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-grpc-client</artifactId>
</dependency>

Step 2:在启动类上添加 @ImportGrpcClients

java 复制代码
import org.springframework.grpc.client.ImportGrpcClients;

@ImportGrpcClients
@SpringBootApplication
public class GrpcClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(GrpcClientApplication.class, args);
    }
}

@ImportGrpcClients 会触发 gRPC stub 的自动创建。

Step 3:配置客户端

application.yml 中配置:

yaml 复制代码
spring:
  grpc:
    client:
      channel:
        order-service:
          target: static://localhost:9090

对于生产环境,不要硬编码 static://localhost:9090,应该使用服务发现或环境变量驱动:

yaml 复制代码
target: static://${ORDER_GRPC_HOST:localhost}:${ORDER_GRPC_PORT:9090}

Step 4:注入并使用 Stub

java 复制代码
import org.springframework.grpc.client.GrpcClient;
import org.springframework.stereotype.Component;

@Component
public class HelloClient {
    
    @GrpcClient("order-service")
    private SimpleGrpc.SimpleBlockingStub simpleStub;
    
    public String sayHello(String name) {
        HelloRequest request = HelloRequest.newBuilder()
            .setName(name)
            .build();
        HelloReply response = simpleStub.sayHello(request);
        return response.getMessage();
    }
}

6.9 统一异常处理(@GrpcAdvice)

Spring Boot 4.1 还引入了 @GrpcAdvice 注解,用于集中处理 gRPC 异常:

java 复制代码
import io.grpc.Status;
import org.springframework.grpc.server.exception.GrpcExceptionHandler;
import org.springframework.grpc.server.exception.GrpcExceptionHandlerAdvice;

@GrpcExceptionHandlerAdvice
public class GlobalGrpcExceptionHandler {
    
    @GrpcExceptionHandler(IllegalArgumentException.class)
    public Status handleBadRequest(IllegalArgumentException ex) {
        return Status.INVALID_ARGUMENT
            .withDescription(ex.getMessage())
            .asRuntimeException();
    }
}

6.10 Spring Boot 4.1 gRPC 完整流程

6.11 补充说明:配置属性的变化

如果你之前用的是 spring-grpc 1.0.x(Spring Boot 4.0),迁移到 Spring Boot 4.1 时需要注意配置属性的变化:

spring-grpc 1.0 (Boot 4.0) Spring Boot 4.1
spring.grpc.client.channels.<name>.address spring.grpc.client.channel.<name>.target
spring.grpc.client.channels.<name>.default-deadline spring.grpc.client.channel.<name>.default.deadline
spring.grpc.server.address(host:port 组合) spring.grpc.server.address(仅地址)+ spring.grpc.server.port
spring.grpc.server.health.actuator.* spring.grpc.server.health.*

关键点:Spring Boot 4.1 的 Gradle 插件不再自动配置 gRPC------只有当 Protobuf 插件被应用时才会触发配置。如果用 Maven,不受这个影响。

七、为什么越来越多人用gRPC?

7.1 性能优势是硬道理

gRPC最直观的优势就是性能。一项2026年的基准测试显示,在相同的业务逻辑下,gRPC的吞吐量比REST高出107%,延迟降低48%

有团队实测,在一项包含1000次请求的测试中,REST的平均延迟约250ms,而gRPC仅约25ms。

在高并发场景下,gRPC通过HTTP/2多路复用避免了频繁建立连接的开销,吞吐量优势会非常明显。

另一项针对API协议的对比研究也印证了这一点:gRPC在处理小消息负载时延迟最短,非常适合微服务间的内部通信。

7.2 流式通信,不止一问一答

gRPC支持四种通信模式:

模式 说明 典型场景
Unary 一问一答 普通RPC调用
Server Streaming 客户端发一个请求,服务端流式返回 实时日志、事件推送
Client Streaming 客户端流式发送,服务端一次返回 文件上传、批量数据提交
Bidirectional Streaming 双向流式通信 实时聊天、AI流式对话

这种灵活性让gRPC不仅适合传统的请求-响应场景,也非常适合实时数据交互。特别是双向流模式,在AI Agent需要实时交互的场景下尤其有价值。

7.3 跨语言,一份Proto走天下

用Proto定义好接口后,可以用protoc生成Java、Go、Python、C++、Node.js、C# 等语言的代码。同一个服务,客户端和服务器可以用不同的语言实现,底层通信完全一致

这在多语言技术栈的团队里尤其有价值。

现在很多公司的微服务架构里,不同的服务可能用不同的语言写------网关用Java,数据处理用Python,性能敏感的服务用Go。

gRPC让跨语言的服务调用变得跟同语言一样简单。

7.4 强类型契约,接口即文档

Proto文件本身就是一个精确的接口契约

不用额外写文档,不用手动维护API规范。Proto文件长什么样,接口就长什么样。

任何一方修改了Proto,重新生成代码就能发现不兼容的地方。

八、gRPC的挑战与注意事项

gRPC不是银弹,它也有自己的短板。

8.1 浏览器兼容性

浏览器原生不支持gRPC,需要通过gRPC-Web代理来转发。这增加了架构的复杂度,也让gRPC在对外暴露的API场景中不如REST方便。

8.2 调试难度

gRPC使用Protobuf二进制格式,不像JSON那样可以直接在浏览器里查看。排查问题需要专门的工具,比如grpcurl、BloomRPC等。

8.3 学习曲线

使用gRPC需要学习Proto语法、代码生成、HTTP/2等一系列新概念。对于习惯了REST API的团队,切换需要一定的学习成本。

8.4 K8s负载均衡适配

gRPC使用长连接,在Kubernetes中默认的Service负载均衡策略会导致连接"粘滞"。需要额外的配置,比如Headless Service + 客户端轮询策略,或者引入Service Mesh(如Linkerd/Istio)。相比之下,REST API的扩容和负载均衡要简单得多。

九、优缺点对比

对比维度 REST + JSON gRPC
数据格式 文本(JSON) 二进制(Protobuf)
传输协议 HTTP/1.1 HTTP/2
连接复用 ✅ 多路复用
序列化大小 基准 减少60%-80%
序列化速度 基准 提升3-5倍
跨语言支持 极好
流式通信 ✅ 双向流
浏览器支持 ✅ 原生 ⚠️ 需gRPC-Web
调试难度 简单 较高
学习曲线 中等
适用场景 对外API 内部微服务

十、适用场景

场景 推荐程度 理由
微服务间通信 ✅✅✅ 强烈推荐 性能优势最明显,跨语言支持好
多语言混合团队 ✅✅✅ 强烈推荐 一份Proto生成所有语言代码
流式数据交互 ✅✅✅ 强烈推荐 原生支持Server/Client/Bidirectional Streaming
AI Agent实时通信 ✅✅✅ 强烈推荐 双向流模式适合Agent实时交互
高性能网关内部路由 ✅✅ 推荐 内部转发性能远优于REST
对外公开API ⚠️ 需评估 浏览器兼容性差
简单CRUD应用 ⚠️ 需评估 REST足够,过度设计
前端直接调用 ❌ 不推荐 需要gRPC-Web代理

更多项目实战在Java突击队网:susan.net.cn/project

十一、写在最后

回到最初的问题:为什么越来越多人用gRPC?

答案其实不复杂------因为它用HTTP/2解决了HTTP/1.1的并发瓶颈,用Protobuf解决了JSON的传输和解析开销。

同一个业务逻辑,gRPC能跑出REST两到三倍的吞吐量,响应时间降低一个数量级。

在微服务架构中,服务间调用的频率极高,这个差距会被进一步放大。

当然,gRPC也有自己的短板------浏览器兼容性、调试难度、学习曲线。

它不是来取代REST的,而是REST在内部服务间通信场景下的替代方案。

我的建议是:如果你在做对外API,REST依然是更好的选择。

但如果你的系统是微服务架构,服务之间需要高频通信,或者你的团队有多个语言的技术栈------gRPC值得你花一个下午跑一遍官方示例

一条连接复用所有请求,一份Proto生成所有语言代码。你会发现,服务间通信可以这么高效。

相关推荐
MacroZheng1 小时前
程序员看文档神器,装上它,看Spring官方文档就一目了然了!
java·人工智能·后端
想风1 小时前
Claude Code 实用技巧工作坊 —— Boris Cherny
前端·后端·github
长栎1 小时前
Java 17 的模式匹配,正在杀死访问者模式
后端
风曳丷1 小时前
# 03|恶意文本如何一路摸到工具按钮
后端
2501_915918411 小时前
Rust 程序抓包解密,rustls 不认系统证书的几种办法
开发语言·后端·网络协议·ios·adb·https·rust
深漂的华哥1 小时前
Ruoyi-Vue-Plus(V5.6.2) 开发环境搭建
java·前端·spring boot·后端·spring·ruoyi
小月土星1 小时前
构建 AI 应用的后端,最主流的选择就是 Python + FastAPI。
后端·fastapi
薛定谔的悦1 小时前
我是被现实教育之后,才明白 Skill 到底该怎么写
后端
掘金者阿豪1 小时前
你的服务器做过体检么?一份真实的 CentOS 高并发服务器体检与优化实录
后端