Spring Cloud为什么需要gRPC

微服务架构的风格,是每个微服务运行在自己的进程中,并使用轻量级的通信机制,通常是HTTP RESTFUL API。这些服务是围绕业务能力来划分的、构建的,并通过完全自动化的机制来部署。这些服务可以用不同的语言来编写,以及不同的数据存储技术,以保证最低限度的集中式管理。

但是在通常情况下,HTTP不会开启KeepAlive功能,即为短连接,每次请求都需要建立TCP连接,这使得其在耗时非常低效。

对外提供RESTAPI是可以理解的,但内部服务之间进行调用若还是采用HTTP就会显得性能低下,Spring Cloud默认使用Feign进行内部服务调用,而Feign底层使用HTTP协议进行服务之间的调用。

Spring Cloud官方没有提供RESTAPI之外的服务调用方案,现有的更高效的内部服务调用方案是GRPC。

GRPC相比HTTP/JSON客户端有如下优势:

  • GRPC采用了Proto Buffer作为序列化工具,这比采用JSON方式进行序列化性能提高了不少。
  • GRPC采用了HTTP2协议,进行了头部信息压缩,对连接进行了复用,减少了TCP的连接次数。
  • GRPC采用Netty做为IO处理框架,提高了性能。

gRPC简介

是谷歌开源的一个高性能的、通用的RPC框架。和其他RPC一样,客户端应用程序可以直接调用远程服务的方法,就好像调用本地方法一样。它隐藏了底层的实现细节,包括序列化(XML、JSON、二进制)、数据传输(TCP、HTTP、UDP)、反序列化等,开发人员只需要关自业务本身,而不需要关注RPC的技术细节。

与其他RPC框架一样,gRPC也遵循定义服务(类似于定义接口的思想)。gRPC客户端通过定义方法名、方法参数和返回类型来声明一个可以被远程调用的接口方法。由服务端实现客户端定义的接口方法,并运行一个gRPC服务来处理gPRC
客户端调用。注意,gRPC客户端和服务端共用一个接口方法。

gRPC客户端和服务端具有运行环境和开发语言无关性。

gRPC的核心概念

服务定义

和其他RPC框架一样,gRPC遵循服务定义(Service definition)的思想。gRPC默认情况下使用protocol buffers作为接口定义语言(IDL),这种接口语言描述了服务接口和负载消息的结构。

使用Protocol Buffers的Maven插件

使用Protocol Buffers需要掌握额外的接口定义语言(IDL),还需要Protocol Buffers编译组件的支持。

使用最广泛、最方便的Maven插件方式来使用Protocol Buffers。

依赖

<dependency>
   <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.5.1</version>
</dependency>

安装Protocol Buffers的Maven插件,具体可参考https://www.xolstice.org/protobuf-maven-plugin中的介绍。

<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>-->
   <!--<extensions>true</extensions>-->

   <!--<executions>-->
   <!--<execution>-->
   <!--<goals>-->
   <!--<goal>compile</goal>-->
   <!--<goal>test-compile</goal>-->
   <!--</goals>-->
   <!--<configuration>-->
   <!--<protocArtifact>com.google.protobuf:protoc:3.4.0:exe:${os.detected.classifier}</protocArtifact>-->
   <!--</configuration>-->
   <!--</execution>-->
   <!--</executions>-->
   <!--</plugin>-->

   <!--</plugins>-->

   <!--<extensions>-->
   <!--<extension>-->
   <!--<groupId>kr.motd.maven</groupId>-->
   <!--<artifactId>os-maven-plugin</artifactId>-->
   <!--<version>1.4.1.Final</version>-->
   <!--</extension>-->
   <!--</extensions>-->
   <!--<plugins>-->
   <!--<plugin>-->
   <!--<groupId>org.xolstice.maven.plugins</groupId>-->
   <!--<artifactId>protobuf-maven-plugin</artifactId>-->
   <!--<version>0.5.0</version>-->
   <!--<configuration>-->
   <!--<protocArtifact>-->
   <!--com.google.protobuf:protoc:3.1.0:exe:${os.detected.classifier}-->
   <!--</protocArtifact>-->
   <!--<pluginId>grpc-java</pluginId>-->
   <!--<pluginArtifact>-->
   <!--io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}-->
   <!--</pluginArtifact>-->
   <!--</configuration>-->
   <!--<executions>-->
   <!--<execution>-->
   <!--<goals>-->
   <!--<goal>compile</goal>-->
   <!--<goal>compile-custom</goal>-->
   <!--</goals>-->
   <!--</execution>-->
   <!--</executions>-->
   <!--</plugin>-->
   <!--</plugins>-->



   <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>
               <!--源IDL文件,如下为默认值-->
               <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
               <!--默认值-->
               <!--<outputDirectory>${project.build.directory}/generated-sources/protobuf/java</outputDirectory>-->
               <!--配置编译后输出的文件地址-->
               <outputDirectory>${project.build.directory}/generated-sources/protobuf/java</outputDirectory>
               <!--设置是否在生成java文件之前清空outputDirectory的文件,默认值为true,设置为false时也会覆盖同名文件-->
               <clearOutputDirectory>true</clearOutputDirectory>
               <protocArtifact>
                   com.google.protobuf:protoc:3.5.1:exe:${os.detected.classifier}
               </protocArtifact>
               <pluginId>grpc-java</pluginId>
               <!--编译器地址,用artifact、groupId、version来确定唯一性f-->
               <pluginArtifact>
                   io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
               </pluginArtifact>
           </configuration>
           <executions>
               <execution>
                   <goals>
                       <goal>compile</goal>
                       <goal>compile-custom</goal>
                   </goals>
               </execution>
           </executions>
       </plugin>
   </plugins>
</build>

实例代码如下:

		PersonModel.person forezp = PersonModel.person.newBuilder()
                .setId(1)
                .setName("bzb")
                .setEmail("15851485932@163.com")
                .build();

        for (byte b : forezp.toByteArray()){
            System.out.print(b);
        }

        System.out.println("\n bytes长度" + forezp.toByteString().size());

        System.out.println("forezp byte结束==============");

        System.out.println("forezp 反序列化对象开始================");
        PersonModel.person forezpCopy = null;

        try {
            forezpCopy = PersonModel.person.parseFrom(forezp.toByteArray());
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }

        System.out.println(forezpCopy.toString());
        System.out.println("forezp 反序列化对象结束================");

采用Protobuf序列化的数据可读性很差,但体积很小,所有能大大提高传输效率。

Proto Buffer语法介绍

  1. 定义一个Java的List集合类的变量,需要在变量前使用repeated关键字,如下所示:
message ComplexObject {
	repeated string sons = 4; // List列表
	repeated Person persons = 6; // 复杂对象列表
}
2. 定义一个Map类型,使用map关键字,如下:

message ComplexObject {
map<string, Person> map = 8;
}

3. 定义枚举类,使用enum关键字,如下:

enum Gender {
MAN = 0;
WOMAN = 1;
}


可以参考https://developers.google.com/protocol-buffers/docs/proto3
# gRPC基于HTTP2
HTTP2即超文本传输协议版本2,HTTP2通过对头部字段进行压缩,在客户端和服务端建立连接之后允许双向传输数据来提供传输效率、减少延迟。HTTP2还允许在客户端和服务端未建立连接的情况下由服务端发送消息到客户端。

HTTP2的一些概念和特性:
1. 二进制流
HTTP2是一个二进制协议,对帧(Frame)的使用更为简单。帧是数据传输的最小单位。每个帧都属于一个特定的流(stream)。一个请求或响应可能由多个帧组成。HTTP2的请求和响应以帧为单位进行了划分,所有的帧都在流上传输,帧可以不按照顺序发送,然后在服务端重新按照头部字段Stream Identifier进行排序。帧具有如下的公共字段:Type, Length, Flags, Stream Identifier, Frame Payload。
2. 多路复用
客户端和服务端的连接可以包含多个并发的流,流被客户端和服务端共享,可以让客户端和服务端同时发生消息,也可以单方发生消息,也可以被任意一方关闭。流的多路复用,提高了连接的利用率。
3. 头压缩
HTTP是一种无状态的协议,客户端只有发生cookies才能让服务器记住客户端 的一些状态,cookies可能包含的消息比较复杂,并且每次请求都需要携带cookies,这会严重拖累HTTP请求的速度,所以头压缩是非常有必要的。
4. 服务器推送
也称为缓存推送。它可以让服务器在客户端不发生请求的情况下,主动将资源推送给客户端,并让客户端缓存起来,从而但需要该资源时,直接从缓存中取。服务器推送需要客户端主动开启,开启之后,客户端可以随时终止该推送服务(发送一个RST_STREAM帧来终止)。

gRPC基于HTTP2,继承了HTTP2的优点,带来诸如双向、流控、头部压缩、单TCP连接上的多路复用请求等特性。

# gRPC基于Netty进行IO处理
Netty是由JBOSS开源的一个异步事件驱动的Java网络应用框架,用于快速开发可维护的、高性能的协议客户端和服务端。

gRPC的Java版本用Netty做为NIO框架。gRPC的客户端和服务端均使用Netty Channel作为数据传输通道,使用Proto Buffer作为数据序列化和反序列化的工具。每个请求被封装成符合HTTP2规范的数据流。客户端连接上服务端的Channel之后,保持长连接,这样就做到了连接复用,从而极大提高了数据交互的效率。
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐