热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一个简单的案例入门gRPC

这篇文章本来要在年前和小伙伴们见面,但是因为我之前的Mac系统版本是10.13.6,这个版本比较老,时至今天在运行一些新鲜玩意的时候有时候


这篇文章本来要在年前和小伙伴们见面,但是因为我之前的 Mac 系统版本是 10.13.6,这个版本比较老,时至今天在运行一些新鲜玩意的时候有时候会有一些 BUG(例如运行最新版的 Nacos 等),运行 gRPC 的插件也有 BUG,代码总是生成有问题,但是因为系统升级是一个大事,所以一直等到过年放假,在家才慢慢折腾将 Mac 升级到目前的 13.1 版本,之前这些问题现在都没有了,gRPC 的案例现在也可以顺利跑起来了。

所以今天就来和小伙伴们简单聊一聊 gRPC。


1. 缘起

我为什么想写一篇 gRPC 的文章呢?其实本来我是想和小伙伴们梳理一下在微服务中都有哪些跨进城调用的方式,在梳理的过程中想到了 gRPC,发现还没写文章和小伙伴们聊过 gRPC,因此打算先来几篇文章和小伙伴们详细介绍一下 gRPC,然后再梳理微服务中的跨进程方案。


2. 什么是 gRPC

了解 gRPC 之前先来看看什么是 RPC。

RPC 全称是 Remote Procedure Call,中文一般译作远程过程调用。RPC 是一种进程间的通信模式,程序分布在不同的地址空间里。简单来说,就是两个进程之间互相调用的一种方式。

gRPC 则是一个由 Google 发起的开源的 RPC 框架,它是一个高性能远程过程调用 (RPC) 框架,可以在任何环境中运行。gRPC 通过对负载均衡、跟踪、健康检查和身份验证的可插拔支持,有效地连接数据中心内和数据中心之间的服务。

在 gRPC 中,客户端应用程序可以直接调用部署在不同机器上的服务端应用程序中的方法,就好像它是本地对象一样,使用 gRPC 可以更容易地创建分布式应用程序和服务。与许多 RPC 系统一样,gRPC 基于定义服务的思想,指定基于参数和返回类型远程调用的方法。在服务端侧,服务端实现接口,运行 gRPC 服务,处理客户端调用。在客户端侧,客户端拥有存根(Stub,在某些语言中称为客户端),它提供与服务端相同的方法。

gRPC 客户端和服务端可以在各种环境中运行和相互通信 – 从 Google 内部的服务器到你自己的桌面 – 并且可以使用 gRPC 支持的任何语言编写。因此,你可以轻松地用 Java 创建 gRPC 服务端,使用 Go、Python 或 Ruby 创建客户端。此外,最新的 Google API 将包含 gRPC 版本的接口,使你轻松地将 Google 功能构建到你的应用程序中。

gRPC 支持的语言版本:

说了这么多,还是得整两个小案例小伙伴们可能才会清晰,所以我们也不废话了,上案例。


3. 实践

先来看下我们的项目结构:

├── grpc-api
│   ├── pom.xml
│   ├── src
├── grpc-client
│   ├── pom.xml
│   ├── src
├── grpc-server
│   ├── pom.xml
│   ├── src
└── pom.xml

大家看下,这里首先有一个 grpc-api,这个模块用来放我们的公共代码;grpc-server 是我们的服务端,grpc-client 则是我们的客户端,这些都是普通的 maven 项目。


3.1 grpc-api

在 grpc-api 中,我们首先引入项目依赖,如下:

<dependencies>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-netty-shadedartifactId>
<version>1.52.1version>
dependency>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-protobufartifactId>
<version>1.52.1version>
dependency>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-stubartifactId>
<version>1.52.1version>
dependency>
<dependency>
<groupId>org.apache.tomcatgroupId>
<artifactId>annotations-apiartifactId>
<version>6.0.53version>
<scope>providedscope>
dependency>
dependencies>

除了这些常规的依赖之外&#xff0c;还需要一个插件&#xff1a;

<build>
<extensions>
<extension>
<groupId>kr.motd.mavengroupId>
<artifactId>os-maven-pluginartifactId>
<version>1.6.2version>
extension>
extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.pluginsgroupId>
<artifactId>protobuf-maven-pluginartifactId>
<version>0.6.1version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier}protocArtifact>
<pluginId>grpc-javapluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.51.0:exe:${os.detected.classifier}pluginArtifact>
configuration>
<executions>
<execution>
<goals>
<goal>compilegoal>
<goal>compile-customgoal>
goals>
execution>
executions>
plugin>
plugins>
build>

我来说一下这个插件的作用。

默认情况下&#xff0c;gRPC 使用 Protocol Buffers&#xff0c;这是 Google 提供的一个成熟的开源的跨平台的序列化数据结构的协议&#xff0c;我们编写对应的 proto 文件&#xff0c;通过上面这个插件可以将我们编写的 proto 文件自动转为对应的 Java 类。



多说一句&#xff0c;使用 Protocol Buffers 并不是必须的&#xff0c;也可以使用 JSON 等&#xff0c;但是目前来说这个场景更常用的还是 Portal Buffers。


接下来我们在 main 目录下新建 proto 文件夹&#xff0c;如下&#xff1a;

注意&#xff0c;这个文件夹位置是默认的。如果我们的 proto 文件不是放在 src/main/proto 位置&#xff0c;那么在配置插件的时候需要指定 proto 文件的位置&#xff0c;咱们本篇文章主要是入门&#xff0c;我这里就使用默认的位置。

在 proto 文件夹中&#xff0c;我们新建一个 product.proto 文件&#xff0c;内容如下&#xff1a;

syntax &#61; "proto3";
option java_multiple_files &#61; true;
option java_package &#61; "org.javaboy.grpc.demo";
option java_outer_classname &#61; "ProductProto";
package product;
service ProductInfo {
rpc addProduct (Product) returns (ProductId);
rpc getProduct(ProductId) returns(Product);
}
message Product {
string id &#61; 1;
string name&#61;2;
string description&#61;3;
float price&#61;4;
}
message ProductId {
string value &#61; 1;
}

这段配置算是一个比较核心的配置了&#xff0c;这里主要说明了负责进程传输的类、方法等到底是个啥样子&#xff1a;


  1. syntax &#61; "proto3";&#xff1a;这个是 protocol buffers 的版本。
  2. option java_multiple_files &#61; true;&#xff1a;这个字段是可选的&#xff0c;如果设置为 true&#xff0c;表示每一个 message 文件都会有一个单独的 class 文件&#xff1b;否则&#xff0c;message 全部定义在 outerclass 文件里。
  3. option java_package &#61; "org.javaboy.grpc.demo";&#xff1a;这个字段是可选的&#xff0c;用于标识生成的 java 文件的 package。如果没有指定&#xff0c;则使用 proto 里定义的 package&#xff0c;如果package 也没有指定&#xff0c;那就会生成在根目录下。
  4. option java_outer_classname &#61; "ProductProto";&#xff1a;这个字段是可选的&#xff0c;用于指定 proto 文件生成的 java 类的 outerclass 类名。什么是 outerclass&#xff1f;简单来说就是用一个 class 文件来定义所有的 message 对应的 Java 类&#xff0c;这个 class 就是 outerclass&#xff1b;如果没有指定&#xff0c;默认是 proto 文件的驼峰式&#xff1b;
  5. package product;&#xff1a;这个属性用来定义 message 的包名。包名的含义与平台语言无关&#xff0c;这个 package 仅仅被用在 proto 文件中用于区分同名的 message 类型。可以理解为 message 全名的前缀&#xff0c;和 message 名称合起来唯一标识一个 message 类型。当我们在 proto 文件中导入其他 proto 文件的 message&#xff0c;需要加上 package 前缀才行。所以包名是用来唯一标识 message 的。
  6. service&#xff1a;我们定义的跨平台方法都写在 service 中&#xff0c;上面的案例中我们定义了两个方法&#xff1a;addProduct 表示添加一件商品&#xff0c;参数是一个 Product 对象&#xff0c;返回值则是刚刚添加成功的商品的 ID&#xff1b;getProduct 则表示根据 ID 查询一个商品&#xff0c;参数是一个商品 ID&#xff0c;返回值则是查询到的商品对象。这里的定义相当于一个接口&#xff0c;将来我们要在 Java 代码中实现这个接口。
  7. message&#xff1a;这里有点像我们在 Java 中定义类&#xff0c;上文中我们定义了两个类&#xff0c;分别是 Product 和 ProductId 两个类。这两个类在 service 中被使用。

message 中定义的有点像我们 Java 中定义的类&#xff0c;但是不能直接使用 Java 中的数据类型&#xff0c;毕竟这是 Protocol buffers&#xff0c;这个是和语言无关的&#xff0c;将来可以据此生成不同语言的代码&#xff0c;这里我们可以使用的类型和我们 Java 类型之间的对应关系如下&#xff1a;

另外我们在 message 中定义的属性的时候&#xff0c;都会给一个数字&#xff0c;例如 id&#61;1&#xff0c;name&#61;2 等&#xff0c;这个数字将来会在二进制消息中标识我们的字段&#xff0c;并且一旦我们的消息类型被使用就不应更改&#xff0c;这个有点像序列化的感觉。

实际上&#xff0c;这个 message 编译后的字节内容大概像下面这样&#xff1a;

这里的标签中的内容包含两部分&#xff0c;字段索引和字段类型&#xff0c;字段索引其实就是我们上面定义的数字。

定义完成之后&#xff0c;接下来我们就需要使用插件来生成对应的 Java 代码了&#xff0c;插件我们在前面已经引入了&#xff0c;现在只需要执行了&#xff0c;如下图&#xff1a;

注意&#xff0c;compile 和 compile-custom 两个指令都需要执行。其中 compile 用来编译消息对象&#xff0c;compile-custom 则依赖消息对象,生成接口服务。

首先我们点击 compile 看看生成的代码&#xff0c;如下&#xff1a;

再看 compile-custom 生成的代码&#xff0c;如下&#xff1a;

好了&#xff0c;这样我们的准备工作就算完成了。



有的小伙伴生成的代码文件夹颜色不对劲&#xff0c;此时有两种解决办法&#xff1a;1.选中目标文件夹&#xff0c;右键单击&#xff0c;选择 Mark Directory as-> Generated Sources root&#xff1b;2.选中工程&#xff0c;右键单击&#xff0c;选择 Maven->Reload project。推荐使用第二种方案。



3.2 grpc-server

接下来我们创建 grpc-server 项目&#xff0c;并使该项目依赖 grpc-api&#xff0c;然后在 grpc-server 中&#xff0c;提供 ProductInfo 的具体实现&#xff1a;

public class ProductInfoImpl extends ProductInfoGrpc.ProductInfoImplBase {
&#64;Override
public void addProduct(Product request, StreamObserver<ProductId> responseObserver) {
System.out.println("request.toString() &#61; " &#43; request.toString());
responseObserver.onNext(ProductId.newBuilder().setValue(request.getId()).build());
responseObserver.onCompleted();
}
&#64;Override
public void getProduct(ProductId request, StreamObserver<Product> responseObserver) {
responseObserver.onNext(Product.newBuilder().setId(request.getValue()).setName("三国演义").build());
responseObserver.onCompleted();
}
}

ProductInfoGrpc.ProductInfoImplBase 是根据我们在 proto 文件中定义的 service 自动生成的&#xff0c;我们的 ProductInfoImpl 继承自该类&#xff0c;并且提供了我们给出的方法的具体实现。

以 addProduct 方法为例&#xff0c;参数 request 就是将来客户端调用的时候传来的 Product 对象&#xff0c;返回结果则通过 responseObserver 来完成。我们的方法逻辑很简单&#xff0c;我就把参数传来的 Product 对象打印出来&#xff0c;然后构建一个 ProductId 对象并返回&#xff0c;最后调用 responseObserver.onCompleted(); 表示数据返回完毕。

剩下的 getProduct 方法逻辑就很好懂了&#xff0c;我这里就不再赘述了。

最后&#xff0c;我们再把这个 grpc-server 项目启动起来&#xff1a;

public class ProductInfoServer {
Server server;
public static void main(String[] args) throws IOException, InterruptedException {
ProductInfoServer server &#61; new ProductInfoServer();
server.start();
server.blockUntilShutdown();
}
public void start() throws IOException {
int port &#61; 50051;
server &#61; ServerBuilder.forPort(port)
.addService(new ProductInfoImpl())
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
ProductInfoServer.this.stop();
}));
}
private void stop() {
if (server !&#61; null) {
server.shutdown();
}
}
private void blockUntilShutdown() throws InterruptedException {
if (server !&#61; null) {
server.awaitTermination();
}
}
}

由于我们这里是一个 JavaSE 项目&#xff0c;为了避免项目启动之后就停止&#xff0c;我们这里调用了 server.awaitTermination(); 方法&#xff0c;就是让服务启动成功之后不要停止。


3.3 grpc-client

最后再来看看客户端的调用。首先 grpc-client 项目也是需要依赖 grpc-api 的&#xff0c;然后直接进行方法调用&#xff0c;如下&#xff1a;

public class ProductClient {
public static void main(String[] args) {
ManagedChannel channel &#61; ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext()
.build();
ProductInfoGrpc.ProductInfoBlockingStub stub &#61; ProductInfoGrpc.newBlockingStub(channel);
Product p &#61; Product.newBuilder().setId("1")
.setPrice(399.0f)
.setName("TienChin项目")
.setDescription("SpringBoot&#43;Vue3实战视频")
.build();
ProductId productId &#61; stub.addProduct(p);
System.out.println("productId.getValue() &#61; " &#43; productId.getValue());
Product product &#61; stub.getProduct(ProductId.newBuilder().setValue("99999").build());
System.out.println("product.toString() &#61; " &#43; product.toString());
}
}

小伙伴们看到&#xff0c;这里首先需要和服务端建立连接&#xff0c;给出服务端的地址和端口号即可&#xff0c;usePlaintext() 方法表示不使用 TLS 对连接进行加密&#xff08;默认情况下会使用 TLS 对连接进行加密&#xff09;&#xff0c;生产环境建议使用加密连接。

剩下的代码就比较好懂了&#xff0c;创建 Product 对象&#xff0c;调用 addProduct 方法进行添加&#xff1b;创建 ProductId 对象&#xff0c;调用 getProduct。Product 对象和 ProductId 对象都是根据我们在 proto 中定义的 message 自动生成的。


4. 总结

好啦&#xff0c;一个简单的例子&#xff0c;小伙伴们先对 gRPC 入个门&#xff0c;后面松哥会再整几篇文章跟大家介绍这里边的一些细节。







推荐阅读
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 2018年人工智能大数据的爆发,学Java还是Python?
    本文介绍了2018年人工智能大数据的爆发以及学习Java和Python的相关知识。在人工智能和大数据时代,Java和Python这两门编程语言都很优秀且火爆。选择学习哪门语言要根据个人兴趣爱好来决定。Python是一门拥有简洁语法的高级编程语言,容易上手。其特色之一是强制使用空白符作为语句缩进,使得新手可以快速上手。目前,Python在人工智能领域有着广泛的应用。如果对Java、Python或大数据感兴趣,欢迎加入qq群458345782。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • mapreduce源码分析总结
    这篇文章总结的非常到位,故而转之一MapReduce概述MapReduce是一个用于大规模数据处理的分布式计算模型,它最初是由Google工程师设计并实现的ÿ ... [详细]
  • windows平台使用NSP拦截具体进程的域名解析过程(xFsRedir的代理功能之域名代理)
    byfanxiushu2022-10-17转载或引用请注明原始作者。xFsRedir软件其中之一的功能就是实现了全方位的网络代理,从主机代理,到本地代理 ... [详细]
  • 学习SLAM的女生,很酷
    本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
  • 本文介绍了在Win10上安装WinPythonHadoop的详细步骤,包括安装Python环境、安装JDK8、安装pyspark、安装Hadoop和Spark、设置环境变量、下载winutils.exe等。同时提醒注意Hadoop版本与pyspark版本的一致性,并建议重启电脑以确保安装成功。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 设计模式——模板方法模式的应用和优缺点
    本文介绍了设计模式中的模板方法模式,包括其定义、应用、优点、缺点和使用场景。模板方法模式是一种基于继承的代码复用技术,通过将复杂流程的实现步骤封装在基本方法中,并在抽象父类中定义模板方法的执行次序,子类可以覆盖某些步骤,实现相同的算法框架的不同功能。该模式在软件开发中具有广泛的应用价值。 ... [详细]
  • Java中线程池,你真的了解会用吗
    2019独角兽企业重金招聘Python工程师标准在《深入源码分析Java线程池的实现原理》这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理。在文中 ... [详细]
author-avatar
埃博拉网络科技
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有