【橘子分布式】gRPC(编程篇-上)

一、简介

我们之前学习了grpc的一些理论知识,现在我们开始正式进入编程环节。

我们的项目结构和之前的thrift结构还是一样的,一个common,一个client,一个server。只不过在grpc这里common它一般叫做api模块。还是放置一些公共的实体类,业务service类之类的。

二、项目结构

markdown 复制代码
1. xxxx-api 模块 
   定义 protobuf的idl语言 
   并且通过命令创建对应的通用代码,包括实体类和service,其他模块统一引入即可。
2. xxxx-server模块
   1. 实现api模块中定义的服务接口
   2. 发布gRPC服务 (创建服务端程序,提供对外服务)
3. xxxx-clien模块
   1. 创建服务端stub(代理)
   2. 基于代理(stub) RPC调用。

1、代码编写

先创建一个rpc-grpc-api模块。

1.1、创建proto文件

我们在模块目录下创建一个proto目录来管理我们的.proto文件。

然后在该目录下创建一个hello.proto文件用来编写idl语言。

proto 复制代码
// 定义proto文件版本号
syntax = "proto3";

// 生成一个java类即可
option java_multiple_files = false;
// 生成的java类的包名
option java_package = "com.levi";
// 外部类,这里就是HelloProto,实际开发你可以有多个proto管理不同业务类,然后各自的外部类都可以。比如OrderService就是Order.proto 外部类就是OrderProto
option java_outer_classname = "HelloProto";

// 定义请求接口参数
message HelloRequest{
  string name = 1;
}

// 定义接口响应参数
message HelloResponse{
  string result = 1;
}

// 定义服务
service HelloService{
  /* 简单rpc,参数为HelloRequest类型,返回类型为HelloResponse */
  rpc hello(HelloRequest) returns (HelloResponse){}
}

然后我们就用protoc命令来翻译这个idl。

1.1、命令行生成IDL

如同之前thrift一样,我们在这里也是要先编写idl的部分。我们需要首先在.proto后缀的文件中编写protobuf的IDL。

然后使用protoc命令把proto文件中的IDL 转换成编程语言 ,我们之前已经安装过protobuf环境了,所以可以直接使用。

具体命令格式为

proto 复制代码
protoc  --java_out=生成的java代码路径   依赖的.proto文件
如果你要翻译为go语言,那就是protoc -go_out ......

举个例子就是:

proto 复制代码
protoc --java_out=/src/java  /src/proto/hello.proto

但是这种我们需要在终端操作,不太方便。还好idea的maven插件支持了这个能力。

1.2、maven插件支持

maven插件 进行protobuf IDL文件的编译,并把他放置IDEA具体位置。这个能力的支持来自于一个grpc-java开源项目,我们看到它对于maven很方便的支持了能力,我们先创建一个模块来引入这个maven配置。

xml 复制代码
<?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>
    <parent>
        <groupId>com.levi</groupId>
        <artifactId>rpc</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>rpc-grpc-api</artifactId>

    <properties>
        <java.version>11</java.version>
        <grpc-netty-shaded.version>1.73.0</grpc-netty-shaded.version>
        <grpc-protobuf.version>1.73.0</grpc-protobuf.version>
        <grpc-stub.version>1.73.0</grpc-stub.version>
        <annotations-api.version>6.0.53</annotations-api.version>

        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--grpc的http2就是通过netty实现的-->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>${grpc-netty-shaded.version}</version>
            <scope>runtime</scope>
        </dependency>
        <!--支持protobuf的序列化-->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc-protobuf.version}</version>
        </dependency>
        <!--支持grpc代理-->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc-stub.version}</version>
        </dependency>
        <dependency> <!-- necessary for Java 9+ -->
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>${annotations-api.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.1</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <!--com.google.protobuf:protoc这个命令是生成grpc的实体message的,os.detected.classifier是maven的一个内置系统参数,用来获取本机操作系统类型,
                        这里是获取本机的操作系统类型,然后根据操作系统类型来获取protoc的命令,无需你自己写死,这里maven会自己获取-->
                    <protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
                    <!--grpc-java-->
                    <pluginId>grpc-java</pluginId>
                    <!--io.grpc:protoc-gen-grpc-java这个命令是支持grpc的service服务的-->
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.73.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <!--goals是maven执行的命令,类似于clean或者install,这里是grpc插件下的两个命令,
                            compile是生成grpc的实体message的,compile-custom是生成grpc的service服务的,两个依次执行就能获得结果-->
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

ok,至此我们完成了这个maven的配置,然后我们来测试一下效果。

我们来到idea的maven插件位置找到对应的goal。

然后先执行compile生成实体message,再执行compile-custom生成service。他会自己去扫这个目录下的proto文件然后生成的默认位置就是模块的target目录。然后把对应的生成类拷贝到我们的java类路径下。然后删除proto生成的文件即可。注意service和实体类不再一个目录下,但是都在generated-sources这个目录下。

而且生成的实体类名字为:HelloProto.java. 这个名字就是我们的那个外部类名字,这是他的规范。

生成的service类名字为:HelloServiceGrpc.java 这个名字就是我们的service名字加了一个Grpc,这是他的规范。

ok至此我们就完成了初始化类文件。后续我们就可以使用了,但是现在存在两个问题需要优化。

1、我们每次都得点两下maven插件,一个生成message,一个生成service才好,这样太麻烦了,我们实际上可以构建复合goal,可以在idea的maven插件组合多个gola,然后就能顺序执行了。这个我就不演示了。我还是执行两个命令吧,不太喜欢定制化。而且你可以用maven的组合命令执行,idea只是一个图形化了这个功能。

2、我们每次输出的java类都是在target目录下的。害得拷贝一次,我们可以在maven配置直接生成到我们要的目标java目录下。

我们在maven中添加outputDirectory配置。

xml 复制代码
<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.7.1</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.6.1</version>
            <configuration>
                <!--com.google.protobuf:protoc这个命令是生成grpc的实体message的,os.detected.classifier是maven的一个内置系统参数,用来获取本机操作系统类型,
                    这里是获取本机的操作系统类型,然后根据操作系统类型来获取protoc的命令,无需你自己写死,这里maven会自己获取-->
                <protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
                <!--grpc-java-->
                <pluginId>grpc-java</pluginId>
                <!--io.grpc:protoc-gen-grpc-java这个命令是支持grpc的service服务的-->
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.73.0:exe:${os.detected.classifier}</pluginArtifact>
                <!--basedir就是当前模块的根目录,我们直接生成到我们的java目录下面去-->
                <outputDirectory>${basedir}/src/main/java</outputDirectory>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <!--goals是maven执行的命令,类似于clean或者install,这里是grpc插件下的两个命令,
                        compile是生成grpc的实体message的,compile-custom是生成grpc的service服务的,两个依次执行就能获得结果-->
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

然后我们删除掉现在的类,再执行一次。

我们发现service把前面的message覆盖了,第二次生成的结果会覆盖第一次,所以我们还要加一个配置。

xml 复制代码
<!--追加新的文件,而不是清除旧的文件-->
<clearOutputDirectory> false</clearOutputDirectory>

此时配置变成这样。

xml 复制代码
<plugins>
    <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>0.6.1</version>
        <configuration>
            <!--com.google.protobuf:protoc这个命令是生成grpc的实体message的,os.detected.classifier是maven的一个内置系统参数,用来获取本机操作系统类型,
                这里是获取本机的操作系统类型,然后根据操作系统类型来获取protoc的命令,无需你自己写死,这里maven会自己获取-->
            <protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
            <!--grpc-java-->
            <pluginId>grpc-java</pluginId>
            <!--io.grpc:protoc-gen-grpc-java这个命令是支持grpc的service服务的-->
            <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.73.0:exe:${os.detected.classifier}</pluginArtifact>
            <!--basedir就是当前模块的根目录,我们直接生成到我们的java目录下面去-->
            <outputDirectory>${basedir}/src/main/java</outputDirectory>
            <!--追加新的文件,而不是清除旧的文件-->
            <clearOutputDirectory>false</clearOutputDirectory>
        </configuration>
        <executions>
            <execution>
                <goals>
                    <!--goals是maven执行的命令,类似于clean或者install,这里是grpc插件下的两个命令,
                    compile是生成grpc的实体message的,compile-custom是生成grpc的service服务的,两个依次执行就能获得结果-->
                    <goal>compile</goal>
                    <goal>compile-custom</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

此时就没问题了,而且target也不生成了。ok。下面我们就可以正式开始编写rpc能力的业务代码了。

2、代码结构

我们现在已经完成了api模块的开发,而且我们也生成了对应的grpc的类。我们现在就来看看我们生成的这些类的内容。我们先来看实体消息message类。

2.1、实体类message

我们在定义message的时候,就说了我们的实体类是被管理在一个外部类也就是我们这里的HelloProto里面的,我们使用的时候是以内部类的形式使用的HelloProto.HelloRequest以及HelloProto.HelloResponse这样的形式使用的。其余的是一些构建类和方法,我们先不说。

2.2、服务类service

而我们的服务类定义的时候名字是HelloService,最终生成的结果类为HelloServiceGrpc,可见其实就是在后面加了一个Grpc,而且也是都是内部类的形式,我们主要使用的就是这些内部类。我们之前在thrift的时候使用grpc的业务类其实是要实现他的接口覆盖他的方法实现自己的业务。

在grpc这里我们要实现的那个自己的业务类其实是HelloServiceImplBase这个内部类,我们需要在他的bindService里面书写我们的业务内容。他的命名方式就是我们声明的HelloServiceI后面加了一个ImplBase,这个和当初thrift的IFace接口一样。

其余的那些以Stub结尾的类其实就是生成的客户端发起远程rpc调用的接口类,不同的stub表示不同的rpc方法。

HelloServiceFutureStub这一看就是异步调用,返回Future。

HelloServiceBlockingStub这一看就是阻塞调用。其余等等,我们后面操作的时候会看到的。

ok至此我们就完成了api模块的实现,下面我们来编写client和server模块的代码,实现基于grpc的一个rpc调用。

相关推荐
Jack_David3 小时前
Kafka批量消息发送
java·分布式·kafka
wanhengidc4 小时前
服务器托管对企业的作用
大数据·运维·服务器·分布式·智能手机
Code知行合壹4 小时前
Spark使用总结
大数据·分布式·spark
Swift社区4 小时前
分布式能力不是功能,而是一种架构约束
分布式·架构
0xDevNull4 小时前
Apache Kafka 完全指南
分布式·kafka
zb200641205 小时前
RabbitMQ 客户端 连接、发送、接收处理消息
分布式·rabbitmq·ruby
半桶水专家6 小时前
Kafka JMX详解
分布式·kafka
渔民小镇6 小时前
告别 if-else 地狱 —— JSR380 参数验证在 ionet 中的应用
java·服务器·分布式·游戏
C++chaofan7 小时前
RPC框架SPI机制深度解析
java·网络·后端·网络协议·rpc·spi·序列化器
智慧科技的海洋7 小时前
微电网智慧平台:破解能源困局的分布式能源革命
分布式·能源