1 为什么选择 gRPC
gRPC是一种高性能的先进RPC(远程过程调用)框架,是开源的,并且兼容不同的环境。它使用协议缓冲区作为消息交换格式。
不同语言中的 gRPC 客户端和服务器通信示例
gRPC可以让客户端代码像调用本地对象方法一样轻松地调用位于不同计算机上的服务器应用程序的方法,从而简化了开发分布式应用程序和服务的过程。
2 gRPC VS REST(简要比较)
主要的区别在于:
- 协议:gRPC 使用 HTTP/2,但通常 REST 使用 HTTP/1.1(下面进行比较)。简而言之,HTTP/2 比 HTTP/1.1 快得多,效率更高。
- 数据格式:REST 通常使用 JSON,而 gRPC 使用协议缓冲区。
- API 格式:gRPC 的 API 范式是 RPC(远程过程调用),而 REST 基于表现层状态转移模型。
- 流式传输:虽然 gRPC 支持双向流式传输,但 REST 仅限于请求-响应模式。
图片
3 项目结构
- grpc-proto:Demo 项目的 gRPC proto 文件
- grpc-server:Spring Boot 中的 gRPC 服务器项目
- grpc-client:Spring Boot 中的 gRPC 客户端项目
4 grpc-proto 项目
syntax = "proto3";
package com.imertyildiz.grpcproto;
option java_multiple_files = true;
message HelloWorldRequest{
string requestMessage = 1;
string clientName = 2;
}
message HelloWorldResponse{
string responseMessage = 1;
}
service HelloWorldService {
rpc HelloWorld(HelloWorldRequest) returns (HelloWorldResponse);
}
这里创建了一个简单的 .proto 文件,包括服务、方法和消息定义。
使用 protobuf-maven-plugin 将服务器和客户端代码生成集成到 Maven 构建系统中。
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-maven-plugin.version}</version>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:${io.grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<id>client-code-generation</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>server-code-generation</id>
<goals>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
结果是,当项目通过 mvn package 命令编译时,服务器和客户端代码都会生成。
但是,我们应该将项目 JAR 安装到本地 Maven 仓库中,以便 grpc-client 和 grpc-server 项目可以包含此项目 JAR。
因此,我们应该调用 mvn install 命令。
mvn install 后生成的源代码
我们将在 grpc-server 和 grpc-client 项目中使用的服务和请求对象已创建并安装在本地 Maven 仓库中。
5 grpc-server 项目
使用 grpc-spring-boot-starter 的服务器库,它通过注解简化了客户端和服务器的定义。下面是 proto 项目和 starter 库的一部分 pom.xml。
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.imertyildiz</groupId>
<artifactId>grpcproto</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
该库在应用程序启动时启动 gRPC 服务器,并监听端口:9090(默认值)。如果我们想更改端口,可以通过 application.properties 文件更改,例如:grpc.server.port=8000。
当我们为扩展自动生成的 gRPC 服务定义的类使用 @GrpcService 注解时,该库会将服务注册到 gRPC 服务器上。
下面是实现代码:
package com.imertyildiz.grpcserver.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.imertyildiz.grpcproto.HelloWorldRequest;
import com.imertyildiz.grpcproto.HelloWorldResponse;
import com.imertyildiz.grpcproto.HelloWorldServiceGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
public class GreeterServer extends HelloWorldServiceGrpc.HelloWorldServiceImplBase {
private static final Logger logger = LoggerFactory.getLogger(GreeterServer.class);
@Override
public void helloWorld(HelloWorldRequest request, StreamObserver<HelloWorldResponse> responseObserver) {
HelloWorldResponse setResponseMessage = HelloWorldResponse.newBuilder()
.setResponseMessage("Hello " + request.getClientName() + " !!!").build();
logger.info(String.format("%1s sent a message: %1s", request.getClientName(),request.getRequestMessage()));
responseObserver.onNext(setResponseMessage);
responseObserver.onCompleted();
}
}
由于此 POC 只记录了来自请求的客户端名称,因此服务器只是记录了传入消息。
6 grpc-client 项目
同样,使用 grpc-spring-boot-starter 的客户端库。我们通过 @GrpcClient("grpc-server") 定义 gRPC 客户端。该注解带有命名目标服务器的参数。我们应该在 application.properties 文件中配置目标服务器地址。创建的文件如下所示:
grpc.client.grpc-server.address=static://localhost:8000
grpc.client.grpc-server.negotiation-type=plaintext
grpc.server.port=8001
@GrpcClient 注解中的目标服务器名称参数在这里用于配置地址和端口信息。
客户端代码如下:
package com.imertyildiz.grpcclient.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.imertyildiz.grpcproto.HelloWorldRequest;
import com.imertyildiz.grpcproto.HelloWorldResponse;
import com.imertyildiz.grpcproto.HelloWorldServiceGrpc.HelloWorldServiceBlockingStub;
import net.devh.boot.grpc.client.inject.GrpcClient;
@Service
public class GreeterClient {
private static final Logger logger = LoggerFactory.getLogger(GreeterClient.class);
@GrpcClient("grpc-server")
private HelloWorldServiceBlockingStub helloWorldServiceStub;
public void sayHello(String sender, String message) {
HelloWorldRequest helloWorldRequest = HelloWorldRequest.newBuilder().setClientName(sender)
.setRequestMessage(message).build();
HelloWorldResponse helloWorldResponse = this.helloWorldServiceStub.helloWorld(helloWorldRequest);
logger.info(String.format("Server sent a response: %1s", helloWorldResponse.getResponseMessage()));
}
}
在我们为自动生成的服务注释 BlockingStub 对象之后,它就可以使用了。我们发送消息并获取响应,然后记录响应。
从主函数中触发请求函数。代码如下:
@SpringBootApplication
public class GrpcClientApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(GrpcClientApplication.class, args);
GreeterClient greeterClientService = applicationContext.getBean(GreeterClient.class);
greeterClientService.sayHello("Client", "Hello Server !!!");
}
}
我们来看看结果:
首先启动了服务器,然后启动了客户端。结果如下:
gRPC 服务器的日志
gRPC 客户端的日志
总的来说,本文创建了简单的 Demo 项目,展示了在 Spring Boot、Java 中 gRPC 客户端和服务器的实现和通信,以及通过 protobuf 编译器生成客户端和服务器代码的单独 proto 项目。