文章目录
1 什么是 RPC
- RPC 执行流程:调用方和被调用方
1. Client 调用以本地调用方式调用服务;
2. Client Stub 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(桩代码是未实现的接口,此处使用动态代理);
3. Client Stub 找到服务地址,并将消息发送到服务端;
4. Server Stub 收到消息后进行解码;
5. Server Stub 根据解码结果调用本地的服务;
6. 本地服务执行并将结果返回给 Server Stub;
7. Server Stub 将返回结果打包成消息并发送至消费方;
8. Client Stub 接收到消息,并进行解码;
9. Client 得到最终结果
- Stub (桩代码)指满足形式要求但没有实现实际功能的占位或代理代码
- RPC 中的桩代码负责远程请求或响应
-
RPC 和 HTTP 的关系
- 基于 HTTP 的远程调用方案(包含了接口规范、序列化反序列化等)和 RPC 是并列的
- 单从 HTTP 协议来看,RPC 可以使用 HTTP 作为通信协议
-
基于 HTTP 的远程调用方案和 RPC 的相同点、不同点
- 相同点
- 都可以实现远程调用(上图的顶端节点)
- 不同点
- 基于 HTTP RESTful 的远程调用可读性更好
- RPC 可以针对场景选用不同的框架(如 Dubbo、gRPC 等),不同的通信协议(HTTP、TCP、UDP),以及不同的序列化方式(JSON、PB 等),效率更高
- 相同点
2 ProtocolBuffer
- PB 是一种语言无关、平台无关、可扩展的序列化结构数据的接口描述语言(Interface Description Language, IDL),可用于通信协议、数据存储等
- 使用
service
定义服务,使用message
定义服务使用的类service
定义的每个 rpc 方法,根据输入和返回是否为stream
修饰,分为四种情况
- 基本语法
syntax = "proto3" // 语法版本
option java_package = "com.hit.demo"; // 生成桩代码的包,位于target的子目录
...
// 定义服务
service PhoneLookup {
rpc QueryPhoneByAddress (Request) returns (Response) {}
}
message Requset {
required string id = 1; // required:必须传入1个参数
optional string name = 2; // optional:可以传入0或1个参数,如果不传入则为默认0值
repeated string address = 3; // repeated:可以传入0或多个参数
}
message Response {
string id = 1;
ResultCode result = 2 [default = SUCCESS]; // 声明默认值
repeated int64 number = 3;
repeated bool active = 4;
enum ResultCode {
SUCCESS = 0;
FAIL = 1;
UNKNOWN = 3;
}
}
3 Demo: HelloService
1. 创建 Maven 工程,导入 gRPC 依赖,配置 PB 插件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<grpc.version>1.51.1</grpc.version><!-- CURRENT_GRPC_VERSION -->
<protobuf.version>3.21.7</protobuf.version>
<protoc.version>3.21.7</protoc.version>
<!-- required for jdk9 -->
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version> <!-- prevent downgrade via protobuf-java-util -->
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope> <!-- not needed at runtime -->
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-testing</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.4.0</version>
<scope>test</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:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2. 定义 PB
syntax="proto3";
option java_package = "com.xuecheng.fastdfs.target.stub"; // 指定桩代码在target目录下的包
option java_generic_services = true;
service HelloService {
rpc sayHello(HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
int32 requestId = 1;
string name = 2;
}
message HelloResponse {
int32 responseId = 1;
bool responseState = 2;
string greeting = 3;
}
3. 执行 Maven lifecycle compile
编译工程
- 根据插件的配置会自动执行
protobuf compile(生成message实体类)
和protobuf compile-customer(生成service的实现基类)
4. 生成桩代码,将其复制到源代码目录下
- PB 配置的
option java_package="com.xuecheng.fastdfs.target.stub"
指定了生成的桩代码在编译结果target
目录下的位置(蓝框所在的包),不要和复制的目标位置(红框)处于相同的包- 如果相同,即设置
option java_package="com.xuecheng.fastdfs.stub"
,会在后续执行maven compile
时出现类重复的异常(因为HelloService.proto
和源代码都想向同一个包下写入同名class
文件)
5. 实现服务器和客户端逻辑、启动类
- 服务器的逻辑和启动类
package com.xuecheng.fastdfs.demo;
import com.xuecheng.fastdfs.stub.HelloServiceGrpc;
import com.xuecheng.fastdfs.stub.HelloServiceOuterClass;
import io.grpc.Server;
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class MyHelloServer extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void sayHello(HelloServiceOuterClass.HelloRequest request, StreamObserver<HelloServiceOuterClass.HelloResponse> responseObserver) {
int requestId = request.getRequestId();
String requestName = request.getName();
HelloServiceOuterClass.HelloResponse response = HelloServiceOuterClass.HelloResponse.newBuilder()
.setResponseId(requestId)
.setResponseState(true)
.setGreeting("Hello " + requestName + "! This greeting is from server!")
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
public static void main(String[] args) {
// 设置服务端口号
int port = 8899;
Server server = NettyServerBuilder.forPort(port)
.addService(new MyHelloServer())
.build();
try {
// 启动服务
server.start();
System.out.println("gRPC server started, on port: " + port);
// 设置60秒后关闭
server.awaitTermination(60, TimeUnit.SECONDS);
System.out.println("gRPC server terminated.");
} catch (IOException e) {
System.out.println("Start server exception: " + e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
System.out.println("Server interrupted: " + e.getMessage());
e.printStackTrace();
}
}
}
- 客户端的逻辑和启动类
package com.xuecheng.fastdfs.demo;
import com.xuecheng.fastdfs.stub.HelloServiceGrpc;
import com.xuecheng.fastdfs.stub.HelloServiceOuterClass;
import io.grpc.ManagedChannel;
import io.grpc.netty.shaded.io.grpc.netty.NegotiationType;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
public class MyHelloClient {
public static void main(String[] args) {
// 指定服务的ip和端口号
String host = "192.168.0.105";
int port = 8899;
// 创建客户端
ManagedChannel channel = NettyChannelBuilder.forAddress(host, port)
.negotiationType(NegotiationType.PLAINTEXT)
.build();
HelloServiceGrpc.HelloServiceBlockingStub blockingStub = HelloServiceGrpc.newBlockingStub(channel);
// 构造请求
HelloServiceOuterClass.HelloRequest request = HelloServiceOuterClass.HelloRequest.newBuilder()
.setRequestId(1)
.setName("zy")
.build();
// 请求服务
HelloServiceOuterClass.HelloResponse response = blockingStub.sayHello(request);
// 解析结果
System.out.println("Greeting: " + response.getGreeting());
}
}