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

发布于:2025-07-20 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、简介

我们之前学习了grpc的一些理论知识,现在我们开始正式进入编程环节。
我们的项目结构和之前的thrift结构还是一样的,一个common,一个client,一个server。只不过在grpc这里common它一般叫做api模块。还是放置一些公共的实体类,业务service类之类的。

二、项目结构

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文件版本号
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环境了,所以可以直接使用。
具体命令格式为

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

举个例子就是:

protoc --java_out=/src/java  /src/proto/hello.proto

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

1.2、maven插件支持

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

<?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配置。

<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覆盖了,第二次生成的结果会覆盖第一次,所以我们还要加一个配置。

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

此时配置变成这样。

<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调用。


网站公告

今日签到

点亮在社区的每一天
去签到