关于后端:高性能RPC框架gRPC竟恐怖如斯

87次阅读

共计 9570 个字符,预计需要花费 24 分钟才能阅读完成。

大家好,我是不才陈某~

RPC、gRPC、Thrift、HTTP,大家晓得它们之间的分割和区别么?这些都是面试常考的问题,明天带大家先搞懂 RPC 和 gRPC。

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

不 BB,间接上文章目录:

什么是 RPC?

RPC(Remote Procedure Call Protocol)近程过程调用协定,指标就是让近程服务调用更加简略、通明。

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

为什么要用 RPC?

当咱们的业务越来越多、利用也越来越多时,天然的,咱们会发现有些性能曾经不能简略划分开来或者划分不进去。

此时能够将公共业务逻辑抽离进去,将之组成独立的服务 Service 利用,而原有的、新增的利用都能够与那些独立的 Service 利用 交互,以此来实现残缺的业务性能。

所以咱们急需一种高效的应用程序之间的通信伎俩来实现这种需要,RPC 大显神通的时候来了!

罕用的 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 的调用流程

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

  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 这些步骤都封装起来,让用户对这些细节通明,上面是网上的另外一幅图,感觉高深莫测:

什么是 gRPC?

gRPC 是一个高性能、通用的开源 RPC 框架,其由 Google 2015 年次要面向挪动利用开发并基于 HTTP/2 协定规范而设计,基于 ProtoBuf 序列化协定开发,且反对泛滥开发语言。

因为是开源框架,通信的单方能够进行二次开发,所以客户端和服务器端之间的通信会更加专一于业务层面的内容,缩小了对由 gRPC 框架实现的底层通信的关注。

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

gRPC 的特点

  • 跨语言应用,反对 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)。

gRPC 交互过程

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

简略地说,gRPC 就是在客户端和服务器端开启 gRPC 性能后建设连贯,将设施上配置的订阅数据推送给服务器端。

咱们能够看到整个过程是须要用到 Protocol Buffers 将所须要解决数据的结构化数据在 proto 文件中进行定义。

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 也实用于公司间对性能要求比拟高的场景;
  • 因为其解析性能高,序列化后数据量绝对少,非常适合应用层对象的长久化场景;
  • 次要问题在于其所 反对的语言绝对较少 ,另外因为没有绑定的规范底层传输层协定, 在公司间进行传输层协定的调试工作绝对麻烦。

基于 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 应用程序间所做的比照测试后果。

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

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

从上图可得出如下论断:

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

gRPC 实战

1. 我的项目构造

咱们先看一下我的项目构造:

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

    <artifactId>grpc-demo</artifactId>

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

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

        </plugins>
    </build>
</project>

这外面的 build 其实是为了装置 protobuf 插件,外面其实有 2 个插件咱们须要用到,别离为 protobuf:compile 和 protobuf:compile-javanano,当咱们间接执行时,会生成左侧文件,其中 GreeterGrpc 提供调用接口,Hello 结尾的文件性能次要是对数据进行序列化,而后解决入参和返回值。

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

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

3. 服务端和客户端

文件 HelloWorldClient.java:

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

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

        blockingStub = GreeterGrpc.newBlockingStub(channel);
    }


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

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

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

这个太简略了,就是连贯服务端口,调用 sayHello() 办法。

文件 HelloWorldServer.java:

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

    private int port = 50051;
    private Server server;

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

        Runtime.getRuntime().addShutdownHook(new Thread() {

            @Override
            public void run() {System.err.println("*** shutting down gRPC server since JVM is shutting down");
                HelloWorldServer.this.stop();
                System.err.println("*** server shut down");
            }
        });
    }

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

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

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

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

次要是实现 sayHello() 办法,外面对数据进行了简略解决,入参为“W orld”,返回的是“Hello World”。

4. 启动服务

先启动 Server,返回如下:

再启动 Client,返回如下:

同时 Server 返回如下:

源码地址:https://github.com/lml2007011…

写在最初

这篇文章其实是我去年写的,这次是重新整理,文章具体解说了 RPC 和 gRPC,以及 gRPC 的利用示例,十分全面,前面会再把 Thrift 整理出来。

这个 Demo 看起来很简略,我 TM 竟然搞了大半天,一开始是因为不晓得须要执行 2 个不同的插件来生成 protobuf,认为只须要点击 protobuf:compile 就能够,后果发现 protobuf:compile-javanano 也须要点一下。

最初说一句(别白嫖,求关注)

陈某每一篇文章都是精心输入,曾经写了 3 个专栏,整顿成PDF,获取形式如下:

  1. 《Spring Cloud 进阶》PDF:关注公众号:【Java 后端面试官】回复关键词 Spring Cloud 进阶 获取!
  2. 《Spring Boot 进阶》PDF:关注公众号:【Java 后端面试官】回复关键词 Spring Boot 进阶 获取!
  3. 《Mybatis 进阶》PDF:关注公众号:【Java 后端面试官】回复关键词 Mybatis 进阶 获取!

如果这篇文章对你有所帮忙,或者有所启发的话,帮忙 点赞 在看 转发 珍藏,你的反对就是我坚持下去的最大能源!

正文完
 0