《RPC实战与核心原理》学习笔记Day5
06 | RPC实战:剖析gRPC源码,动手实现一个完整的RPC
我们通过动态代理技术,屏蔽RPC调用的细节,从而让使用者能够面向接口编程。
什么是gRPC?
gRPC是由Google开发并且开源的一款高性能、跨语言的RPC框架,当前支持C、Java和Go语言,当前Java版本最新Release版是1.51.3。
什么是protobuf?
protocol buffers是一种语言无关、平台无关、可扩展的序列化结构数据方法,可用于通信协议、数据存储等。
我们可以定义数据结构,然后使用特殊生成的源代码在各种数据流中使用各种语言进行编写和读取数据结构,也可以更新数据结构。
protobuf的三大特点:
- 语言无关,平台无关
- 灵活、高效
- 扩展性、兼容性好
下面我们来看一下如何使用gRPC。
首先我们需要安装protobuf,如果你使用Mac电脑,那么可以运行下面的命令来安装protobuf。
brew install protobuf
执行成功后,可以运行下面的命令来查看相关版本信息。
protoc --version
libprotoc 3.21.9
然后我们来创建下面的proto文件hello.proto。
syntax = "proto3";
option java_generic_services = true;
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
我们可以使用下面的命令来自动生成Java代码文件。
protoc ./hello.proto --java_out=./
命令执行完成后,会在当前目录下,生成代码文件,对应的目录结构如下。
tree .
.
├── hello.proto
└── io
└── grpc
└── examples
└── helloworld
├── Greeter.java
├── Hello.java
├── HelloReply.java
├── HelloReplyOrBuilder.java
├── HelloRequest.java
└── HelloRequestOrBuilder.java
上面就完成了proto文件转换的过程。
接下来,我们看怎么在Java工程中完整的使用gRPC。
我们创建一个空的Maven工程,在pom.xml中引用必要的依赖以及protobuf Maven插件, 完整的pom.xml内容如下:
<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>sample.grpc</groupId>
<artifactId>grpc-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<grpc.version>1.52.1</grpc.version>
<protobuf.version>3.21.9</protobuf.version>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.29.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.29.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.29.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
</dependencies>
<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.11.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.29.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
然后将上面的hello.proto文件复制到src/main/proto目录下面创建hello.proto目录下, 这个目录是固定的。
pom.xml文件准备好以后,我们来运行下面的工程编译命令。
maven compile
编译结束后,会在target/generated-sources目录下维护自动生成的代码,目录结构如下。
tree target/generated-sources
target/generated-sources
└── protobuf
├── grpc-java
│ └── io
│ └── grpc
│ └── examples
│ └── helloworld
│ └── GreeterGrpc.java
└── java
└── io
└── grpc
└── examples
└── helloworld
├── Greeter.java
├── Hello.java
├── HelloReply.java
├── HelloReplyOrBuilder.java
├── HelloRequest.java
└── HelloRequestOrBuilder.java
11 directories, 7 files
接下来,我们来编写服务器端,代码如下。
package io.grpc.examples.helloworld;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
public class HelloWorldServer {
private Server server;
private void start() throws IOException {
/* The port on which the server should run */
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
try {
HelloWorldServer.this.stop();
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
System.err.println("*** server shut down");
}
});
}
private void stop() throws InterruptedException {
if (server != null) {
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
}
}
/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldServer server = new HelloWorldServer();
server.start();
server.blockUntilShutdown();
}
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
System.out.println("=====server=====");
System.out.println("server: Hello " + req.getName());
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}
编写完成后,直接运行,这样会启动一个Server,端口是50051。
最后,我们来编写客户端,代码如下。
package io.grpc.examples.helloworld;
import java.util.concurrent.TimeUnit;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
public class HelloWorldClient {
private final GreeterGrpc.GreeterBlockingStub blockingStub;
/** Construct client for accessing HelloWorld server using the existing channel. */
public HelloWorldClient(Channel channel) {
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
/** Say hello to server. */
public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
return;
}
System.out.println("Greeting: " + response.getMessage());
}
public static void main(String[] args) throws Exception {
String user = "hahahahaha";
// Access a service running on the local machine on port 50051
String target = "localhost:50051";
// Allow passing in the user and target strings as command line arguments
if (args.length > 0) {
if ("--help".equals(args[0])) {
System.err.println("Usage: [name [target]]");
System.err.println("");
System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
System.err.println(" target The server to connect to. Defaults to " + target);
System.exit(1);
}
user = args[0];
}
if (args.length > 1) {
target = args[1];
}
ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
.usePlaintext()
.build();
try {
HelloWorldClient client = new HelloWorldClient(channel);
client.greet(user);
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
}
当我们多次运行客户端程序后,我们可以在服务器端的控制台上,看到如下输出。
=====server=====
server: Hello hahahahah
=====server=====
server: Hello hahahahah
=====server=====
server: Hello hahahahah
这说明,客户端和服务器端在gRPC框架下面通信正常。