参考资料:
- https://learn.microsoft.com/zh-cn/aspnet/core/grpc/comparison?view=aspnetcore-8.0
- https://github.com/grpc/grpc-java
基础知识
gRPC是一个由google设计开发基于HTTP/2协议和Protobuf序列化协议的的高性能、多语言、通用的开源 RPC 框架。
传统rpc对比Grpc
- 传统RPC框架:使用各种协议和编码方式进行通信,可能导致跨语言通信困难、性能不佳等问题;
- GRPC框架:使用Google开发的ProtoBuf进行序列化、并通过http/2(多路复用)进行通信,通信效率更加高效;
性能
gRPC 消息使用 Protobuf(一种高效的二进制消息格式)
进行序列化。 Protobuf 在服务器和客户端上可以非常快速地序列化。 Protobuf 序列化产生的有效负载较小,这在移动应用等带宽有限的方案中很重要。
gRPC 专为 HTTP/2(HTTP 的主要版本)而设计,与 HTTP 1.x 相比,HTTP/2 具有巨大性能优势:
- 二进制组帧和压缩。 HTTP/2 协议在发送和接收方面均紧凑且高效。
- 在单个 TCP 连接上多路复用多个 HTTP/2 调用。 多路复用可消除队头阻塞。
HTTP/2 不是 gRPC 独占的。 许多请求类型(包括具有 JSON 的 HTTP API)都可以使用 HTTP/2,并受益于其性能改进。
HTTP/2
- HTTP/1.x 是超文本传输协议第1版,可读性好,但效率不高。
- HTTP/2 是超文本传输协议第2版,是一个
二进制协议
。
HTTP/2通用术语:
- Stream: 流,一个双向流,一条连接可以有多个 streams。
- Message: 逻辑上面的 request,response。
- Frame:帧,HTTP/2 数据传输的最小单位。每个 Frame 都属于一个特定的 stream。一个 message 可能由多个 frame 组成。
HTTP/2连接上传输的每个帧(frame)都关联到一个流,一个连接上可以同时有多个流, 同一个流的帧按序传输,不同流的帧交错混合传输, 客户端、服务端双方都可以建立流,流也可以被任意一方关闭。
客户端发起的流使用奇数流ID,服务端发起的使用偶数流ID。
HTTP/2 特性
新的二进制格式(Binary Format)
HTTP/1 的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同。
基于这种考虑HTTP/2的协议解析决定采用二进制格式,实现方便且健壮。
多路复用(MultiPlexing)
- HTTP/1 的request是阻塞的,如果想并发发送多个request,必须使用多个 TCP connection。
- HTTP/2 一个TCP connection可以有多个streams(最大数量由参数SETTINGS_MAX_CONCURRENT_STREAMS控制), 多个streams 并行发送不同的请求的frames。
代码生成
所有 gRPC 框架都为代码生成提供一流支持。 .proto
文件是 gRPC 开发的核心文件,它定义 gRPC 服务和消息的协定。 通过此文件,gRPC 框架生成服务基类、消息和完整的客户端。
通过在服务器和客户端之间共享 .proto 文件,可以端到端生成消息和客户端代码。 客户端的代码生成消除了客户端和服务器上的消息重复,并为你创建强类型客户端。 无需编写客户端可在具有许多服务的应用程序中节省大量开发时间。
严格规范
具有 JSON 的 HTTP API 没有正式规范。 开发人员为 URL、HTTP 谓词和响应代码的最佳格式争论不休。
gRPC 规范对 gRPC 服务必须遵循的格式进行了规定。 gRPC 消除了争论并为开发人员节省了时间,因为 gRPC 在各个平台和实现中都是一致的。
流式处理
HTTP/2 为长期实时通信流提供基础。 gRPC 为通过 HTTP/2 进行流式传输提供一流支持。
gRPC 服务支持所有流式传输组合:
- 一元(无流式传输)
- 服务器到客户端流式传输
- 客户端到服务器流式传输
- 双向流式传输
引入
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.62.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.62.2</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.62.2</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
对于基于protobuf的代码生成,您可以将您的proto文件放在src/main/proto和src/test/proto目录中,并使用适当的插件。
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.62.2:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
简单使用
syntax = "proto3"; // 语法版本
// stub选项
option java_package = "com.xxx.grpc.api";// 指定生成的Java代码的包名
option java_outer_classname = "RPCDateServiceApi";//指定生成的Java类的外部类名
option java_multiple_files = true;// 指定是否为每个定义的服务生成多个文件
// 定义包名
package com.xxx.grpc.api;
// 服务接口定义,服务端和客户端都要遵守该接口进行通信
service RPCDateService {
rpc getDate (RPCDateRequest) returns (RPCDateResponse) {}
}
// 定义消息(请求)
message RPCDateRequest {
string userName = 1;
}
// 定义消息(响应)
message RPCDateResponse {
string serverDate = 1;
}
Server端
Server server = ServerBuilder.
forPort(port)
.addService( new RPCDateServiceImpl() )
.build().start();
System.out.println( "grpc服务端启动成功, 端口=" + port );
server.awaitTermination();
Client端
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress( host, serverPort )
.usePlaintext()//这通常用于测试或内部网络中的通信,其中安全性不是主要关注点。
.build();
try {
RPCDateServiceGrpc.RPCDateServiceBlockingStub rpcDateService = RPCDateServiceGrpc.newBlockingStub( managedChannel );
RPCDateRequest rpcDateRequest = RPCDateRequest
.newBuilder()
.setUserName("XXX")
.build();
RPCDateResponse rpcDateResponse = rpcDateService.getDate( rpcDateRequest );
System.out.println( rpcDateResponse.getServerDate() );
} finally {
managedChannel.shutdown();
}