一个高性能框架:gRPC

e9cf58cca2026d47313e7f43a5afdf55.jpeg


RPC、gRPC、Thrift、HTTP,大家知道它们之间的联系和区别么?这些都是面试常考的问题,今天我们带大家先搞懂 RPC 和 gRPC。

在讲述 gRPC 之前,我们需要先搞懂什么是 RPC。

不 BB,直接上文章目录:

1. RPC

RPC(Remote Procedure Call Protocol)远程过程调用协议,目标就是让远程服务调用更加简单、透明。

RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二进制)和通信细节,服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程。

6171fc617912aab05f59878f55f93cef.jpeg

当我们的业务越来越多、应用也越来越多时,自然的,我们会发现有些功能已经不能简单划分开来或者划分不出来。

此时可以将公共业务逻辑抽离出来,将之组成独立的服务 Service 应用,而原有的、新增的应用都可以与那些独立的 Service 应用 交互,以此来完成完整的业务功能。

所以我们急需一种高效的应用程序之间的通讯手段来完成这种需求,RPC 大显身手的时候来了!

1.3 常用的 RPC 框架

  • gRPC :一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。
  • Thrift :thrift 是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。
  • Dubbo :Dubbo 是一个分布式服务框架,以及 SOA 治理方案,Dubbo自2011年开源后,已被许多非阿里系公司使用。
  • Spring Cloud :Spring Cloud 由众多子项目组成,如 Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Consul 等,提供了搭建分布式系统及微服务常用的工具。

要让网络通信细节对使用者透明,我们需要对通信细节进行封装,我们先看下一个 RPC 调用的流程涉及到哪些通信细节:

d57268f86b85bfdb86a87e6d77f6ea0c.jpeg

  1. 服务消费方(client)调用以本地调用方式调用服务;
  2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
  3. client stub找到服务地址,并将消息发送到服务端;
  4. server stub收到消息后进行解码;
  5. server stub根据解码结果调用本地的服务;
  6. 本地服务执行并将结果返回给 server stub;
  7. server stub将返回结果打包成消息并发送至消费方;
  8. client stub接收到消息,并进行解码;
  9. 服务消费方得到最终结果。

RPC 的目标就是要 2~8 这些步骤都封装起来,让用户对这些细节透明,下面是网上的另外一幅图,感觉一目了然:

2. gRPC

gRPC 是一个高性能、通用的开源 RPC 框架,其由 Google 2015 年主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf 序列化协议开发,且支持众多开发语言。

由于是开源框架,通信的双方可以进行二次开发,所以客户端和服务器端之间的通信会更加专注于业务层面的内容,减少了对由 gRPC 框架实现的底层通信的关注。

如下图,DATA 部分即业务层面内容,下面所有的信息都由 gRPC 进行封装。

8c8dddf26b4b2200782025f0f61ca799.jpeg

  • 跨语言使用,支持 C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP 等编程语言;
  • 基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
  • 通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
  • 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能;
  • 安装简单,扩展方便(用该框架每秒可达到百万个RPC)。

2.3 gRPC 交互过程

  • 交换机在开启 gRPC 功能后充当 gRPC 客户端的角色,采集服务器充当 gRPC 服务器角色;
  • 交换机会根据订阅的事件构建对应数据的格式(GPB/JSON),通过 Protocol Buffers 进行编写 proto 文件,交换机与服务器建立 gRPC 通道,通过 gRPC 协议向服务器发送请求消息;
  • 服务器收到请求消息后,服务器会通过 Protocol Buffers 解译 proto 文件,还原出最先定义好格式的数据结构,进行业务处理;
  • 数据处理完后,服务器需要使用 Protocol Buffers 重编译应答数据,通过 gRPC 协议向交换机发送应答消息;
  • 交换机收到应答消息后,结束本次的 gRPC 交互。

简单地说,gRPC 就是在客户端和服务器端开启 gRPC 功能后建立连接,将设备上配置的订阅数据推送给服务器端。

我们可以看到整个过程是需要用到 Protocol Buffers 将所需要处理数据的结构化数据在 proto 文件中进行定义。

2.4 Protocol Buffers

你可以理解 ProtoBuf 是一种更加灵活、高效的数据格式 ,与 XML、JSON 类似,在一些高性能且对响应速度有要求的数据传输场景非常适用。

ProtoBuf 在 gRPC 的框架中主要有三个作用:定义数据结构、定义服务接口,通过序列化和反序列化方式提升传输效率。

为什么 ProtoBuf 会提高传输效率 呢?

我们知道使用 XML、JSON 进行数据编译时,数据文本格式更容易阅读,但进行数据交换时,设备就需要耗费大量的 CPU 在 I/O 动作上,自然会影响整个传输速率。

Protocol Buffers 不像前者,它会将字符串进行序列化后再进行传输,即二进制数据

可以看到其实两者内容相差不大,并且内容非常直观,但是 Protocol Buffers 编码的内容只是提供给操作者阅读的,实际上传输的并不会以这种文本形式,而是序列化后的二进制数据,字节数会比 JSON、XML 的字节数少很多,速率更快。

gPRC 如何支撑跨平台,多语言 呢 ?

Protocol Buffers 自带一个编译器也是一个优势点,前面提到的 proto 文件就是通过编译器进行编译的,proto 文件需要编译生成一个类似库文件,基于库文件才能真正开发数据应用。

具体用什么编程语言编译生成这个库文件呢?由于现网中负责网络设备和服务器设备的运维人员往往不是同一组人,运维人员可能会习惯使用不同的编程语言进行运维开发,那么 Protocol Buffers 其中一个优势就能发挥出来——跨语言。

从上面的介绍,我们得出在编码方面 Protocol Buffers 对比 JSON、XML 的优点:

  • 标准的 IDL 和 IDL 编译器,这使得其对工程师非常友好;
  • 序列化数据非常简洁,紧凑,与 XML 相比,其序列化之后的数据量约为 1/3 到 1/10;
  • 解析速度非常快,比对应的 XML 快约 20-100 倍;
  • 提供了非常友好的动态库,使用非常简单,反序列化只需要一行代码。

Protobuf 也有其局限性:

  • 由于 Protobuf 产生于 Google,所以目前其仅支持 Java、C++、Python 三种语言
  • Protobuf 支持的数据类型相对较少,不支持常量类型;
  • 由于其设计的理念是纯粹的展现层协议(Presentation Layer),目前并没有一个专门支持 Protobuf 的 RPC 框架。

Protobuf 适用场景:

  • Protobuf 具有广泛的用户基础,空间开销小以及高解析性能是其亮点,非常适合于公司内部的对性能要求高的 RPC 调用
  • 由于 Protobuf 提供了标准的 IDL 以及对应的编译器,其 IDL 文件是参与各方的非常强的业务约束;
  • Protobuf 与传输层无关,采用 HTTP 具有良好的跨防火墙的访问属性,所以 Protobuf 也适用于公司间对性能要求比较高的场景;
  • 由于其解析性能高,序列化后数据量相对少,非常适合应用层对象的持久化场景;
  • 主要问题在于其所支持的语言相对较少 ,另外由于没有绑定的标准底层传输层协议,在公司间进行传输层协议的调试工作相对麻烦。

2.5 基于 HTTP 2.0 标准设计

除了 Protocol Buffers 之外,从交互图中和分层框架可以看到, gRPC 还有另外一个优势——它是基于 HTTP 2.0 协议的。

由于 gRPC 基于 HTTP 2.0 标准设计,带来了更多强大功能,如多路复用、二进制帧、头部压缩、推送机制。

这些功能给设备带来重大益处,如节省带宽、降低 TCP 连接次数、节省 CPU 使用等,gRPC 既能够在客户端应用,也能够在服务器端应用,从而以透明的方式实现两端的通信和简化通信系统的构建。

HTTP 1.X 定义了四种与服务器交互的方式,分别为 GET、POST、PUT、DELETE,这些在 HTTP 2.0 中均保留,我们看看 HTTP 2.0 的新特性 :双向流、多路复用、二进制帧、头部压缩。

与采用文本格式的 JSON 相比,采用二进制格式的 protobuf 在速度上可以达到前者的 5 倍!

Auth0 网站所做的性能测试结果显示,protobuf 和 JSON 的优势差异在 Java、Python 等环境中尤为明显,下图是 Auth0 在两个 Spring Boot 应用程序间所做的对比测试结果。

38282b7bd53a78bd3147014f644c29eb.jpeg

结果显示,protobuf 所需的请求时间最多只有 JSON 的 20% 左右,即速度是其 5 倍!

下面看一下性能和空间开销对比。

77c8eefdb821b71cb14cc46d4aff3108.png

从上图可得出如下结论:

  • XML序列化(Xstream)无论在性能和简洁性上比较差。
  • Thrift 与 Protobuf 相比在时空开销方面都有一定的劣势。
  • Protobuf 和 Avro 在两方面表现都非常优越。

3.1 项目结构

我们先看一下项目结构:

3.2 生成 protobuf 文件

文件 helloworld.proto:

syntax =  "proto3";

option java_multiple_files =  true;
option java_package =  "io.grpc.examples.helloworld";
option java_outer_classname =  "HelloWorldProto";
option objc_class_prefix =  "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user 's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

这里提供了一个 SayHello() 方法,然后入参为 HelloRequest,返回值为 HelloReply,可以看到 proto 文件只定义了入参和返回值的格式,以及调用的接口,至于接口内部的实现,该文件完全不用关心。

文件 pom.xml:

<?xml&nbsp;version="1.0"&nbsp;encoding="UTF-8"?>
<project&nbsp;xmlns="http://maven.apache.org/POM/4.0.0"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xsi:schemaLocation="http://maven.apache.org/POM/4.0.0&nbsp;http://maven.apache.org/xsd/maven-4.0.0.xsd">
&nbsp;&nbsp;&nbsp;&nbsp; <parent>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <artifactId>rpc-study </artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <groupId>org.example </groupId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <version>1.0-SNAPSHOT </version>
&nbsp;&nbsp;&nbsp;&nbsp; </parent>
&nbsp;&nbsp;&nbsp;&nbsp; <modelVersion>4.0.0 </modelVersion>

&nbsp;&nbsp;&nbsp;&nbsp; <artifactId>grpc-demo </artifactId>

&nbsp;&nbsp;&nbsp;&nbsp; <dependencies>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <dependency>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <groupId>io.grpc </groupId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <artifactId>grpc-netty-shaded </artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <version>1.14.0 </version>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </dependency>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <dependency>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <groupId>io.grpc </groupId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <artifactId>grpc-protobuf </artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <version>1.14.0 </version>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </dependency>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <dependency>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <groupId>io.grpc </groupId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <artifactId>grpc-stub </artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <version>1.14.0 </version>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </dependency>
&nbsp;&nbsp;&nbsp;&nbsp; </dependencies>

&nbsp;&nbsp;&nbsp;&nbsp; <build>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <extensions>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <extension>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <groupId>kr.motd.maven </groupId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <artifactId>os-maven-plugin </artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <version>1.5.0.Final </version>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </extension>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </extensions>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <plugins>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <plugin>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <groupId>org.xolstice.maven.plugins </groupId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <artifactId>protobuf-maven-plugin </artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <version>0.5.1 </version>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <configuration>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier} </protocArtifact>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <pluginId>grpc-java </pluginId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.14.0:exe:${os.detected.classifier} </pluginArtifact>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </configuration>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <executions>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <execution>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <goals>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <goal>compile </goal>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <goal>compile-custom </goal>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </goals>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </execution>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </executions>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </plugin>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <plugin>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <groupId>org.apache.maven.plugins </groupId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <artifactId>maven-compiler-plugin </artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <configuration>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <source>6 </source>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <target>6 </target>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </configuration>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </plugin>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </plugins>
&nbsp;&nbsp;&nbsp;&nbsp; </build>
</project>

这里面的 build 其实是为了安装 protobuf 插件,里面其实有 2 个插件我们需要用到,分别为 protobuf:compile 和 protobuf:compile-javanano,当我们直接执行时,会生成左侧文件,其中 GreeterGrpc 提供调用接口,Hello 开头的文件功能主要是对数据进行序列化,然后处理入参和返回值。

可能有同学会问,你把文件生成到 target 中,我想放到 main.src 中,你可以把这些文件 copy 出来,或者也可以通过工具生成:

  • 下载 protoc.exe 工具 ,下载地址:https://github.com/protocolbuffers/protobuf/releases
  • 下载 protoc-gen-grpc 插件, 下载地址: http://jcenter.bintray.com/io/grpc/protoc-gen-grpc-java/

3.3 服务端和客户端

文件 HelloWorldClient.java:

public&nbsp; class&nbsp;HelloWorldClient&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp; final&nbsp;ManagedChannel&nbsp;channel;
&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp; final&nbsp;GreeterGrpc.GreeterBlockingStub&nbsp;blockingStub;
&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp; static&nbsp; final&nbsp;Logger&nbsp;logger&nbsp;=&nbsp;Logger.getLogger(HelloWorldClient .class.getName());

&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp;HelloWorldClient(String&nbsp;host,int&nbsp;port){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;channel&nbsp;=&nbsp;ManagedChannelBuilder.forAddress(host,port)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.usePlaintext( true)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.build();

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;blockingStub&nbsp;=&nbsp;GreeterGrpc.newBlockingStub(channel);
&nbsp;&nbsp;&nbsp;&nbsp;}


&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp;void&nbsp;shutdown()&nbsp;throws&nbsp;InterruptedException&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;channel.shutdown().awaitTermination( 5,&nbsp;TimeUnit.SECONDS);
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp;&nbsp;void&nbsp;greet(String&nbsp;name){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HelloRequest&nbsp;request&nbsp;=&nbsp;HelloRequest.newBuilder().setName(name).build();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HelloReply&nbsp;response;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response&nbsp;=&nbsp;blockingStub.sayHello(request);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp; catch&nbsp;(StatusRuntimeException&nbsp;e)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.log(Level.WARNING,&nbsp; "RPC&nbsp;failed:&nbsp;{0}",&nbsp;e.getStatus());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info( "Message&nbsp;from&nbsp;gRPC-Server:&nbsp;"+response.getMessage());
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;throws&nbsp;InterruptedException&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HelloWorldClient&nbsp;client&nbsp;=&nbsp; new&nbsp;HelloWorldClient( "127.0.0.1", 50051);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;user&nbsp;=&nbsp; "world";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if&nbsp;(args.length&nbsp;>&nbsp; 0){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user&nbsp;=&nbsp;args[ 0];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;client.greet(user);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} finally&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;client.shutdown();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
}

这个太简单了,就是连接服务端口,调用 sayHello() 方法。

文件 HelloWorldServer.java:

public&nbsp; class&nbsp;HelloWorldServer&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp; static&nbsp; final&nbsp;Logger&nbsp;logger&nbsp;=&nbsp;Logger.getLogger(HelloWorldServer .class.getName());

&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp; int&nbsp;port&nbsp;=&nbsp; 50051;
&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp;Server&nbsp;server;

&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp;void&nbsp;start()&nbsp;throws&nbsp;IOException&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;server&nbsp;=&nbsp;ServerBuilder.forPort(port)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.addService( new&nbsp;GreeterImpl())
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.build()
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.start();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info( "Server&nbsp;started,&nbsp;listening&nbsp;on&nbsp;"&nbsp;+&nbsp;port);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Runtime.getRuntime().addShutdownHook( new&nbsp;Thread()&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp;void&nbsp;run()&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.err.println( "***&nbsp;shutting&nbsp;down&nbsp;gRPC&nbsp;server&nbsp;since&nbsp;JVM&nbsp;is&nbsp;shutting&nbsp;down");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HelloWorldServer. this.stop();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.err.println( "***&nbsp;server&nbsp;shut&nbsp;down");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp;void&nbsp;stop()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if&nbsp;(server&nbsp;!=&nbsp; null)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;server.shutdown();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;block&nbsp;一直到退出程序
&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp;void&nbsp;blockUntilShutdown()&nbsp;throws&nbsp;InterruptedException&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if&nbsp;(server&nbsp;!=&nbsp; null)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;server.awaitTermination();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;throws&nbsp;IOException,&nbsp;InterruptedException&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; final&nbsp;HelloWorldServer&nbsp;server&nbsp;=&nbsp; new&nbsp;HelloWorldServer();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;server.start();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;server.blockUntilShutdown();
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;实现&nbsp;定义一个实现服务接口的类
&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp; class&nbsp;GreeterImpl&nbsp;extends&nbsp;GreeterGrpc.GreeterImplBase&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp;void&nbsp;sayHello(HelloRequest&nbsp;req,&nbsp;StreamObserver<HelloReply>&nbsp;responseObserver)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HelloReply&nbsp;reply&nbsp;=&nbsp;HelloReply.newBuilder().setMessage(( "Hello&nbsp;"&nbsp;+&nbsp;req.getName())).build();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;responseObserver.onNext(reply);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;responseObserver.onCompleted();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println( "Message&nbsp;from&nbsp;gRPC-Client:"&nbsp;+&nbsp;req.getName());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println( "Message&nbsp;Response:"&nbsp;+&nbsp;reply.getMessage());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
}

主要是实现 sayHello() 方法,里面对数据进行了简单处理,入参为 “W &nbsp;orld”,返回的是 “Hello World”。

先启动 Server,返回如下:

44ba71f57f7de6281ae51ad30fac3486.jpeg

再启动 Client,返回如下:

同时 Server返回如下:

34f8a65789fdb73b2cc01f932be132a5.jpeg

Git 地址:https://github.com/lml200701158/rpc-study

4. 写在最后

这篇文章其实是我去年写的,这次是重新整理,文章详细讲解了 RPC 和 gRPC,以及 gRPC 的应用示例 ,非常全面,后面会再把 Thrift 整理出来。

这个 Demo 看起来很简单,我 TM 居然搞了大半天,一开始是因为不知道需要执行 2 个不同的插件来生成 protobuf,以为只需要点击 protobuf:compile 就可以,结果发现 protobuf:compile-javanano 也需要点一下。

还有就是我自己喜欢作,感觉通过插件生成 protobuf 不完美,我想通过自己下载的插件,手动生成 protobuf 文件,结果手动生成的没有搞定,自动生成的方式也不可用,搞了半天才发现是缓存的问题,最后直接执行 “Invalidate Caches / Restart” 才搞定。

应征了一句话“no zuo no die”,不过这个过程还是需要经历的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340