Bootstrap

Grpc框架+Protobuf 搭建以及使用(入门)

1.Grpc框架简介

Google开发的一个高性能、开源和通用的 RPC 框架,基于ProtoBuf(Protocol Buffers) 序列化协议开发,且支持众多开发语言。面向服务端和移动端,基于 HTTP/2 设计。

2.Grpc框架特性

  • grpc可以跨语言使用。支持多种语言 支持C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP等编程语言
  • 基于 IDL ( 接口定义语言(Interface Define Language))文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
  • 通信协议基于标准的 HTTP/2 设计,支持·双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
  • 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
  • 安装简单,扩展方便(用该框架每秒可达到百万个RPC)

3.Grpc执行流程

1、客户端(gRPC Stub)调用 A 方法,发起 RPC 调用。
2、对请求信息使用 Protobuf 进行对象序列化压缩(IDL)。
3、服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回。
4、对响应结果使用 Protobuf 进行对象序列化压缩(IDL)。
5、客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果。

4.Rpc与HttpAPI的优劣势

Grpc(远程过程调用)与HTTP远程调用

5. Grpc框架特性搭建

  5.1.框架准备条件

  ①:JDK11(上文也提及基于 HTTP/2,JDK11之后才开始正式使用HTTP/2)

  ②:Protobuf插件(上文也提及基于protobuf序列化协议,用插件读.proto生成Stub代码)

  5.2.工程搭建

  ①:spring-grpc:一个主工程做依赖管理

  ②:client:Grpc的客户端

  ③:server:Grpc的服务端

  ④:grpc-lib:通过.proto文件生成stub的java文件 

  

 5.3.各工程依赖

  ①:主工程依赖(此处的net.devh的两个starter集成了Grpc的一些繁琐配置,开包即用很方便)

          另外感谢yindongnan作者为开源做出的贡献

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring-grpc</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>client</module>
        <module>server</module>
        <module>grpc-lib</module>
    </modules>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.13.RELEASE</version>
        <relativePath/>
    </parent>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>2.10.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-server-spring-boot-starter</artifactId>
            <version>2.10.1.RELEASE</version>
        </dependency>
    </dependencies>
</project>

  ②:grpc-lib工程依赖(此处完全是抄别人的,主要是配置proto生成的stub路径等等)

<?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>spring-grpc</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>grpc-lib</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <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.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.0</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.34.1:exe:${os.detected.classifier}</pluginArtifact>
                    <!--设置grpc生成代码到指定路径-->
                    <outputDirectory>${project.basedir}/src/main/java</outputDirectory>
                    <!--生成代码前是否清空目录-->
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <!-- 设置多个源文件夹 -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <!-- 添加主源码目录 -->
                    <execution>
                        <id>add-source</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>${project.basedir}/src/main/gen</source>
                                <source>${project.basedir}/src/main/java</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

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

  ③:server端依赖(没什么特别的,client也雷同)

<?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>spring-grpc</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>client</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>grpc-lib</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

</project>

5.4.编写proto文件

  proto 3 语法不熟悉的可以去查一查

// 基于proto3版本
syntax = "proto3";

option java_multiple_files = true;
// 生成java代码的包路径
option java_package = "com.yuwenwen.grpc";

// grpc服务
service YuWenWenService{
// 一个查询接口
rpc CheckUser(ApiIn) returns (ApiOut) {}
}
// 定义接口入参数
message ApiIn{
  string name = 1;
  string code = 2;
  int64 age =3;
}
// 定义接口返回参数
message ApiOut {
  bool success = 1;
  string auth=2;
}

5.5.compile生成Stub java代码

  此处注意:在grpc-lib的工程下,java包和proto包保持平级。

  

  compile代码之后就会按照指定的路径生成Stub代码 

 

   生成完的效果

 5.6.Grpc服务端

  ①:application定义服务端的端口和名称

grpc:
  server:
    port: 8888
spring:
  application:
    name: grpc-server
server:
  port: 8890

  ②:业务Service继承生成的java代码

在用工具生成的代码中(YuWenWenServiceGrpc.java)有很多内部类

选择继承 YuWenWenServiceImplBase.java并标注上starter中封装好的@GrpcService注解

 注意:responseObserver.onNext方法被调用,可以向客户端输出数据,最后通过          responseObserver.onCompleted结束输出

package com.yu.service;

import com.yuwenwen.grpc.ApiIn;
import com.yuwenwen.grpc.ApiOut;
import com.yuwenwen.grpc.YuWenWenServiceGrpc.YuWenWenServiceImplBase;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService
public class YuWenWenServiceImpl extends YuWenWenServiceImplBase {

    @Override
    public void checkUser(ApiIn request, StreamObserver<ApiOut> responseObserver) {
        ApiOut rep = ApiOut.newBuilder().setAuth("admin").setSuccess(true).build();
        responseObserver.onNext(rep);
        responseObserver.onCompleted();
    }
}

5.6.1. StreamObserver<T>深研究

这个接口有三个抽象方法,而且命名规则来看是用了观察者模式。

public interface StreamObserver<V> {
    void onNext(V var1);

    void onError(Throwable var1);

    void onCompleted();
}
其中的onNext、onError以及onComplete⽅法均会调⽤内部的ServerCall实例发送消息。
具体发送逻辑实现这⾥不做深⼊,总之可以明确的是,这⾥的StreamObserver回调接⼝,其实现逻辑就是将消息发送⾄客户端,这就是观
察者的逻辑。再看下被观察者,也就是StreamObserver回调接⼝的调⽤⽅。其实就是实现类⾥返回值的⽣成的逻辑,我们需要根据
request取到参数,然后⽣成返回值,调⽤StreamObserver回调接⼝,来通知Grpc框架层发送返回值。⾄此服务端实现⽅法⾥的
StreamObserver已经清晰了:被观察的对象就是返回值,Grpc框架层是观察者,提供发送逻辑作为回调函数,实现类是被观察者,每⼀
次返回值的⽣成都会调⽤回调函数通知Grpc。还有⼀点,StreamObserver接⼝的定义其实和stream息息相关。我们知道stream模式意
味着可以在⼀个连接中发送多条消息,所以该接⼝提供了onNext回调函数,该函数可以被多次调⽤,每⼀次对onNext的调⽤都代表⼀条消
息的发送。如果全部发送完了或者发送出错,那么就需要调⽤onError或者onComplete来告知对⽅本次stream已经结束。所以该接⼝的设
计也与stream的概念也完全契合。

  ③:启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

5.7.客户端

  ①:依赖Stub代码

  RPC(Remote Procedure Call)的中文:远程过程调用
  RPC框架追求的就是,两个服务之间的调用就如同调本地方法一样

package com.yuwenwen.controller;

import com.yuwenwen.grpc.ApiIn;
import com.yuwenwen.grpc.ApiOut;
import com.yuwenwen.grpc.YuWenWenServiceGrpc.YuWenWenServiceBlockingStub;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/userInfo")
public class UserInfoController {

    @GrpcClient("grpc-server")
    YuWenWenServiceBlockingStub yuWenWenStub;

    @RequestMapping(value = "/query")
    public String queryUser() {
        ApiOut apiOut = yuWenWenStub.checkUser(ApiIn.newBuilder().setAge(25).setCode("1124").build());
        String result = apiOut.getSuccess() ? "successfully" : "failed";
        return result;
    }
}

②:application定义客户端和服务端的端口和名称

grpc:
  client:
    grpc-server:
      // # gRPC服务端地址
      address: static://localhost:8888
      enableKeepAlive: true
      keepAliveWithoutCalls: true
      negotiationType: plaintext
spring:
  application:
    name: grpc-client
server:
  port: 8891

③:启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

6.Postman测试

 

 结果:

 

 以上就可以完成一个简易版的Grpc框架以及服务之间的调用

;