Grpc+Spring整合

695 阅读10分钟

「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战

GRPC简介

官网 www.grpc.io/

gRPC 官方文档中文版 doc.oschina.net/grpc

RPC 框架的目标就是让远程服务调用更加简单、透明,其负责屏蔽底层的传输方式(TCP/UDP)、序列化方式(XML/Json)和通信细节。服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程。

gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.

在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。 grpc_concept_diagram_00 gRPC 客户端和服务端可以在多种环境中运行和交互 - 从 google 内部的服务器到你自己的笔记本,并且可以用任何 gRPC 支持的语言来编写。所以,你可以很容易地用 Java 创建一个 gRPC 服务端,用 Go、Python、Ruby 来创建客户端。

gRPC 的功能优点:

● 高兼容性、高性能、使用简单

gRPC 的组成部分:

● 使用 http2 作为网络传输层

● 使用 protobuf 这个高性能的数据包序列化协议

● 通过 protoc gprc 插件生成易用的 SDK

protobuf序列化协议

ProtoBuf(Protocol Buffers)是一种跨平台、语言无关、可扩展的序列化结构数据的方法,可用于网络数据交换及存储。 在序列化结构化数据的机制中,ProtoBuf是灵活、高效、自动化的,相对常见的XML、JSON,描述同样的信息,ProtoBuf序列化后数据量更小 (在网络中传输消耗的网络流量更少)、序列化/反序列化速度更快、更简单。 一旦定义了要处理的数据的数据结构之后,就可以利用ProtoBuf的代码生成工具生成相关的代码。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言(proto3支持C++, Java, Python, Go, Ruby, Objective-C, C#)或从各种不同流中对你的结构化数据轻松读写。

  • ProtoBuf 协议的工作流程 792f4da0b8ae888f3a98043e90a6f2a

在开发 gRPC 应用程序时,先要定义服务接口,其中应包含如下信息:消费者消费服务的方式、消费者能够远程调用的方法以及调用这些方法所使用的参数和消息格式等。在服务定义中所使用的语言叫作接口定义语言(interface definition language,IDL)。 借助服务定义,可以生成服务器端代码,也就是服务器端骨架 (这里的“骨架”和“存根”都是代理。服务器端代理叫作“骨架”(skeleton),客户端代理叫作“存根”(stub)。),它通过提供低层级的通信抽象简化了服务器端的逻辑。同时,还可以生成客户端代码,也就是客户端存根,它使用抽象简化了客户端的通信,为不同的编程语言隐藏了低层级的通信。就像调用本地函数那样,客户端能够远程调用我们在服务接口定义中所指定的方法。底层的 gRPC 框架处理所有的复杂工作,通常包括确保严格的服务契约、数据序列化、网络通信、认证、访问控制、可观察性等。

为了理解 gRPC 的基本概念,我们来看一个使用 gRPC 实现微服务的实际场景。假设我们正在构建一个在线零售应用程序,该应用程序由多个微服务组成。 如图 所示,假设我们要构建一个微服务来展现在线零售应用程序中可售商品的详情。例如,将 ProductInfo 服务建模为 gRPC 服务,通过网络对外暴露。 9e19b012025e87dcf3dec2498578305 典型的序列化和反序列化过程往往需要如下组件: ● IDL(Interface description language)文件:参与通讯的各方需要对通讯的内容需要做相关的约定(Specifications)。为了建立一个与语言和平台无关的约定,这个约定需要采用与具体开发语言、平台无关的语言来进行描述。这种语言被称为接口描述语言(IDL),采用IDL撰写的协议约定称之为IDL文件。 ● IDL Compiler:IDL文件中约定的内容为了在各语言和平台可见,需要有一个编译器,将IDL文件转换成各语言对应的动态库。 ● Stub/Skeleton Lib:负责序列化和反序列化的工作代码。Stub是一段部署在分布式系统客户端的代码,一方面接收应用层的参数,并对其序列化后通过底层协议栈发送到服务端,另一方面接收服务端序列化后的结果数据,反序列化后交给客户端应用层;Skeleton部署在服务端,其功能与Stub相反,从传输层接收序列化参数,反序列化后交给服务端应用层,并将应用层的执行结果序列化后最终传送给客户端Stub。 ● Client/Server:指的是应用层程序代码,他们面对的是IDL所生存的特定语言的class或struct。 ● 底层协议栈和互联网:序列化之后的数据通过底层的传输层、网络层、链路层以及物理层协议转换成数字信号在互联网中传递。

gRPC核心概念

image 上图中列出了 gRPC 基础概念及其关系图。其中包括:Service(定义)、RPC、API、Client、Stub、Channel、Server、Service(实现)、ServiceBuilder 等。

  • 为什么会选用 http2 作为 gRPC 的传输协议? 除了速度之外,最大的原因就是最大程度的服务兼容性。因为 gRPC 基于 http2 协议,加之市面上主流的代理工具也都支持 http2 协议,所以自然就支持 gRPC 了。
HPPT1.0和HTTP2.0的区别

image ● length定义了整个frame的开始到结束, ● type定义frame的类型(一共10种); ● flags用bit位定义一些重要的参数; ● stream id用作流控制,一个request对应一个stream并分配一个id,这样一个连接上可以有多个stream,每个stream的frame可以随机的混杂在一起,接收方可以根据stream id将frame再归属到各自不同的request里面; ● payload就是request的正文了; 虽然看上去协议的格式和http1.x完全不同了,实际上http2.0并没有改变http1.x的语义,只是把原来http1.x的header和body部分用frame重新封装了一层而已。调试的时候浏览器甚至会把http2.0的frame自动还原成http1.x的格式。

对于http1.x来说,是通过设置tcp segment里的reset flag来通知对端关闭连接的。这种方式会直接断开连接,下次再发请求就必须重新建立连接。http2.0引入RST_STREAM类型的frame,可以在不断开连接的前提下取消某个request的stream,表现更好。

可以看到,对于序列化协议来说,使用方只需要关注业务对象本身,即 idl (Interface description language)定义,序列化和反序列化的代码只需要通过工具生成即可。

4种gRPC请求的和响应类型

gRPC主要有4种请求和响应模式,分别是简单模式(Simple RPC)、服务端流式(Server-side streaming RPC)、客户端流式(Client-side streaming RPC)、和双向流式(Bidirectional streaming RPC)。 ● 简单模式(Simple RPC):客户端发起请求并等待服务端响应。 ● 服务端流式(Server-side streaming RPC):客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。 ● 客户端流式(Client-side streaming RPC):与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。 ● 双向流式(Bidirectional streaming RPC):双方使用读写流去发送一个消息序列,两个流独立操作,双方可以同时发送和同时接收。

ProtoBuf 消息定义
  • proto3版本
syntax = "proto3"; // 协议版本(proto3中,在第一行非空白非注释行,必须写:syntax = "proto3";)
package protocobuff_Demo;
// 关注1:包名,防止不同 .proto 项目间命名 发生冲突

option java_package = "om.jackgreek.grpcinterface";//// 作用:指定生成的类应该放在什么Java包名下
option java_outer_classname = "Demo";//作用:生成对应.java 文件的类名(不能跟下面message的类名相同)
// 关注2:option选项,作用:影响 特定环境下 的处理方式

// 关注3:消息模型 作用:真正用于描述 数据结构
// 下面详细说明
// 生成 Person 消息对象(包含多个字段,下面详细说明)
message Person {
     string name = 1;//(proto3消息定义时,移除了 “required”、 “optional” :)
     int32 id = 2;//(proto3消息定义时,移除了 “required”、 “optional” :)
     string email = 3;//(proto3消息定义时,移除了 “required”、 “optional” :)

    enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
    }

    message PhoneNumber {
        string number = 1;
        PhoneType type = 2 ;//(proto3消息定义时,移除了 default 选项:)
    }

    repeated PhoneNumber phone = 4;
}

message AddressBook {
    repeated Person person = 1;
}

  • 安装 ProtoBuf 编译器 protoc

protoc下载地址:github.com/protocolbuf…

bed621017daeffef8af9f37d783a5ad 解压压缩包,并配置环境变量

生成文件
protoc --java_out=./ ./helloworld.proto

在helloworld.proto文件处该命令,并添加了两个参数:java_out,定义./为Java代码的输出目录;而./helloworld.proto是.proto文件所在目录。 (编译器为每个.proto文件里的每个消息类型生成一个.java文件&一个Builder类 (Builder类用于创建消息类接口)) c1a486a05653ab88ffaf80e88166458

拓展

为了更方便的使用gRPC,包括protoc的命令,针对不同语言有下面额外的方法: protoc --java_out 305d2c9cb03a62f0268d814748043e1

  • Java:protoc --java_out

  • Go: protoc --go_out

整合springboot

image

项目名称模块功能
spring-grpc父工程
grpc-interface主要放生成好的接口文件
grpc-serverGrpc服务端
grpc-clientGrpc客户端
在idea添加 Protobuf插件方便proto文件编写
image
  • grpc-interface 导入grpc的依赖并在maven中添加protobuf:protoc插件方便代码生成
<dependencies>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>1.37.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.37.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.37.0</version>
        </dependency>
        <dependency> <!-- necessary for Java 9+ -->
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>6.0.53</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>


    <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:3.17.3:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.37.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

  • 编写proto文件
syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.jackgreek.grpcinterface";
option java_outer_classname = "HelloWorldProto";

// The greeting service definition.
service Simple {
    // 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;
}

image.png

注意:proto文件的放到位置,以便maven的rotobuf插件生成文件 1.mvn protobuf:compile 生成消息体类文件 2.mvn protobuf:compile-custom 生成XXXGrpc类文件 image 将生成后的文件复制到grpc-interface项目中 image.png

  • grpc-server 添加grpc-server-spring-boot-startergrpc-interface依赖
 <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-server-spring-boot-starter</artifactId>
            <version>2.12.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.jackgreek</groupId>
            <artifactId>grpc-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

编写服务的实现在GrpcServerService类上加上@GrpcService

@GrpcService
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {

    @Override
    public void sayHello(HelloRequest request,
                         StreamObserver<HelloReply> responseObserver) {
        HelloReply reply = HelloReply.newBuilder().setMessage("你好, " + request.getName() + ", " + new Date()).build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}

编写配置文件

spring.application.name=grpc-server
server.port=8081
## 配置grpc发布的端口
grpc.server.port=9091
  • grpc-client 客户端的依赖和服务的有点不同
     <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>2.12.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.jackgreek</groupId>
            <artifactId>grpc-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

编写配置文件

spring.application.name=grpc-client
server.port=8082
grpc.client.grpc-server.address=static://127.0.0.1:9091
grpc.client.grpc-server.enable-keep-alive=true
grpc.client.grpc-server.keep-alive-without-calls=true
grpc.client.grpc-server.negotiation-type=plaintext

使用@GrpcClient注解调用服务端接口

@Service
public class GrpcClientService  {
    @GrpcClient("grpc-server")
    private SimpleGrpc.SimpleBlockingStub simpleStub;



    public String sendMessage(String name) {
        try {
            final HelloReply response = simpleStub.sayHello(HelloRequest.newBuilder().setName(name).build());
            return response.getMessage();
        } catch (final StatusRuntimeException e) {
            return "FAILED with " + e.getStatus().getCode().name();
        }
    }
}