Spring Cloud与gRPC

本文探讨了gRPC在SpringCloud微服务架构中的作用,详细解释了gRPC如何通过改进序列化、采用HTTP2协议和利用Netty框架提升内部服务调用的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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之后,保持长连接,这样就做到了连接复用,从而极大提高了数据交互的效率。
### Spring Cloud 中集成 gRPC 服务 #### Maven 配置 为了在 Spring Cloud 项目中成功集成 gRPC,首先需要设置项目的 `pom.xml` 文件来引入必要的依赖项。这通常涉及到添加特定于 gRPC Protocol Buffers 编译器插件的支持[^4]。 ```xml <dependencies> <!-- gRPC dependency --> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty-shaded</artifactId> <version>${grpc.version}</version> </dependency> <!-- Protobuf Java runtime library and compiler plugin --> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>${protobuf.version}</version> </dependency> <!-- Other dependencies... --> </dependencies> <!-- Plugin configuration for compiling .proto files into Java classes --> <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:${protobuf.version}: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> </build> ``` #### 定义 Proto 文件 创建 `.proto` 文件用于描述服务接口以及消息结构。这些文件应当放置在一个名为 `common` 的模块内以便被其他模块所共享使用。 ```protobuf syntax = "proto3"; option java_multiple_files = true; package com.example; service ExampleService { rpc SayHello (HelloRequest) returns (HelloResponse); } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } ``` #### YAML 配置 对于服务器端而言,在应用程序的配置文件 (`application.yml`) 中指定监听地址其他必要参数;而对于客户端,则需指明目标主机名端口,并可选地设定连接选项如协商类型为纯文本模式以简化开发过程。 **Server-side Configuration** ```yaml server: port: 9090 grpc: server: port: ${server.port} ``` **Client-side Configuration** ```yaml spring: application: name: example-client grpc: negotiation-type: plaintext name-resolver-provider: dns-unresolved enable-channelz: false keepalive-time: 1h max-inbound-message-size: 4MB ``` #### 实现 Service 类 编写具体的业务逻辑处理程序并将其注册到 gRPC 服务器上作为远程调用的目标对象实例之一。 ```java @Service public class ExampleServiceImpl extends ExampleGrpc.ExampleImplBase { @Override public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) { String greeting = new StringBuilder().append("Hello ").append(request.getName()).toString(); HelloResponse reply = HelloResponse.newBuilder() .setMessage(greeting).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } } ``` #### 启动 Server Client 应用 最后一步是在各自的应用入口处启动相应的 gRPC 组件——通过继承自 `SpringBootServletInitializer` 或者直接利用 `@SpringBootApplication` 注解标记主类的方式完成初始化工作。 ```java @SpringBootApplication @EnableDiscoveryClient public class GrpcApplication { private static final Logger logger = LoggerFactory.getLogger(GrpcApplication.class); public static void main(String[] args) throws Exception { SpringApplication.run(GrpcApplication.class, args); } @Bean public ServerFactoryBean createGrpcServer() { return NettyServerBuilder.forPort(port) .addService(new ExampleServiceImpl()) .build(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值