gRPC使用说明

TMaize 于 2021-12-26 发布

Remote Produre Call(RPC) 即远程过程调用,像调用本地方法一样调用远程方法。其实 HTTP 接口也是 RPC 的一种,如果你再封装一层的话也能实现像调用本地方法一样。作为一个 Javaer,接触的最早的 RPC 框架是 Hessian,据说 JDK 自带的也有一个 RMI 也是 RPC 框架。

gRPC

gRPC,是 Google 主导开发的 RPC 框架,包含特性:

  1. 使用了 http2.0

  2. 通过 protobuf 来定义接口,从而可以有更加严格的接口约束条件

  3. 序列化/反序列化效率很高

  4. protobuf 是跨语言的

安装 Protobuf 编译工具

用于把 proto 文件编译其他语言的代码

下载地址:https://github.com/protocolbuffers/protobuf/releases

根据系统去选择,如 protoc-3.11.4-win64.zip,解压后添加到环境变量中即可

检测是否配置成功

protoc --version
libprotoc 3.11.4

注意:原生的 protoc 并不包含 Go 版本的插件,需要去安装 protoc-gen-go 插件。会安装到$GOPATH/bin,确保这个目录在环境变量中

go get -u github.com/golang/protobuf/protoc-gen-go

使用 Go 作为服务端

项目结构

│  article.proto
│  go.mod
│  go.sum
│
├─client
│      main.go
│
├─remote
│      article.pb.go
│
└─server
        main.go

新建 Server 端项目

go mod init grpc_server

# 国内需要GOPROXY
go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf

定义 proto 文件

article.proto

syntax = "proto3";

// 定义包名
package remote;

// 可以定义多个服务,每个服务内可以定义多个接口
service Article {
    // 方法 (请求消息结构体) returns (返回消息结构体) {}
    // 请求/返回参数必须为message,不能为基础类型
    rpc SearchArticle (SearchRequest) returns (SearchResponse) {
    }
}

message SearchRequest {
    string keyword = 1;
}

message SearchResponse {
    int32 code = 1;
    repeated ArticleEntity data = 2;
}

message ArticleEntity {
    int64 id = 1;
    string title = 2;
}

使用 protoc 生成服务端代码

protoc ./article.proto --go_out=plugins=grpc:./remote

编写业务逻辑/注册服务并启动

// 实现 remote.ArticleServer
type ArticleServer struct{}

func (a *ArticleServer) SearchArticle(context.Context, *remote.SearchRequest) (*remote.SearchResponse, error) {
  var data []*remote.ArticleEntity
  for i := 0; i < 10; i++ {
    data = append(data, &remote.ArticleEntity{
      Id:    int64(i),
      Title: "标题" + strconv.Itoa(i),
    })
  }
  resp := remote.SearchResponse{Code: 200, Data: data}
  return &resp, nil
}

func main() {
  listener, err := net.Listen("tcp", ": 9999")
  if err != nil {
    panic(err)
  }
  server := grpc.NewServer()
  remote.RegisterArticleServer(server, &ArticleServer{})
  reflection.Register(server)

  err = server.Serve(listener)
  if err != nil {
    panic(err)
  }
}

使用 Go 作为客户端

使用和服务端相同的 proto 文件生成代码

protoc ./article.proto --go_out=plugins=grpc:./remote

连接服务端并调用

func main() {
  conn, err := grpc.Dial(": 9999", grpc.WithInsecure())
  if err != nil {
    panic(err)
  }
  // 实际生产中一般都有一个连接池,不会用一次关一次
  defer func() { _ = conn.Close() }()

  c := remote.NewArticleClient(conn)

  resp, err := c.SearchArticle(context.Background(), &remote.SearchRequest{})

  if err != nil {
    log.Panicln(err)
  } else {
    for _, v := range resp.Data {
      log.Println(v.Id, v.Title)
    }
  }
}

生成 Java 代码

在 proto 文件文件中增加 java 的一些选项,一般放在 package 下面一行

option java_multiple_files = true;
option java_package = "net.tmaize.rpc.proto.article";
option java_outer_classname = "ArticleProto";

生成 java 代码,除了需要protoc外还需要安装protoc-gen-grpc-java

# 生成消息类
protoc ./article.proto --java_out=./src/main/java
# 生成服务类,XXXGrpc.java文件
protoc ./article.proto --plugin=protoc-gen-grpc-java=D:/protoc-gen-grpc-java.exe --grpc-java_out=./src/main/java

另外也可以通过 maven 插件来生成,把你的 proto 文件放在src/main/proto,执行mvn clean compile,就会在target/generated-sources/protobuf下生成 Java 代码。如果使用的是 IDEA 的话这个目录会自动为你标记为 Sources,可在项目中直接引用

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

Java 作为服务端/客户端

相关依赖

<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-all</artifactId>
    <version>1.27.1</version>
</dependency>

服务端代码

Server server = ServerBuilder.forPort(9999)
        .addService(new ArticleImpl())
        .build();
server.start();
server.awaitTermination();

客户端代码

ManagedChannel c = ManagedChannelBuilder
        .forAddress("127.0.0.1",  9999)
        .usePlaintext()
        .build();
ArticleGrpc.ArticleBlockingStub stub = ArticleGrpc.newBlockingStub(c);
SearchResponse resp = stub.searchArticle(SearchRequest.newBuilder().build());
System.out.println(resp.toString());

总结

由于是 grpc 跨语言的,只要使用同一个 proto 定义出来的服务,无论服务端可客户端是什么语言,都是可以进行互相调用的

参考

Documentation

grpc/grpc-java

golang/protobuf

Protobuf 语法指南(proto3)

gRPC-Go 和 Java 的一次 HelloWorld