文章目录
01.概述
进程间通信的演化过程
进程间的通信:通常指的是一个应用实例调用另外一个应用实例所提供的服务,而这两个应用都运行在自己独立的进程中,通过网络彼此交换信息的过程。
进程间通信的演化过程:
-
传统的RPC技术:JAVA远程方法调用(RMI),基于TCP通信实现远程调用,实现逻辑非常复杂,且只能规定在
JAVA应用之间
实现远程通信。 -
SOAP协议:简单对象访问协议,通过HTTP协议上封装XML格式的SOAP数据包,实现跨进程通信,是早期webService技术的底层实现,消息格式的复杂性与以及围绕SOAP所构建的各种规范的复杂性,妨碍了构建分布式应用程序的敏捷性,逐渐被REST架构风格的应用程序所替代。
-
RESTful架构风格:基于HTTP协议。
-
Google:gRPC框架,可以像调用本地方法一样调用远程方法。
Google gRPC框架
RPC(Remote Procedure Call)即远程过程调用:
-
它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
-
RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了
传输层
和应用层
,RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 -
RPC采用
客户机
/服务器
模式,请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。
RPC优势:
- 提供高效的进程间通信,使用protobuf的二进制协议进行进程间的通信
- 具有简单且定义良好的服务接口和模式(约定大于配置)
- 属于强类型调用
- 支持多语言
- 支持双工通信
RPC的缺点:
- 不适合向外部提供服务
- 巨大的服务定义变更起来比较复杂
- gRPC生态系统相对较小
gRPC传输格式protobuf:
- protobuf(Google Protocol Buffers)是Google提供的一个高效数据交换的协议工具库,Protobuf有更高的转化效率,时间效率和空间效率,是JSON的3-5倍。
安装Protobuf环境和ProtobufIDEA插件
proto文件编译器下载地址:https://github.com/protocolbuffers/protobuf/releases
注意下载对应版本的编译器(windows,linux等),下载好之后解压,然后配置环境变量即可(bin目录)
查看能否运行,在cmd中输入命令:protoc --version
能打印出版本号即可
在IDEA 插件中安装 Protobuf插件
,安装这个插件之后会对proto文件
有个语法检查和代码提示的功能
02.原理
GRPC底层原理
protobuf语法解析
// proto版本
syntax="proto3";
// 是否生成多个类,true会生成多个类,false生成单个类
option java_multiple_files=false;
// 服务类所在的包
option java_package="com.aismall.news.proto";
//服务的名字:也就是服务的名字,一个服务中有多个接口
option java_outer_classname = "NewsProto";
// .proto包名
package news;
// service关键字,用于描述要生成的api接口是什么,类似于java的逻辑接口类
// rpc 为关键字
// rpc 方法(参数类型) returns (返回值类型){}
service NewsService {
rpc list(NewsRequest)
returns (NewsResponse){}
}
/*
消息是rpc描述信息的基本单位,类似于java中的实体类
消息名字,对应于生成代码的类名
每个消息都对应生成的一个类,根据java_muitiple_files设置不同文件数量
option java_multiple_files=true; protobuf会给每个message生成一个java类
option java_multiple_files=false; protobuf会生成一个大类,消息为该类的子类,都在一个java大类中
*/
/*
字段定义方式:
类型 名称 =索引值(id)
每个字段都要定义一个唯一的索引值,这些数字用来在消息二进制时,用来识别字段
一旦开始使用就不能再更改,标识号从1开始
可以为将来需要使用的标识号预留标识符
*/
message NewsRequest {
string data = 1;
}
message NewsResponse {
// repeated说明是一个集合(数组),数组的每一个对象都是news
repeated News news = 1;
}
message News {
int32 id = 1;
string title = 2;
string content=3;
int64 createTime=4;
}
/*
生成文件介绍:
消息名orBuilder:消息类于构造器接口
消息名:消息的具体实现
protobuf还可以实现类型的嵌套,嵌套之后必须要声明这个message才可以
*/
注意:
-
proto文件:可以生成
不同语言的代码
,通过不同的指令生成不同的语言代码,如java,go等。 -
poto文件中的message一旦定义好编号,尽量不要修改。
生成代码解析
03.案例实战演示
客户端和服务端进行通信的依据就是基于相同的proto文件生成的代码,两种方式:
- 方式一:server端和client端使用相同的proto文件,生成相同的代码。
- 方式二:使用proto生成代码之后,将相应的代码打成jar包,然后上传到自己的中央仓库,然后server端和client端都引用这个jar包。
注意:
- springoot版本:2.4.1
RPC服务端搭建
步骤:
- 添加gRPC依赖坐标,引入protobuf-maven-plugin插件
- 编写proto文件
- 实现服务端业务逻辑
- 服务端开发
1、新建一个maven项目,引入如下依赖
以及插件
:
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.21.2</version>
</dependency>
<!-- grpc底层基于netty开发:底层通信组件 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.42.0</version>
</dependency>
<!-- protobuf的支持工具包 -->
<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>
<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>
<!--protobuf插件:通过配置的环境变量找到protobuf编译器-->
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!--生成代码-->
<protocArtifact>com.google.protobuf:protoc:3.21.2: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>
<!--生成GRPC通信文件-->
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2、在main目录下新建一个proto文件,用于存放拓展名proto结尾的文件(news.proto)。
syntax="proto3";
option java_multiple_files=false;
option java_package="com.aismall.news.proto";
option java_outer_classname = "NewsProto";
package news;
service NewsService{
rpc list(NewsRequest) returns (NewsResponse){}
}
message NewsRequest{
string date=1;
}
message NewsResponse{
repeated News news = 1;
}
message News{
int32 id = 1;
string title = 2;
string content=3;
int64 createTime=4;
}
3、使用插件生成消息代码
和GRPC通信文件
- Maven——>Plugins—>protobuf——>compile:生成消息代码
- Maven——>Plugins—>protobuf——>compile-custom:生成GRPC通信文件
4、移动生成的文件:生成的文件会在target/generated-sources/protobuf/grpc-java目录
和target/generated-sources/protobuf/java目录
下面,将生成文件移动到源代码同级目录下即可。
5、编写业务类:继承NewsServiceGrpc.NewsServiceImplBase
类,并实现里面的方法,在方法里重写业务逻辑。
public class NewsService extends NewsServiceGrpc.NewsServiceImplBase {
//注意:入参为方法里面第一个参数,出参为方法里面的第二个参数
@Override
public void list(NewsProto.NewsRequest request, StreamObserver<NewsProto.NewsResponse> responseObserver) {
// 获取入参的data属性
String date = request.getDate();
// 反参对象
NewsProto.NewsResponse newsList = null;
try {
// 反参的构建器
NewsProto.NewsResponse.Builder newsListBuilder = NewsProto.NewsResponse.newBuilder();
for ( int i = 1;i<=25;i++ ){
// 构建反参中的存储的实体类
NewsProto.News news = NewsProto.News.newBuilder().setId(i)
.setContent(date + "新闻内容" + i)
.setTitle("新闻标题" + i)
.setCreateTime(new Date().getTime())
.build();
// 将构建好的实体类存入反参的构建器中
newsListBuilder.addNews(news);
}
// 调用build()方法完成反参的构建
newsList = newsListBuilder.build();
} catch (Exception e) {
responseObserver.onError(e);
}finally {
// 实用观察者模式,将参数返回
responseObserver.onNext(newsList);
}
// 关闭
responseObserver.onCompleted();
}
}
6、修改启动类:
public class GrpcNewsServer {
// 定义一个端口
public static final int port = 7520;
public static void main(String[] args) throws InterruptedException, IOException {
// 启动grpc服务
io.grpc.Server server = ServerBuilder.forPort(port).addService(new NewsService())
.build().start();
System.out.println(String.format("GRPC 服务启动,端口号:%d", port));
server.awaitTermination();
}
}
RPC客户端
1 、2、3、4步骤如法炮制(参考:server端)
5、修改主启动类:在主启动类中调用server端提供的服务。
/*
* 测试 NewsService接口中的list服务
* */
public class GrpcNewsClient {
// 访问server的url和端口号
private static final String host="localhost";
private static final int serverPort=7520;
public static void main(String[] args) {
//建立一个传输文本的通道
ManagedChannel channel= ManagedChannelBuilder.forAddress(host,serverPort).usePlaintext()
.build();
// 创建一个传输通道,使用阻塞模式
NewsServiceGrpc.NewsServiceBlockingStub blockingStub=NewsServiceGrpc.newBlockingStub(channel);
//创建一个入参类
NewsProto.NewsRequest request= NewsProto.NewsRequest.newBuilder().setData("20220709").build();
// 调用NewService接口中的list服务:调用方式类似于调用方法一样。
// 调用之后,会给我们返回一个出参类型的值:NewsResponse实例,该实例在服务端创建,通过上面定义的通信方式传输过来
NewsProto.NewsResponse response=blockingStub.list(request);
//获取出参实例中的参数
List<NewsProto.News> newsList=response.getNewsList();
for (NewsProto.News news:newsList) {
System.out.println(news);
}
//关闭传输通道
channel.shutdown();
}
}
小结
通过上面的例子我们可以看出,rpc服务之间的调用,就好像调用自己本地的方法一样。
本次演示,server端和client端使用相同的proto文件,生成相同的代码。
后面介绍springboot整合grpc框架
的时候采用,使用proto生成代码之后,将相应的代码打成jar包,然后上传到自己的中央仓库,然后server端和client端都引用这个jar包。
04.Springboot整合gPRC
grpc-server-spring-boot-starter
/grpc-client-spring-boot-starter
是第三方提供的Springboot整合gRPC的整合包,基于Springboot自动装配和声明式特性,整体简化了gRPC在Springboot的开发过程。
提前准备
新建一个项目maven项目,用于将proto文件生成代码打成jar包,然后上传到自己的本地仓库。
1、新建名为news
的Maven工程,引入如下依赖。
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.21.2</version>
</dependency>
<!-- grpc底层基于netty开发:底层通信组件 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.42.0</version>
</dependency>
<!-- protobuf的支持工具包 -->
<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>
<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>
<!--protobuf插件:通过配置的环境变量找到protobuf编译器-->
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!--生成代码-->
<protocArtifact>com.google.protobuf:protoc:3.21.2: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>
<!--生成GRPC通信文件-->
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2、在main目录下新建一个proto文件夹,用于存放拓展名proto结尾的文件(news.proto
)。
syntax="proto3";
option java_multiple_files=false;
option java_package="com.aismall.news.proto";
option java_outer_classname = "NewsProto";
package news;
service NewsService{
rpc list(NewsRequest) returns (NewsResponse){}
}
message NewsRequest{
string date=1;
}
message NewsResponse{
repeated News news = 1;
}
message News{
int32 id = 1;
string title = 2;
string content=3;
int64 createTime=4;
}
3、使用插件生成消息代码
和GRPC通信文件
- Maven——>Plugins—>protobuf——>compile:生成消息代码
- Maven——>Plugins—>protobuf——>compile-custom:生成GRPC通信文件
4、移动生成的文件:生成的文件会在target/generated-sources/protobuf/grpc-java目录
和target/generated-sources/protobuf/java目录
下面,将生成文件移动到源代码同级目录下即可。
5、在POM文件里把插件注释掉
,要不然在第六步会报错。
6、在maven插件中的Lifecyle中: 先双击clean、在双击install,就可以把我们的news项目上传到本地仓库了。
注意: 依赖的坐标
server
1、新建一个Springboot项目,Springboot版本为2.4.1。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Springboot集成gRPC框架:server端-->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.13.0.RELEASE</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>1.42.0</version>
</dependency>
<!--注意:这个是前面自己生成的jar包-->
<dependency>
<groupId>org.aismall</groupId>
<artifactId>news</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2、编写业务类,继承NewsServiceGrpc.NewsServiceImplBase类,并实现里面的方法
// 使用注解标注这是一个Grpc的服务:标识接口
//springboot扫描的时候,会认为这个类是grpc的实现类
@GrpcService
public class NewsService extends NewsServiceGrpc.NewsServiceImplBase {
//注意:入参为方法里面第一个参数,出参为方法里面的第二个参数
@Override
public void list(NewsProto.NewsRequest request, StreamObserver<NewsProto.NewsResponse> responseObserver) {
// 获取入参的data属性
String date = request.getDate();
// 反参对象
NewsProto.NewsResponse newsList = null;
try {
// 反参的构建器
NewsProto.NewsResponse.Builder newsListBuilder = NewsProto.NewsResponse.newBuilder();
for ( int i = 1;i<=25;i++ ){
// 构建反参中的存储的实体类
NewsProto.News news = NewsProto.News.newBuilder().setId(i)
.setContent(date + "新闻内容" + i)
.setTitle("新闻标题" + i)
.setCreateTime(new Date().getTime())
.build();
// 将构建好的实体类存入反参的构建器中
newsListBuilder.addNews(news);
}
// 调用build()方法完成反参的构建
newsList = newsListBuilder.build();
} catch (Exception e) {
responseObserver.onError(e);
}finally {
// 实用观察者模式,将参数返回
responseObserver.onNext(newsList);
}
// 关闭
responseObserver.onCompleted();
}
}
3、修改配置文件
# 应用名称
spring.application.name=springboot-grpc-server
# web服务:内置tomcat对外提供的端口号
server.port = 8725
# grpc服务:grpc服务之间通信的端口号
grpc.server.port = 7725
4、启动服务等待客户端的调用。
client
1、新建一个Springboot项目,Springboot版本为2.4.1。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Springboot集成gRPC框架:client端-->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.13.0.RELEASE</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>1.42.0</version>
</dependency>
<dependency>
<groupId>org.aismall</groupId>
<artifactId>news</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2、编controller类,继承NewsServiceGrpc.NewsServiceImplBase类,并实现里面的方法。
//restful风格的控制器
@RestController
public class NewsController {
//指向配置文件的 grpc-server 配置
@GrpcClient("grpc-server")
private NewsServiceGrpc.NewsServiceBlockingStub newsServiceBlockingStub;
//访问链接地址:localhost:8726/news?date=20220725
@GetMapping("news")
public String news(String date) {
System.out.println("==================");
NewsProto.NewsResponse newsResponse = newsServiceBlockingStub.list(NewsProto.NewsRequest.newBuilder().setDate(date).build());
return newsResponse.getNewsList().toString();
}
}
3、修改配置文件
# 应用名称
spring.application.name=springboot-grpc-client
# web服务 :内置tomcat对外提供的端口号
server.port = 8726
# 定义客户端访问的grpc服务地址和端口号
grpc.client.grpc-server.address= static://localhost:7725
# 文本响应
grpc.client.grpc-server.negotiation-type: plaintext
4、启动服务,使用浏览器访问:localhost:8726/news?date=20220725
05.总结
代码地址:https://gitee.com/aismall/mystudym