文章目录
- [1 gRPC架构](#1 gRPC架构)
-
- [1.1 简介](#1.1 简介)
- [1.2 gRPC通信流程](#1.2 gRPC通信流程)
- [1.3 实际操作](#1.3 实际操作)
-
- [1.3.1 pom.xml](#1.3.1 pom.xml)
- [1.3.2 proto](#1.3.2 proto)
-
- [1.3.2.1 文件讲解](#1.3.2.1 文件讲解)
- [1.3.2.2 编译生成源码](#1.3.2.2 编译生成源码)
- [1.3.3 业务类](#1.3.3 业务类)
- [1.3.4 客户端 @GrpcClient](#1.3.4 客户端 @GrpcClient)
-
- [1.3.4.1 配置文件](#1.3.4.1 配置文件)
- [1.3.4.2 对应客服端实现](#1.3.4.2 对应客服端实现)
- [1.3.5 测试](#1.3.5 测试)
-
- [1.3.5.1 用Bruno测试](#1.3.5.1 用Bruno测试)
- [1.3.5.2 服务端调用](#1.3.5.2 服务端调用)
1 gRPC架构
1.1 简介
有些小伙伴在工作中构建微服务架构时,可能会遇到服务间通信性能瓶颈。
gRPC正是为了解决高性能分布式系统通信而设计的。
gRPC基于HTTP/2和Protocol Buffers,提供以下核心特性:
- 双向流:支持客户端流、服务器流和双向流
- 流量控制:基于HTTP/2的流控制
- 多路复用:单个连接上并行多个请求
- 头部压缩:减少传输开销
优缺点:
- 优点:
高性能,二进制编码
支持双向流式通信
强类型接口定义
多语言支持
内置认证、负载均衡等 - 缺点:
浏览器支持有限(需要gRPC-Web)
可读性差,需要工具调试
学习曲线较陡
生态系统相对较小
1.2 gRPC通信流程

1.3 实际操作
1.3.1 pom.xml
xml
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>2.15.0.RELEASE</version> <!-- 2025 年最新稳定版 -->
</dependency>
<!-- 序列化工具类 -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.21.8</version>
</dependency>
<!-- 必须加这个插件!否则 .proto 文件不会被编译 -->
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<!-- 1. 编译 proto -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!-- 将版本改为与 grpc-spring-boot-starter 兼容的版本 -->
<protocArtifact>com.google.protobuf:protoc:3.24.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:1.58.0:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
端口配置:
yml
grpc:
server:
port: 9090
1.3.2 proto
1.3.2.1 文件讲解
需要创建 src/main/proto 目录 ,比如:user.proto
go
//声明使用 Protocol Buffers 第 3 版语法
syntax = "proto3";
//让每个 message 和 service 都生成独立的 Java 文件,而不是全部塞到一个大类里
option java_multiple_files = true;
//指定生成的 Java 类放在哪个包下
option java_package = "com.example.grpc";
//当 java_multiple_files=false 时才生效,指定包裹所有消息的外层类名
// option java_outer_classname = "UserProto";
//定义一个 gRPC 服务名为 UserService
service UserService {
// 一元调用
rpc GetUser (UserRequest) returns (UserResponse);
// 服务端流式 RPC 客户端发一次请求,服务端可以返回多次数据
rpc ListUsers (Empty) returns (stream UserResponse);
}
//Google 官方定义的空消息(相当于 void) 可以不定义,直接 import
message Empty {}
message UserRequest {
int64 id = 1;
}
message UserResponse {
int64 id = 1;//字段编号 编号从 1 开始,不能重复
string userName = 2;
string phoneNumer = 3;
string remark = 4;
// int32 age = 5 [deprecated=true]; // 标记废弃
// reserved 5; // 彻底保留编号
// reserved "oldField", "legacyField"; // 保留字段名
}
| 模式 | proto 写法 | 场景 |
|---|---|---|
| 一元 | rpc Call(req) returns (resp); | 最常见 |
| 服务端流式 | rpc Call(req) returns (stream resp); | 服务器推送多条数据 |
| 客户端流式 | rpc Call(stream req) returns (resp); | 客户端上传大文件、分批上传 |
| 双向流式 | rpc Call(stream req) returns (stream resp); | 聊天室、实时协同 |
1.3.2.2 编译生成源码
选中项目执行 mvn clean compile 可以分开执行也可以一次执行完这时候在target下有生成的源码
如下所示:

这时候业务代码引入不到这里编译后生成的源码,有两种解决方法,修改pom.xml 或者 设置idea
修改pom.xml就是添加插件
xml
<!-- 2. 告诉 Maven 这些是源码 如果还是不生效,不能引入,那么删除此处然后重新引入下即可-->
<!-- 该插件只是添加编译得代码到源码目录中,可以放在父项目中 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals><goal>add-source</goal></goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/protobuf/java</source>
<source>${project.build.directory}/generated-sources/protobuf/grpc-java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
如果不想添加插件可以直接修改idea设置:依次右键选中grpc-java和java俩个文件夹,选择Mark Directory as,再选择 Generated Sources Root即可

1.3.3 业务类
java
@GrpcService
public class ProtoUserServiceImpl extends UserServiceGrpc.UserServiceImplBase{
@Autowired
private UserService userService; // 你的业务层
@Override
public void getUser(UserRequest request, StreamObserver<UserResponse> responseObserver) {
UserEntity entity = userService.getById(request.getId());
UserResponse response = UserResponse.newBuilder()
.setId(entity.getId())
.setUserName(entity.getUserName())
.setPhoneNumer(entity.getPhoneNumer())
.setRemark(entity.getRemark() != null ? entity.getRemark() : "")
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
@Override
public void listUsers(Empty request, StreamObserver<UserResponse> responseObserver) {
userService.list().forEach(entity -> {
UserResponse response = UserResponse.newBuilder()
.setId(entity.getId())
.setUserName(entity.getUserName())
.setPhoneNumer(entity.getPhoneNumer())
.setRemark(entity.getRemark() != null ? entity.getRemark() : "")
.build();
responseObserver.onNext(response);
});
responseObserver.onCompleted();
}
}
1.3.4 客户端 @GrpcClient
1.3.4.1 配置文件
yml
grpc:
server:
port: 9091
client: # 客户端配置
grpc-one: # 客户端配置 服务的名称
address: 'static://localhost:9090' # static:// 固定地址
# 生产可以用 dns:/// 或者 discovery:/// 负载均衡
# address: 'dns:///user-service.prod:9090,user-service.prod:9091'
enableKeepAlive: true
keepAliveTime: 60s
keepAliveTimeout: 20s
keepAliveWithoutCalls: true
# 生产换成 tls
negotiationType: plaintext
# 最大连接
maxInboundMessageSize: 10MB
# 重试策略(推荐开启)
enableRetries: true
maxRetryAttempts: 3
1.3.4.2 对应客服端实现
java
@Slf4j
@Service
public class GrpcOneClientService {
// 这里的服务名要和配置文件中的服务名一致
@GrpcClient("grpc-one")
private UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub;
/**
* 调用grpc-one服务获取用户信息
*/
public void getUser(Long userId) {
try {
UserRequest request = UserRequest.newBuilder().setId(userId).build();
UserResponse user = userServiceBlockingStub.getUser(request);
String jsonResponse = JsonFormat.printer().omittingInsignificantWhitespace().print(user);
log.info("得到的user:{} ",jsonResponse);
} catch (Exception e) {
log.error("RPC failed: " + e.getMessage(),e);
}
}
}
1.3.5 测试
1.3.5.1 用Bruno测试
选测gRPC然后只用填地址即可,然后再选中要测试的proto文件

1.3.5.2 服务端调用
如果用于服务端交互,客户端和服务端必须有相同的*.proto文件,gRPC 是契约优先(contract-first),proto 就是契约,双方必须完全一致
一般服务是用 Maven 多模块 + 共享 proto 模块 实现
txt
parent-project
├── grpc-api ← 只放 .proto 文件 + pom.xml
├── user-service ← 服务端实现(依赖 grpc-api)
└── order-service ← 客户端调用(也依赖 grpc-api)
主要客户端操作类,示例说明:
UserServiceGrpc.UserServiceBlockingStub:阻塞式同步调用
调用gRPC服务时会阻塞当前线程,直到收到响应或超时,方法调用会直接返回结果或抛出异常
适用于简单的同步调用场景UserServiceGrpc.UserServiceStub:异步非阻塞调用
调用gRPC服务时不会阻塞当前线程,通过回调方式处理响应结果
适用于需要高性能、高并发的异步调用场景
客户端实现
main方法简单测试
java
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("127.0.0.1", 9090)
.usePlaintext() // 开发环境 不使用SSL/TLS加密
.build();
UserServiceGrpc.UserServiceBlockingStub blockingStub = UserServiceGrpc.newBlockingStub(channel);
UserRequest request = UserRequest.newBuilder().setId(1L).build();
UserResponse user = blockingStub.getUser(request);
// 使用 Protobuf 自带的 JsonFormat
try {
String jsonResponse = JsonFormat.printer().omittingInsignificantWhitespace().print(user);
System.out.println("得到的user: " + jsonResponse);
} catch (Exception e) {
System.err.println("JSON序列化失败: " + e.getMessage());
}
channel.shutdown();
}
结合服务的测试
java
public class UserServiceClient {
private final UserServiceGrpc.UserServiceBlockingStub blockingStub;
private final UserServiceGrpc.UserServiceStub asyncStub;
// 用 ManagedChannel(自动关闭)
public UserServiceClient(String host,int port) {
this(ManagedChannelBuilder.forAddress(host, port)
.usePlaintext() // 开发环境 不使用SSL/TLS加密
.build());
}
// 构造函数注入 Channel(可被单元测试 mock)
public UserServiceClient(Channel channel) {
this.blockingStub = UserServiceGrpc.newBlockingStub(channel);
this.asyncStub = UserServiceGrpc.newStub(channel);
}
// 一元调用(最常用)
public UserResponse getUser(String userId) {
GetUserRequest request = GetUserRequest.newBuilder()
.setUserId(userId)
.build();
return blockingStub
.withDeadlineAfter(5, TimeUnit.SECONDS) // 推荐加超时
.getUser(request);
}
// 服务端流式(推荐返回 Iterator,调用方用 for 循环)
public Iterator<UserResponse> listUsers(int pageSize) {
Empty request = Empty.newBuilder().build();
return blockingStub
.withDeadlineAfter(30, TimeUnit.SECONDS)
.listUsers(request); // blockingStub 直接返回 Iterator!
}
// 异步流式(如果要异步)
public void streamUsers(Consumer<UserResponse> consumer) {
StreamUsersRequest request = StreamUsersRequest.newBuilder()
.setPageSize(10)
.build();
asyncStub.streamUsers(request, new StreamObserver<UserResponse>() {
@Override
public void onNext(UserResponse response) {
consumer.accept(response);
}
@Override
public void onError(Throwable t) {
System.err.println("Error in streaming: " + t.getMessage());
}
@Override
public void onCompleted() {
System.out.println("Stream completed");
}
});
}
}