1. RPC 概述
服务间通讯HTTP 肯定不失为一种有效的方法,但此处要提起的却是另一种方法:RPC。
RPC 的英文全称是 Remote Procedure Call,即远程过程调用,简单点说,就是服务器A可以像函数调用一样调用服务器B上的方法。
其原理大致如下:
其组成结构大致如下:
可以看出,RPC 底层依然使用 TCP 等运输层协议进行网络通信,它跟 HTTP 较为相似,但比 HTTP 更便捷的是,它还封装了序列化与反序列化的功能。
或许,说到此处,还会有人疑惑:其实 HTTP 外加一层序列化与反序列化的封装后,也能实现 RPC 的功能,为何还需要使用 RPC 呢?
其实,HTTP 与 RPC 各有千秋。甚至在 gRPC 中,其底层就是 HTTP2 协议。所以,并没有孰优孰劣,只是可能 RPC 更加适用于服务与服务之间的通信,不需要 HTTP1 协议中多余的头部,序列化与反序列化的功能也更加完善,而且封装程度高、即拿即用
注意:另外,换一种说法,RPC 与 HTTP 并不在同一层面上,并不能直接比较。毕竟 HTTP 是一个应用层协议,而 RPC 更像一种通信机制、一种框架,它内部可能使用自己编写的应用层协议,也可能就是 HTTP 协议。文中的 HTTP 与 RPC 比较,可以看成是 Restful API 与 RPC 的比较
2、生成交互文件
2.1 命令方式
获取protoc软件。用于处理proto文件的工具软件,对proto文件生成消息对象和序列化及反序列化的Java实体类。下载地址:Central Repository: com/google/protobuf/protoc
获取protoc-gen-grpc-java插件。用于处理rpc定义的插件,生成针对rpc定义的Java接口。下载地址:Central Repository: io/grpc/protoc-gen-grpc-java
命令:
#单个命令:
protoc.exe --java_out=. proto/hello.proto
protoc --plugin=protoc-gen-grpc-java=./protoc-gen-grpc-java-1.32.1-windows-x86_64.exe --grpc-java_out=. --proto_path=. ./proto/hello.proto
# 合并之后命令
protoc --plugin=protoc-gen-grpc-java=./protoc-gen-grpc-java-1.42.1-windows-x86_64.exe --grpc-java_out=. --java_out=. --proto_path=. ./hello.proto
2.2 Maven方式
配置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.18.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.42.1:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
刷新pom之后可以看到
或者直接执行命令
mvn protobuf:compile
mvn protobuf:compile-custom
最终归于本质还是maven下载一应的protoc和protoc-gen-grpc-java 到本地进行生成。
在target中就会自动生成了对应的文件,将它们移动到对应的源目录底下即可。
3、代码
3.1 Server
Greeter是上述定义的接口,GreeterGrpc.GreeterImplBase就是我们要继承的父类了,RPC生成的。重写上述接口
返回一个字符串拼接请求的名称,在OnNext中返回消息体,返回onCompleted成功
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
System.err.println("receive a request: "+ request.toString());
HelloReply reply = HelloReply.newBuilder().setMessage("hello," + request.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
服务器启动
public class HelloWorldServer {
private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
private static final int port = 50051;
private Server server;
private void start() throws IOException {
// (1)
server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
.addService(new GreeterImpl())
.build()
.start();
logger.info("Server started, listening on " + port);
// (2)
Runtime.getRuntime().addShutdownHook(
new Thread(() -> {
System.out.println("shutting down gRpc server since JVM is shutting down");
try {
HelloWorldServer.this.stop();
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
System.err.println("shutting down gRpc server since JVM is shutting down");
})
);
}
public void stop() throws InterruptedException {
if (server != null) {
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
}
}
private void blockUtilShutdown() throws InterruptedException {
if (server != null) {
// (3)
server.awaitTermination();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldServer server = new HelloWorldServer();
server.start();
server.blockUtilShutdown();
}
}
3.2 Client
client则更为简单,我们说rpc就像调用本地方法一样简单
public class HelloWorldClient {
private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
private final GreeterGrpc.GreeterBlockingStub blockingStub;
private HelloWorldClient(Channel channel) {
// (2)
this.blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void greet(String name) {
logger.info("will try to greet " + name + "...");
// (3)
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply helloReply = blockingStub.sayHello(request);
System.out.println("=== response ===");
System.out.println(helloReply.toString());
logger.info("greet finished" + name + "...");
}
public static void main(String[] args) throws InterruptedException {
String host = "localhost";
int port = 50051;
// (1)
ManagedChannel managedChannel =
Grpc.newChannelBuilderForAddress(host, port, InsecureChannelCredentials.create()).build();
try {
// (2)
HelloWorldClient helloWorldClient = new HelloWorldClient(managedChannel);
logger.info("hello, I am LagLang");
helloWorldClient.greet("LagLang");
} finally {
managedChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
}
从main方法开始看起
(1) ManagedChannel 构造一个和server端连接的通道,指明了host和端口,以及安全协议
(2) 构造 Client,client有一个GreeterBlockingStub,通过chennel来构造,这是Greeter的Service存根,通过存根可以进行rpc方法调用
(3) 构造request,完成实际的rpc调用
4、调试工具
这里就显示本地有的工具ApiPost7
导入protobuf
测试对应的接口
5、总结
GRPC底层使用protobuf做数据载体,使用TCP进行通信
一个完整的远程调用框架,在学习的过程中对代码生成走了一些迷惑