概要
RPC远程过程访问
gPRC具有标准化、可通用和跨平台的特点
进程间通信,通常是指一个应用实例调用另外一个应用实例所提供的服务,而这两个应用都运行在自己独立的进程中,通过网络彼此交换信息的过程。
契约优先
优点
高效进程间通信
简单且定义良好的服务接口和模式,编译阶段发现问题
属于强类型调用
支持多语言
支持双工通信
缺点
gRPC不适合面向外部的服务
服务定义变更,会出现复杂的开发流程
gRPC生态系统相对较小
Protobuf
Google提供一个具有高效的协议数据交换格式工具库(类似Json)
Protobuf 时间效率和空间效率是JSON的3到5倍
客户端与服务端通信使用 HTTP2 协议
安装
1.安装Protobuf编译器
Protobuf运行环境需安装Protobuf编译器
下载地址:
windows 配置环境变量
- 新建系统变量 PROTOBUF_HOME 变量值 是 protobuf 编译器解压目录。
- 编辑Path环境变量,添加 %PROTOBUF_HOME%\bin
验证是否配置成功
打开CMD,输入protoc,输出一些列参数说明,则说明安装好了
2.安装IDEA Protobuf插件
打开IDEA,进入插件市场,搜索protobuf,如下图所示,安装即可
使用
服务端开发流程
-
Maven增加gRPC依赖坐标系
-
引入protobuf-maven-plugin插件
-
编写proto服务定义文件
-
实现服务端业务逻辑
-
开发服务端启动器
maven 依赖
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.42.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.42.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.42.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>
<!-- protobuf代码生成插件 -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.42.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
引入maven 完成后,在代码的main目录下,创建proto文件夹(约定)。
maven 插件会自动扫描,proto 文件夹下的 proto 后缀文件,进行代码生成。
IDEA插件就是对 proto 文件提供语法检查等编辑器支持。
编辑好proto文件后,点击IDEA右侧maven -> 找到需生成的服务 -> Plugins -> protobuf -> protobuf:complie -> protobuf:complie-custom 生成RPC代码
生成的代码在,target 目录对应配置的包下,需要将生成的java代码,移动到src下
服务端代码demo
// 自己建一个 service类,继承 Grpc 下的 实现类
public class TestService extends TestServiceGrpc.TestServiceImplBase {
@Override
public void hello(TestProto.StringRequest request, StreamObserver<TestProto.StringResponse> responseObserver) {
String name = request.getName();
TestProto.StringResponse response = TestProto.StringResponse.newBuilder().setResult("你好," + name).build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
客户端开发流程
客户端与服务端依赖的jar是相同的
也是main目录下创建 proto 文件夹,然后将proto文件复制到客户端。
然后用maven生成代码,跟服务端一样,如果是同样的语言进行开发,直接从服务端复制过去也可以。
客户端发送请求代码
// 类似 jdbc 一样的套路代码
ManagedChannel channel = ManagedChannelBuilder.forAddress(host, serverPort).usePlaintext()
.build();
try {
TestServiceGrpc.TestServiceBlockingStub blockingStub = TestServiceGrpc.newBlockingStub(channel);
Test.StringResponse response = blockingStub.hello(Test.StringRequest.newBuilder().setName("小黑").build());
System.out.println(response.getResult());
}finally {
channel.shutdown();
}
Protobuf 文件详解
proto 文件
// 使用proto3语法
syntax = "proto3";
// 生成多个类,设置为否
option java_multiple_files = false;
// 生成java类所在的包
option java_package = "com.songjh.demo";
// 生成外层类类名
option java_outer_classname = "Test";
// 逻辑包名,实际不会加到类上,就是给 proto 文件的逻辑包名
package songjh;
// service服务,用于描述要生成的API接口,类似于接口类
service TestService {
//rpc 方法名( 参数类型) returns (返回类型)
rpc list(TestRequest) returns (TestResponse) {}
rpc hello(StringRequest) returns (StringResponse){}
}
/*
消息,类似Java的"实体类"
名字,对应于生成代码后的类名
每一个消息都对应生成一个类,根据java_multiple_files设置不同文件数量也不同
*/
message NewsRequest {
/*
字段:类型 名称 = 索引值(id)
每个字段都要定义唯一的索引值,这些数字是用来在消息的二进制格式中识别各个字段的。
一旦开始使用就不能够再改变,最小的标识号可以从1开始,最大到2^29 - 1, 或 536870911。
不可以使用 [19000-19999]的标识号, Protobuf协议实现中对这些进行了预留。
切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。可以1 ,10,,20, 30 这样初始化。
*/
string date = 1;
}
message TestResponse {
//repeated说明是一个集合(数组),数组每一个元素都是News对象
repeated News news = 1; //List<News> getNewsList();
}
//News新闻实体对象
message News{
int32 id = 1; // int id = 0;
string title = 2; // String title = "";
int64 createTime = 3; // long createTime = 0l;
}
message StringRequest{
string name = 1;
}
message StringResponse{
string result = 1;
}
生成代码
父类 | 类名 | 说明 |
---|---|---|
DemoMess | 消息类的大类 | |
DemoMess | DemoOrBuilder | 消息类与构造器接口 |
DemoMess | DemoRequestOrBuilder | 消息类与构造器接口 |
DemoMess | DemoResponseOrBuilder | 消息类与构造器接口 |
DemoMess | Demo | 具体的消息类实现 |
DemoMess | DemoRequest | 具体的消息类实现 |
DemoMess | DemoResponse | 具体的消息类实现 |
DemoServiceGrpc | gRPC通信类的集合 | |
DemoServiceGrpc | XXXDescriptorSupplier | gRPC通信用的描述符文件 (元数据) |
DemoServiceGrpc | DemoServiceBlockingStub | 同步通信客户端存根,产生阻塞,支持一元与服务端流数据通信 |
DemoServiceGrpc | DemoServiceFutureStub | 异步通信客户端存根,基于Guava Future实现不支持流式处理 |
DemoServiceGrpc | DemoServiceStub | 异步通信客户端存根,支持双向流式传输 |
DemoServiceGrpc | NewsServicelmplBase | 服务器端的骨架类,继承这个类实现业务逻辑 |
通信模式
一元RPC通信模式
客户端向服务器发送单个请求并获得单个响应
客户端使用 newBlockingStub 通信即可。
DemoServiceGrpc.newBlockingStub(channel);
服务端流式RPC通信模式
从客户端发起1次请求,会产生从服务器的多次响应
首先proto文件,service 定义中 rpc 方法的 返回值类型 前增加 stream ,如下
service SmsService {
rpc test(TestRequest) returns (stream TestResponse) {}
}
一般来说客户端传过来的是个 List
服务端代码如下
// 循环遍历客户端传过来的 List
for(String xxx : xxxList) {
Demo.XXXResponse response = Demo.XXXResponse.newBuilder().setXX().build();
// 向客户端返回一次响应
responseObserver.onNext();
}
// 服务器端都处理完了,告诉客户端服务端都完事了
responseObserver.onCompleted();
客户端还是用newBlockingStub。
流式的返回值是 Iterator 。
客户端流式RPC通信模式
从客户端发起多次请求,产生从服务器的1次响应
例如导入多条数据,最后返回一次结果。
客户端流式RPC,服务端必须使用异步处理
首先proto文件客户端需要增加stream,如下
service TestService {
rpc createString(stream TestRequest) returns (TestResponse) {}
}
服务端代码,必须用异步处理
public StreamObserver<TestProto.TestRequest> test(StreamObserver<TestProto.TestResponse> responseObserver) {
/**
* 异步通信基于responseObserver事件回调
*/
return new StreamObserver<TestProto.TestRequest>() {
@Override
//接收到客户端发来的单个请求时,触发onNext
public void onNext(TestProto.TestRequest request) {
// 处理业务请求
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
//客户端传输完毕时,完成消息统计
@Override
public void onCompleted() {
// 服务端返回一次消息结果
responseObserver.onNext()
responseObserver.onCompleted();
}
};
}
双向流式RPC通信模式
双向即是 客户端和服务端都是多对多,其实就是上述 客户端和服务端流式RPC通信代码结合。
proto文件客户端和服务端都需要增加stream
service SmsService {
rpc test(stream testRequest) returns (stream testResponse) {}
}
SpringBoot 集成 gRPC
maven依赖,插件依赖跟之前一样没有变化
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.13.0.RELEASE</version>
</dependency>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.42.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
依然是在src目录下创建 proto 文件夹,然后在该文件夹下创建proto文件。
在创建 service 类上,增加 @GrpcService 注解。
yml配置
# gRPC 服务端设置,服务端开启的端口
grpc:
server:
port: 9090
# gRPC 客户端配置
grpc:
client:
grpc-server: #自定义一个gRPC服务名
address: 'static://127.0.0.1:9090' # static 表示静态ip直接访问
negotiationType: plaintext # 传输类型设置成文本
客户端使用时,可以直接依赖注入,通过@GrpcClient 注解。
@GrpcClient("grpc-server")
private DemoServiceGrpc.DemoServiceBlockingStub newsStub;
Eureka 集成 gRPC
eureka server 没变化,也不用额外引入maven 或单独配置。
gRPC 的 server,也作为 eureka client ,注册到eureka 上。
gRPC 的客户端和服务端 需引入maven 配置如下
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.13.0.RELEASE</version>
</dependency>
<!-- 如遇到版本冲突,则可能是sc依赖的guava版本过低 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0-jre</version>
</dependency>
server 端代码编写,跟springboot 编写一样,就是配置不一样
grpc:
server:
port: 0 # 设置为0 表示使用给一个未被占用的随机端口,会被上传eureka,所以不用配
客户端配置
grpc:
client:
g-server-service: # 服务端名称,spring.application.name
negotiationType: plaintext #代表文本传输
客户端代码
// 这个名字要保持一致,调用哪个服务的方法
@GrpcClient("g-server-service")
private DemoServiceGrpc.DemoServiceBlockingStub newsStub;