gRPC的使用
文章目录
grpc是由google开源的rpc(Remote Procedure Call,远程程序调用)框架,rpc用于跨平台、跨语言传输数据。
grpc采用的是http2协议。
使用
项目结构
分别为client(客户端)、grpc(gRPC 服务)、server(服务端)。
grpc
首先是grpc。
引入依赖
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.38.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.38.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.38.0</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>
因为需要依赖于maven和protobuf进行编译,所以添加maven插件protobuf-maven-plugin,这个插件会将 .proto
文件编译成java代码,但是这个插件是在idea的window平台下使用,在linux下需要别的配置https://github.com/grpc/grpc-java/blob/master/COMPILING.md。依旧是在pom.xml文件添加:
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</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.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.38.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
总的pom.xml文件为:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>grpc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>grpc</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.38.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.38.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.38.0</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>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</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.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.38.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
在src/mian目录下增加一个proto目录,并新建一个.proto文件类型的文件。
定义proto文件
todo 数据类型
//.proto文件
//定义proto的语法
syntax="proto3";
option java_multiple_files = true;
//定义java文件放置在哪个包中
option java_package = "com.example.grpc.api";
option java_outer_classname = "HelloService";
package com.example.grpc.api;
//定义服务
service Hello{
//定义一个rpc方法,客户端请求参数为HelloRequest,服务端返回数据为HelloResponse
rpc SayHello(HelloRequest) returns(HelloResponse){}
}
//定义HelloRequest请求参数
message HelloRequest{
//string为数据类型;name为变量名字;1为数字标识,用来标识字段,每个结构体中唯一,不然会报错
string name=1;
}
//定义HelloResponse返回参数
message HelloResponse{
string name=1;
int32 age=2;
}
编译
接下来编译proto文件。在maven的Lifecycle下找到compile,点击进行编译。
能看到编译出了java文件
被client和server端依赖
都在client和server的pom.xml文件中添加grpc:
<dependency>
<groupId>com.example</groupId>
<artifactId>grpc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>1.27.2</version>
<scope>compile</scope>
</dependency>
client
定义一个客户端。
添加一个配置文件,rpc需要运行在一个单独的端口:
spring:
application:
name: client
rpc:
client:
port: 11111
server:
address: localhost
port: 11112
读取配置文件:
@Configuration
public class RPCConfig {
@Value("${rpc.client.port}")
private int clientPort;
@Value("${rpc.server.address}")
private String serverAddress;
@Value("${rpc.server.port}")
private int serverPort;
public int getClientPort() {
return clientPort;
}
public String getServerAddress() {
return serverAddress;
}
public int getServerPort() {
return serverPort;
}
}
客户端向服务器发起请求:
@Component
public class Client {
@Autowired
private RPCConfig rpcConfig;
public void sayHello(){
ManagedChannel managedChannel= ManagedChannelBuilder.forAddress(rpcConfig.getServerAddress(),rpcConfig.getServerPort()).usePlaintext().build();
try {
HelloGrpc.HelloBlockingStub helloService=HelloGrpc.newBlockingStub(managedChannel);
HelloRequest helloRequest= HelloRequest.newBuilder()
.setName("name")
.build();
HelloResponse helloResponse=helloService.sayHello(helloRequest);
}finally {
managedChannel.shutdown();
}
}
}
定义一个服务端
添加依赖:
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>1.27.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>grpc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
定义一个接受信息的service:
public class SayHelloService extends HelloGrpc.HelloImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
System.out.println("接受到的信息为:"+request.getName());
//返回信息
HelloResponse helloResponse=null;
try {
helloResponse=HelloResponse
.newBuilder()
.setName("name2")
.setAge(11)
.build();
}catch (Exception e){
responseObserver.onError(e);
}finally {
responseObserver.onNext(helloResponse);
}
responseObserver.onCompleted();
}
}
定义配置文件,配置grpc运行的端口:
spring:
application:
name: server
rpc:
server:
port: 11112
读取配置文件:
@Configuration
public class RPCConfig {
@Value("${rpc.server.port}")
private int serverPort;
public int getServerPort() {
return serverPort;
}
}
启动一个rpc的服务,添加服务时,需要使用 addService()
先注入Service:
import io.grpc.Server;
import io.grpc.ServerBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.io.IOException;
@Configuration
public class RPCServer {
@Autowired
private RPCConfig rpcConfig;
@Autowired
private SayHelloService sayHelloService;
@PostConstruct
public void init() throws IOException, InterruptedException {
Server server=ServerBuilder
//服务启动端口
.forPort(rpcConfig.getServerPort())
//使用依赖注入的方式添加服务,调用方法时能直接使用依赖注入的方式调用其他服务。
.addService(sayHelloService)
.build()
//启动服务
.start();
server.awaitTermination(); //停止其他线程,等待请求,等待服务器被终止
}
}
server.awaitTermination()
能在没有其他任务或线程的情况下,保持程序继续运行,防止程序运行后结束。如果客户端还有其他线程或者任务,使用会导致其他线程阻塞。
测试
测试客户端与服务器是否能正常通讯。
在客户端定义一个定时器:
@Component
public class Task {
@Autowired
Client client;
@Scheduled(initialDelay = 1000,fixedDelay = 60*1000)
public void work(){
client.sayHello();
}
}
能看到服务端打印出来了信息:
接受到的信息为:name
其他
依赖注入
在 SayHelloService
中使用依赖注入方法调用其他方法。
定义一个测试的Component:
@Component
public class HelloHandler {
public void work(String name){
}
}
为SayHelloService
添加 @Component
注解,并注入 HelloHandler
:
@Component
public class SayHelloService extends HelloGrpc.HelloImplBase {
@Autowired
private HelloHandler helloHandler;
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
System.out.println("接受到的信息为:"+request.getName());
helloHandler.work(request.getName());
//返回信息
HelloResponse helloResponse=null;
try {
helloResponse=HelloResponse
.newBuilder()
.setName("name2")
.setAge(11)
.build();
}catch (Exception e){
responseObserver.onError(e);
}finally {
responseObserver.onNext(helloResponse);
}
responseObserver.onCompleted();
}
}
传值
因为null值不能被序列化,所以如果请求或者返回值的参数中存在null值,可能会导致另一端无响应。
部署在服务端
如果项目需要部署在服务端的话,为了使maven能够编译通过,有两种方式解决:
-
将编译出来的grpc.jar包以本地依赖的方式依赖到client和server。建议这个。
<dependency> <groupId>com.example</groupId> <artifactId>grpc</artifactId> <version>0.0.1-SNAPSHOT</version> <scope>system</scope> <systemPath>${project.basedir}/lib/grpc-0.0.1-SNAPSHOT.jar</systemPath> </dependency>
-
使用maven install的方式手动将grpc.jar安装到maven库
报错
-
数字标识重复
PROTOC FAILED: grpc.proto:28:21: Field number 1 has already been used in "XXX" by field "nodeId"
原因是数字标识重复使用,每个结构体的数字标识需要唯一:
-
新增服务前需要注册
io.grpc.StatusRuntimeException: UNIMPLEMENTED: Method not found: com.example.grpc.api.HelloService/sayHello
原因是需要在
RPCServer
中使用addService()
注册服务HelloService
参考
文章作者 梧桐碎梦
上次更新 2021-06-13 15:36:18