实用指南:网络传输架构之gRPC讲解

1 gRPC架构

1.1 简介

有些小伙伴在工作中构建微服务架构时,可能会遇到服务间通信性能瓶颈。
gRPC正是为了解决高性能分布式系统通信而设计的。

gRPC基于HTTP/2和Protocol Buffers,提供以下核心特性:

  • 双向流:支持客户端流、服务器流和双向流
  • 流量控制:基于HTTP/2的流控制
  • 多路复用:单个连接上并行多个请求
  • 头部压缩:减少传输开销

优缺点:

  • 优点:
    高性能,二进制编码
    支持双向流式通信
    强类型接口定义
    多语言支持
    内置认证、负载均衡等
  • 缺点:
    浏览器支持有限(需要gRPC-Web)
    可读性差,需要工具调试
    学习曲线较陡
    生态系统相对较小

1.2 gRPC通信流程

在这里插入图片描述

1.3 实际操作

1.3.1 pom.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>

端口配置:

grpc:
server:
port: 9090

1.3.2 proto

1.3.2.1 文件讲解

需要创建 src/main/proto 目录 ,比如:user.proto

//声明使用 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就是添加插件

<!-- 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-javajava俩个文件夹,选择Mark Directory as,再选择 Generated Sources Root即可
在这里插入图片描述

1.3.3 业务类

@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 配置文件
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 对应客服端实现
@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 模块 实现

parent-project
├── grpc-api          ← 只放 .proto 文件 + pom.xml
├── user-service      ← 服务端实现(依赖 grpc-api)
└── order-service     ← 客户端调用(也依赖 grpc-api)

主要客户端操作类,示例说明:

  • UserServiceGrpc.UserServiceBlockingStub:阻塞式同步调用
    调用gRPC服务时会阻塞当前线程,直到收到响应或超时,方法调用会直接返回结果或抛出异常
    适用于简单的同步调用场景
  • UserServiceGrpc.UserServiceStub:异步非阻塞调用
    调用gRPC服务时不会阻塞当前线程,通过回调方式处理响应结果
    适用于需要高性能、高并发的异步调用场景

客户端实现
main方法简单测试

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();
}

结合服务的测试

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");
      }
      });
      }
      }
posted @ 2025-12-20 17:24  yangykaifa  阅读(2)  评论(0)    收藏  举报