网络传输架构之gRPC讲解

文章目录

  • [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-javajava俩个文件夹,选择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");
            }
        });
    }
}
相关推荐
Warren982 小时前
Python自动化测试全栈面试
服务器·网络·数据库·mysql·ubuntu·面试·职场和发展
故渊ZY2 小时前
Java 代理模式:从原理到实战的全方位解析
java·开发语言·架构
起个名字逛街玩2 小时前
前端正在走向“工程系统化”:从页面开发到复杂产品架构的深度进化
前端·架构
欢喜躲在眉梢里2 小时前
CANN 异构计算架构实操指南:从环境部署到 AI 任务加速全流程
运维·服务器·人工智能·ai·架构·计算
云飞云共享云桌面3 小时前
无需配置传统电脑——智能装备工厂10个SolidWorks共享一台工作站
运维·服务器·前端·网络·算法·电脑
骆驼10243 小时前
手机热点和无线路由器在 IPv6 工作模式上的区别
网络·ipv6
u***u6854 小时前
云原生架构2025年趋势:Serverless与边缘计算
云原生·架构·serverless
jenchoi4134 小时前
【2025-11-23】软件供应链安全日报:最新漏洞预警与投毒预警情报汇总
网络·数据库·安全·web安全·网络安全
独行soc6 小时前
2025年渗透测试面试题总结-258(题目+回答)
网络·python·安全·web安全·渗透测试·安全狮