gRPC的原理和实践

简介: gRPC的原理和实践

一、gRPC理论

1.概述

  1. RPC 全称 (Remote Procedure Call),远程过程调用,指的是一台计算机通过网络请求另一台计算机的上服务,从而不需要了解底层网络细节,RPC 是构建在已经存在的协议(TCP/IP,HTTP 等)之上的,RPC 采用的是客户端,服务器模式。
  2. gRPC 是云原生计算基金会(CNCF)项目,gRPC 一开始由 google 开发,是一款语言中立、平台中立的服务间通信框架,使用 gRPC 可以使得客户端像调用本地方法一样,调用远程主机提供的服务。可以在任何地方运行,它使客户端和服务器应用程序能够透明地进行通信,并使构建连接系统变得更加容易。
  3. gRPC 目前最新版本是 v1.22.0。

2.特性

  1. gRPC 基于服务的思想:定义一个服务,描述这个服务的方法以及入参出参,服务器端有这个服务的具体实现,客户端保有一个存根,提供与服务端相同的服务。

  2. gRPC 默认采用 protocol buffer 作为 IDL (Interface Description Lanage) 接口描述语言,服务之间通信的数据序列化和反序列化也是基于 protocol buffer 的,因为 protocol buffer 的特殊性,所以 gRPC 框架是跨语言的通信框架(与编程语言无关性),也就是说用 Java 开发的基于 gRPC 的服务,可以用 GoLang 编程语言调用。

  3. gRPC 同时支持同步调用和异步调用,同步 RPC 调用时会一直阻塞直到服务端处理完成返回结果, 异步 RPC 是客户端调用服务端时不等待服务段处理完成返回,而是服务端处理完成后主动回调客户端告诉客户端处理完成。

  4. gRPC 是基于 http2 协议实现的,http2 协议提供了很多新的特性,并且在性能上也比 http1 提搞了许多,所以 gRPC 的性能是非常好的。

  5. gRPC 并没有直接实现负载均衡和服务发现的功能,但是已经提供了自己的设计思路。已经为命名解析和负载均衡提供了接口。

  6. 基于 http2 协议的特性:gRPC 允许定义如下四类服务方法:

    • 单项 RPC:客户端发送一次请求,等待服务端响应结构,会话结束,就像一次普通的函数调用这样简单

    • 服务端流式 RPC:客户端发起一起请求,服务端会返回一个流,客户端会从流中读取一系列消息,直到没有结果为止

    • 客户端流式 RPC:客户端提供一个数据流并写入消息发给服务端,一旦客户端发送完毕,就等待服务器读取这些消息并返回应答

    • 双向流式 RPC:客户端和服务端都一个数据流,都可以通过各自的流进行读写数据,这两个流是相互独立的,客户端和服务端都可以按其希望的任意顺序独写

3.使用场景

  1. 低延迟、高扩展的分布式系统。
  2. 与云服务通信。
  3. 设计一个需要准确,高效且与语言无关的新协议。
  4. 分层设计,以实现扩展,例如:身份验证,负载平衡,日志记录和监控等。

4.优点和缺点总结

gRPC 优点

  1. protobuf是二进制消息,性能好/效率高(空间和时间效率都很不错)。
  2. 通过在服务器和客户端之间共享 .proto 文件,可以端到端生成消息和客户端代码。 节约开发时间。并且有严格的规范。
  3. 基于HTTP/2,与 HTTP 1.x 相比,HTTP/2 具有巨大性能优势。
  4. 支持流式处理。
  5. 截止时间/超时和取消,gRPC 允许客户端指定其愿意等待 RPC 完成的时间期限。

gRPC 缺点

  1. 浏览器支持受限,无法通过浏览器直接调用gRPC服务。可以通过gRPC-web来做,但并不支持所有 gRPC 功能。 不支持客户端和双向流式传输,并且对服务器流式传输的支持有限。
  2. 默认情况下,gRPC 消息使用 Protobuf 进行编码。 尽管 Protobuf 可以高效地发送和接收,但其二进制格式人工不可读。
  3. gRPC尚未提供连接池,需要自行实现。
  4. 尚未提供“服务发现”、“负载均衡”机制。

5.原理

todo

二、gRPC实践

1.protocol buffer语法

proto buffer是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化。适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。gRPC默认使用 protobuf 作为IDL来定义数据结构和服务。 可以定义数据结构,也可以定义rpc 接口。 然后用proto编译器生成对应语言的框架代码。

proto buffer定义:

user.proto

syntax = "proto3";

import "google/protobuf/any.proto";

//package user;
option go_package = "protos_golang/user";

message User {
  int32 id = 1;
  string name = 2;
  uint32 age = 3;
  enum Flag {
    NORMAL = 0;
    VIP = 1;
    SVIP = 2;
  }
  repeated int32 friends_ids = 5;
  reserved 6, 7, 8;
  message Command {
      int32 id = 1;
      oneof cmd_value {
         string name = 2;
         int32 age = 3;
      }
  }
  Command cmd = 9;
  map<int32, string> tags = 10;
  google.protobuf.Any details = 11;
}

具体参数含义参考文章:gRPC 特性、原理、实践、生态:https://zhuanlan.zhihu.com/p/403336804

2.gRPC的简单实现

1.添加POM依赖

//gRPC依赖包
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-all</artifactId>
    <version>1.12.0</version>
</dependency>

...
//proto buffer编译器maven插件
<build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.4.1.Final</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.0</version>
                <configuration>
                    <pluginId>grpc-java</pluginId>
                    <protocArtifact>com.google.protobuf:protoc:3.0.2:exe:${os.detected.classifier}</protocArtifact>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.2.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

2.编写proto buffer文件

syntax = "proto3"; // 协议版本

// 选项配置
option java_package = "com.yangnk.springboottestexample.grpcExample.api";
option java_outer_classname = "RPCDateServiceApi";
option java_multiple_files = true;

// 定义包名
package com.yangnk.springboottestexample.grpcExample;

// 服务接口.定义请求参数和相应结果
service RPCDateService {
    rpc getDate (RPCDateRequest) returns (RPCDateResponse) {
    }
}

// 定义请求体
message RPCDateRequest {
    string userName = 1;
}

// 定义相应内容
message RPCDateResponse {
    string serverDate = 1;
}

其中option java_package参数规定了proto buffer编译后java代码位置,编译会在target目录下生成,后续需要自己移动到对于的src/main目录中。

image-20230727143715719

在maven的插件选项中,第一步生成的bean实例,第二步生成服务接口。

image-20230727145918284

3.服务端实现

编写接口实现逻辑

import com.yangnk.springboottestexample.grpcExample.api.RPCDateRequest;
import com.yangnk.springboottestexample.grpcExample.api.RPCDateResponse;
import com.yangnk.springboottestexample.grpcExample.api.RPCDateServiceGrpc;
import io.grpc.stub.StreamObserver;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

// RPCDateServiceGrpc.RPCDateServiceImplBase 这个就是接口.
// RPCDateServiceImpl 我们需要继承他的,实现方法回调
public class RPCDateServiceImpl extends RPCDateServiceGrpc.RPCDateServiceImplBase {
   
   
    @Override
    public void getDate(RPCDateRequest request, StreamObserver<RPCDateResponse> responseObserver) {
   
   
        // 请求结果,我们定义的
        RPCDateResponse rpcDateResponse = null;
        String userName = request.getUserName();
        String response = String.format("你好: %s, 今天是%s.", userName, LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        try {
   
   
            // 定义响应,是一个builder构造器.
            rpcDateResponse = RPCDateResponse
                    .newBuilder()
                    .setServerDate(response)
                    .build();
        } catch (Exception e) {
   
   
            responseObserver.onError(e);
        } finally {
   
   
            // 这种写法是observer,异步写法,老外喜欢用这个框架.
            responseObserver.onNext(rpcDateResponse);
        }
        responseObserver.onCompleted();
    }

编写服务端启动类

import io.grpc.Server;
import io.grpc.ServerBuilder;

public class GRPCServer {
   
   
    private static final int port = 9999;

    public static void main(String[] args) throws Exception {
   
   
        // 设置service接口.
        Server server = ServerBuilder.
                forPort(port)
                .addService(new RPCDateServiceImpl())
                .build().start();
        System.out.println(String.format("GRpc服务端启动成功, 端口号: %d.", port));
        server.awaitTermination();
    }
}

4.客户端实现

import com.yangnk.springboottestexample.grpcExample.api.RPCDateRequest;
import com.yangnk.springboottestexample.grpcExample.api.RPCDateResponse;
import com.yangnk.springboottestexample.grpcExample.api.RPCDateServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

public class GRPCClient {
   
   
    private static final String host = "localhost";
    private static final int serverPort = 9999;

    public static void main(String[] args) throws Exception {
   
   
        // 1. 拿到一个通信的channel
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(host, serverPort).usePlaintext().build();
        try {
   
   
            // 2.拿到道理对象
            RPCDateServiceGrpc.RPCDateServiceBlockingStub rpcDateService = RPCDateServiceGrpc.newBlockingStub(managedChannel);
            RPCDateRequest rpcDateRequest = RPCDateRequest
                    .newBuilder()
                    .setUserName("yangnk")
                    .build();
            // 3. 请求
            RPCDateResponse rpcDateResponse = rpcDateService.getDate(rpcDateRequest);
            // 4. 输出结果
            System.out.println(rpcDateResponse.getServerDate());
        } finally {
   
   
            // 5.关闭channel, 释放资源.
            managedChannel.shutdown();
        }
    }
}

5.结果验证

image-20230727152507703

3.gRPC结合springboot的实现

说明

为gRPC结合springboot,使用的是开源库net.devh:grpc-server-spring-boot-starter,在调用其他gRPC服务时用的是net.devh:grpc-client-spring-boot-starter。具体使用方法如下:

  • 在 spring boot 应用中,通过 @GrpcService 自动配置并运行一个嵌入式的 gRPC 服务。
  • 使用 @GrpcClient 自动创建和管理您的 gRPC Channels 和 stubs

具体创建流程如下:

1.创建父工程

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.6</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<modelVersion>4.0.0</modelVersion>
<groupId>com.yangnk</groupId>
<artifactId>gRPCDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<description>Demo project for Spring Boot</description>
<properties>
    <java.version>1.8</java.version>
</properties>

<modules>
    <module>GRPCApi</module>
    <module>GRPCServer</module>
    <module>GRPCClient</module>
</modules>

指定该项目的POM文件中<packaging>pom</packaging>即为父工程。

2.创建API子工程

在src/proto目录下出创建helloworld.proto文件,同上面 :grpc的简单实现-编写proto buffer文件 部分生成对于的bean文件和接口文件。

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.yangnk.grpcapi.api";
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-20230727154152660

3.创建服务端子工程

引入pom文件

...
<!--父工程依赖位置-->
<parent>
    <groupId>com.yangnk</groupId>
    <artifactId>GRPCDemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.yangnk</groupId>
<artifactId>GRPCServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>GRPCServer</name>
<description>Demo project for Spring Boot</description>
<properties>
    <java.version>1.8</java.version>
</properties>
<dependencies>

        <!--grpc服务端依赖包-->
    <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-server-spring-boot-starter</artifactId>
        <version>2.12.0.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>com.yangnk</groupId>
        <artifactId>GRPCApi</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>

    <!--Lombok引入-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
  ...

创建配置文件 application.yaml

spring:
  application:
    name: spring-boot-grpc-server
# gRPC有关的配置,这里只需要配置服务端口号
grpc:
  server:
    port: 9898
server:
  port: 8080

配置接口实现类

import com.yangnk.grpcapi.api.HelloReply;
import com.yangnk.grpcapi.api.HelloRequest;
import com.yangnk.grpcapi.api.SimpleGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
import java.util.Date;

@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();
    }
}

4.创建客户端子工程

    ...
        <parent>
        <groupId>com.yangnk</groupId>
        <artifactId>GRPCDemo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>com.yangnk</groupId>
    <artifactId>GRPCClient</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>GRPCClient</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>2.12.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.yangnk</groupId>
            <artifactId>GRPCApi</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!--Lombok引入-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
...

配置文件application.yaml

server:
  port: 8088
spring:
  application:
    name: local-client

grpc:
  client:
    # gRPC配置的名字,GrpcClient注解会用到
    local-grpc-server:
      # gRPC服务端地址
      address: 'static://127.0.0.1:9898'
      enableKeepAlive: true
      keepAliveWithoutCalls: true
      negotiationType: plaintext

调用服务端接口

import com.yangnk.grpcapi.api.HelloReply;
import com.yangnk.grpcapi.api.HelloRequest;
import com.yangnk.grpcapi.api.SimpleGrpc;
import io.grpc.StatusRuntimeException;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;

@Service
public class GrpcClientService {
   
   

    @GrpcClient("local-grpc-server")
    private SimpleGrpc.SimpleBlockingStub simpleStub;

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

三、总结

gRPC是一种基于CS模式的架构设计,解决了服务端和客户端编程语言或架构设计不一致但需要通信的问题,HTTP也是CS模式的架构设计,常用来和gRPC比较,相比于HTTP通信,gRPC具有以下特征:(1)gRPC可以自定义消息格式,HTTP的格式是固定的,可能存在性能不足的问题;(2)gRPC通过proto buffer进行数据交换,相比于json和XML,他的序列号和反序列号的效率都更高。

四、常用问题总结


TODO

  • [ ] 补充gRPC的原理。
  • [ ] 总结使用gRPC过程中的常见问题。

参考资料

  1. gRPC 官方文档中文版:https://doc.oschina.net/grpc?t=60134
  2. grpc 官方代码:https://github.com/grpc/grpc-java/tree/master/examples
  3. gRPC-Spring-Boot-Starter 文档:https://yidongnan.github.io/grpc-spring-boot-starter/zh-CN/
  4. 带你走入GRPC (Java版本):https://juejin.cn/post/6844904115248562189#heading-6 (纯java编写)
  5. 3小时快速入门Java版gRPC系列(九)-使用spring boot集成grpc:https://zhuanlan.zhihu.com/p/434323358 (grpc+springboot的结合,主要参考文章)
  6. SpringBoot整合高性能微服务框架 gRPC:https://ost.51cto.com/posts/11223
  7. gRPC系列 :RPC 框架原理是?gRPC 是什么?gRPC设计原则:https://developer.aliyun.com/article/886793
  8. gRPC 的特性和背后设计的原则(一):https://learnku.com/articles/32912
目录
相关文章
|
7月前
|
网络协议 编译器 Go
玩转gRPC—深入概念与原理
玩转gRPC—深入概念与原理
145 0
|
6月前
|
存储 负载均衡 网络协议
gRPC 的原理 介绍带你从头了解gRPC
gRPC 的原理 介绍带你从头了解gRPC
301 2
|
7月前
|
XML Go 开发工具
RPC简介和grpc的使用
RPC简介和grpc的使用
104 0
|
编解码 网络协议 Go
golang如何使用原生RPC及微服务简述
golang如何使用原生RPC及微服务简述
|
安全 网络协议 网络安全
【gRPC】来聊一聊gRPC的认证
【gRPC】来聊一聊gRPC的认证
113 0
|
负载均衡
gRPC源码分析(二):从官网文档看gRPC的特性
在第一部分,我们学习了gRPC的基本调用过程,这样我们对全局层面有了一定了解。接下来,我们将结合官方文档,继续深入学习、探索下去。
88 0
|
XML JSON 自然语言处理
gRPC系列 :RPC 框架原理是?gRPC 是什么?gRPC设计原则
gRPC系列 :RPC 框架原理是?gRPC 是什么?gRPC设计原则
1734 0
gRPC系列 :RPC 框架原理是?gRPC 是什么?gRPC设计原则
|
负载均衡 安全 Cloud Native
2023-5-15-gRpc框架学习
2023-5-15-gRpc框架学习
154 0
|
XML 存储 JSON
1. 微服务架构上篇:2. grpc+protobuf+网关实战
1. 微服务架构上篇:2. grpc+protobuf+网关实战
|
Cloud Native 物联网 编译器
gRPC(三)基础:gRPC快速入门
Protobuf 是 Google 的序列化/反序列化协议,可以轻松定义服务和自动生成客户端库。gRPC 使用此协议作为其接口定义语言 (IDL) 和序列化工具集。
403 0
gRPC(三)基础:gRPC快速入门