老齐-gRPC实战
介绍进程通信
何为进程间通信
通信的演化过程
1、传统RPC
2、SOAP
3、RESTful
RESTful架构存在的问题:
- 它是基于文本的低效消息协议
- 应用程序之间缺乏强类型接口
- RESTful架构风格难以强制实施
4、gRPC
新一代RPC通信框架
初识Google gRPC
前身
长期以来,谷歌有一个名为Stubby(['stʌbi] 短粗的)的通用RPC框架,用来连接成千上万的微服务,这些微服务跨多个数据中心并且使用完全不同的技术来构建。Stubby的核心PRC层每秒能处理数百亿次的互联网请求。Stubby有许多很棒的特性,但无法标准化为业界通用的框架,因为它与谷歌内部的基础设施耦合得过于紧密。2015年谷歌发布了开源PRC框架gRPC,这个RPC基础设施具有标准化、可通用和跨平台得特点,旨在提供类似Stubby的可扩展性、性能和功能,但它主要面向社区。
gRPC优点
- 高效的进程间通信
gRPC没有使用JSON或XML这样的文本化格式,而是使用一个基于Protocol buffers的二进制协议与gRPC服务和客户端通信。 - 有简单且定义良好的服务接口和模式
gRPC为应用程序开发提供了一种契约优先(契约驱动)的方式。也就是说,首先必须定义服务接口,然后才能去处理实现细节。 - 属于强类型
gRPC服务契约清晰定义了应用程序间进行通信所使用的类型。这样一来,在构建跨多个团队和技术类型的云原生应用程序时,对于其所产生的大多数运行时错误和互操作错误,可以通过静态类型来克服,因此分布式应用程序的开发更加稳定。 - 支持双共通信
即客户端能发送消息给服务端,服务端也能发消息给客户端,且同一时刻信息可以进行双向传输。 - 支持多语言
- 大厂背书,社区活跃
gRPC缺点
- 不太适合面向外部
适合用在公司内部的通信,不太适合外部,因为gRPC的通信规定严格且它太新了(2015年开源),我们不可能要求客户或者已经实现好的第三方服务去配合我们改成gRPC通信。 - 当出现巨大的服务定义变更时会产生复杂的开发流程
在现代的服务间通信场景,模式修改很常见。如果出现巨大的gRPC服务定义变更,通常需要重新生成客户端代码和服务端代码。这需要整合到现有的持续集成过程中,可能会让整个开发生命周期复杂化。 - gRPC生态相对较小
gRPC传输格式Protobuf
Google Protocol Buffers是Google提供一个具有高效的协议数据交换格式工具库(类似Json),Protobuf有更高的转化效率,时间效率和空间效率都是JSON的3-5倍。
注:Protocol buffers是在HTTP/2的基础上开发的。
安装Protobuf环境和IDEA插件
1、去Github下载,地址:https://github.com/protocolbuffers/protobuf/releases
1.1、配置环境变量,并打开cmd窗口验证是否安装成功,使用protoc命令
2、在IDEA中安装插件
以下是DEMO
开发News服务端
Maven增加gRPC依赖
<dependencies>
<!--grpc底层通信组件-->
<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>
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<!--provided:只能作用在编译和测试时,同时没有传递性-->
<scope>provided</scope>
</dependency>
</dependencies>
引入protobuf一maven-plugin插件
<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.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>
</plugins>
</build>
编写proto服务定义文件
//使用proto3语法
syntax = "proto3";
//生成多个类
option java_multiple_files = false;
//生成java类所在的包
option java_package = "com.yuan.news.proto";
//生成外层类类名
option java_outer_classname = "NewsProto";
//.proto包名
package news;
/*
接下来,定义RPC服务RouteGuide
*/
service NewsService{
//list是方法名,NewsRequest代表传入参数,NewsResponse代表返回响应
rpc list(NewsRequest) returns (NewsResponse){}
}
message NewsRequest{
string date = 1;
}
message NewsResponse{
//repeated说明是一个集合(数组),数组每一个元素都是News对象
repeated News news = 1;
}
//News新闻实体对象
message News{
//对应java的int
int32 id = 1;
//新闻标题
string title = 2;
//新闻内容
string content = 3;
//对应java的long
int64 createTime = 4;
}
实现服务端业务逻辑
1、打开maven插件
2、点击上述两个图标,生成文件,文件在target中
3、打开
文件
将其中的com包中的文件拖到自己的java文件夹下
得到这两个文件
4、编写Service类
package com.yuan.news.service;
import com.yuan.news.proto.NewsProto;
import com.yuan.news.proto.NewsServiceGrpc;
import io.grpc.stub.StreamObserver;
import java.util.Date;
/**
* @author wuhaoyuan
* @date 2022/5/15 18:54
* @Description 核心业务
*
* 此处的NewsServiceGrpc.NewsServiceImplBase是gRPC自己生成的
*/
public class NewsService extends NewsServiceGrpc.NewsServiceImplBase {
/**
* 此处注意,我们之前在news.proto文件中定义过,list方法的返回值应该是NewsResponse,但此处的返回值却是void。
* 可以得知,在gRPC中,返回值是作为传入参数放在传入参数的最后一个位置上。
* */
@Override
public void list(NewsProto.NewsRequest request, StreamObserver<NewsProto.NewsResponse> responseObserver) {
String date = request.getDate();
NewsProto.NewsResponse newList = null;
try {
//gRPC中有大量的构造器模式使用情景
NewsProto.NewsResponse.Builder newListBuilder = NewsProto.NewsResponse.newBuilder();
for (int i = 1; i <= 100; i++) {
NewsProto.News news = NewsProto.News.newBuilder()
.setId(i)
.setTitle("新闻标题"+i)
.setContent(date + "当日新闻内容:"+i)
.setCreateTime(new Date().getTime())
.build();
newListBuilder.addNews(news);
}
newList = newListBuilder.build();
}catch (Exception e){
responseObserver.onError(e);
}finally {
//相当于返回数据
responseObserver.onNext(newList);
}
responseObserver.onCompleted();
}
}
开发服务端启动器
public class GrpcServer {
//自定义一个占用端口
private static final int port = 9999;
public static void main(String[] args){
try {
io.grpc.Server server = ServerBuilder.forPort(port).addService(new NewsService()).build().start();
System.out.println("gRPC服务端启动,端口号为"+port);
server.awaitTermination();
}catch (Exception e){
System.out.println("服务端异常");
}
}
}
开发News客户端
步骤与服务端类似:
1、加maven依赖
2、加maven插件
3、复制一个proto服务定义文件,从服务端那里复制,要一模一样
4、点击maven插件按钮(两个),生成文件(在target中),拖到自己的java文件夹下
5、编写客户端的启动类
package com.yuan.news;
import com.yuan.news.service.NewsService;
import io.grpc.ServerBuilder;
/**
* @author wuhaoyuan
* @date 2022/5/15 19:34
* @Description gRPC启动类
*/
public class GrpcServer {
//自定义一个占用端口
private static final int port = 9999;
public static void main(String[] args){
try {
io.grpc.Server server = ServerBuilder.forPort(port).addService(new NewsService()).build().start();
System.out.println("gRPC服务端启动,端口号为"+port);
server.awaitTermination();
}catch (Exception e){
System.out.println("服务端异常");
}
}
}
7、开启服务端,开启客户端
结束