SpringBoot基于gRPC进行RPC调用

一、gRPC

1.1 什么是gRPC?

In gRPC, a client application can directly call a method on a server application on a different machine as if it were a local object, making it easier for you to create distributed applications and services. As in many RPC systems, gRPC is based around the idea of defining a service, specifying the methods that can be called remotely with their parameters and return types. On the server side, the server implements this interface and runs a gRPC server to handle client calls. On the client side, the client has a stub (referred to as just a client in some languages) that provides the same methods as the server.

在 gRPC 中,客户端应用程序可以直接调用服务器应用程序上的方法 在另一台机器上,就好像它是本地对象一样,使你更容易 创建分布式应用程序和服务。与许多 RPC 系统一样,gRPC 是 基于定义服务的思想,指定可以 使用其参数和返回类型进行远程调用。在服务器端, server 实现此接口并运行 gRPC 服务器来处理客户端调用。 在客户端,客户端有一个存根(在某些客户端中称为客户端 languages),它提供与服务器相同的方法。
在这里插入图片描述

gRPC 客户端和服务器可以在各种 环境 - 从 Google 内部的服务器到您自己的桌面 - 并且可以 用 gRPC 支持的任何语言编写。因此,例如,您可以轻松地 在 Java 中创建一个 gRPC 服务器,客户端使用 Go、Python 或 Ruby。另外 最新的 Google API 将具有其接口的 gRPC 版本,让您 轻松将 Google 功能构建到您的应用程序中。

gRPC 使用 proto buffers 作为服务定义语言,编写 proto 文件,即可完成服务的定义

在这里插入图片描述

1.2 如何编写proto

syntax = "proto3";

option java_multiple_files = true;
// 生成位置
option java_package = "com.lizq.jrpc.api";
option java_outer_classname = "UserService";

package user;

service User {
   
    rpc SayHello (UserRequest) returns (UserResponse) {
   }
}

message UserRequest {
   
    string name = 1;
    int32 age = 2;
    string addr = 3;
}

message UserResponse {
   
    string name = 1;
    int32 age = 2;
    string addr = 3;
    OtherMsg otherMsg = 4;
    map<string, string> otherMap = 5;

    // 嵌套对象
    message OtherMsg {
   
        string ext1 = 1;
        string ext2 = 2;
    }
}
  • syntax = "proto3";:指定使用的protobuf版本;
  • option java_multiple_files = true;:如果为 false,则只会.java为此文件生成一个.proto文件,以及所有 Java 类/枚举/等。为顶级消息、服务和枚举生成的将嵌套在外部类中。如果为 true,.java将为每个 Java 类/枚举/等生成单独的文件。为顶级消息、服务和枚举生成,并且为此.proto文件生成的包装 Java 类将不包含任何嵌套类/枚举/等。 如果不生成 Java 代码,则此选项无效。
  • package user;:定义本服务的包名,避免不同服务相同消息类型产生冲突;
  • option java_package = "com.lizq.jrpc.api";:生成java文件包名;
  • option java_outer_classname = "UserService";:生成java文件类名称。如果文件中没有明确 java_outer_classname指定,.proto则将通过将.proto文件名转换为驼峰式来构造类名(因此 foo_bar.proto变为FooBar.java)
  • message UserResponse:定义服务的接口名称;
  • rpc SayHello (UserRequest) returns (UserResponse) {}:远程调用方法名,参数及响应类型;
  • message XXXXX{}:定义数据类型;

1.3 数据类型及对应关系

.proto类型 Notes C++ Type Java/Kotlin Python
double double double float
float float float float
int32 使用可变长度编码。对负数进行编码效率低下——如果您的字段可能有负值,请改用 sint32。 int32 int int
int64 使用可变长度编码。对负数进行编码效率低下——如果您的字段可能有负值,请改用 sint64。 int64 long int/long
uint32 使用可变长度编码。 uint32 int int/long
uint64 使用可变长度编码。 uint64 long int/long
sint32 使用可变长度编码。带符号的 int 值。这些比常规 int32 更有效地编码负数。 int32 int int
sint64 使用可变长度编码。带符号的 int 值。这些比常规 int64 更有效地编码负数。 int64 long int/long
fixed32 总是四个字节。如果值通常大于 228,则比 uint32 更有效 uint32 int int/long
fixed64 总是八个字节。如果值通常大于 256,则比 uint64 更有效。 uint64 long int/long
sfixed32 总是四个字节。 int32 int int
sfixed64 总是八个字节。 int64 long int/long
bool bool boolean bool
string 字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本,并且不能超过 232。 string String str/unicode
bytes 可能包含不超过 2 32的任意字节序列。 string ByteString str (Python 2)、bytes (Python 3)

1.4 枚举

enum Sex {
   
  NONE = 0;
  MAN = 1;
  WOMAN = 2;
}

message UserRequest {
   
  string name = 1;
  int32 age = 2;
  string addr = 3;
  Sex sex = 4;
}

**注意:**第一个枚举的值必须为0,因为0 是默认值,0 必须是第一个,保持和proto2 兼容

1.5 数组

使用 repeated 关键字来定义数组。

message UserRequest {
   
  string name = 1;
  int32 age = 2;
  string addr = 3;
  Sex sex = 4;
  // 定义一个数组
  repeated string cellphones = 5;
}

1.6 map类型

在开发的过程中经常需要使用关联字段,很自然的想到使用map,protobuf也提供了map的类型。

message UserResponse {
   
  string name = 1;
  map<string, string> otherMap = 2;
}

注意: map 字段前面不能是repeated

1.7 嵌套对象

message UserResponse {
   
  string name = 1;
  int32 age = 2;
  string addr = 3;
  OtherMsg otherMsg = 4;
  map<string, string> otherMap = 5;

  // 嵌套对象
  message OtherMsg {
   
    string ext1 = 1;
    string ext2 = 2;
  }
}

二、SpringBoot gRPC

2.1 工程目录

在这里插入图片描述

2.2 jrpc-api

2.2.1 引入gRPC依赖

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

2.2.2 编写 .proto 文件

syntax = "proto3";

option java_multiple_files = true;
// 生成位置
option java_package = "com.lizq.jrpc.api";
option java_outer_classname = "UserService";

package user;

service User {
   
    rpc SayHello (UserRequest) returns (UserResponse) {
   }
}

message UserRequest {
   
    string name = 1;
    int32 age = 2;
    string addr = 3;
}

message UserResponse {
   
    string name = 1;
    int32 age = 2;
    string addr = 3;
    OtherMsg otherMsg = 4;
    map<string, string> otherMap = 5;

    // 嵌套对象
    message OtherMsg {
   
        string ext1 = 1;
        string ext2 = 2;
    }
}

2.2.3 使用插件机制生产proto相关文件

在 jrpc-api pom.xml 中添加如下:

<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>
                <!--&lt;!&ndash; ${os.detected.classifier} 变量由${os.detected.name} 和 ${os.detected.arch} 组成-->
                <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.28.1:exe:${os.detected.classifier}</pluginArtifact>
                <!--protoSourceRoot 默认src/main/proto-->
                <protoSourceRoot>src/main/proto</protoSourceRoot>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

执行命令生产proto相关文件

在这里插入图片描述
将生成的文件拷贝到工程中,如下:

在这里插入图片描述

2.2 jrpc-server

jrpc-server 为 springboot 项目。

2.2.1 引入 jrpc-api 依赖

<dependency>
    <groupId>com.example</groupId>
    <artifactId>jrpc-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

2.2.2 编写impl

@Service
public class UserServiceImpl extends UserGrpc.UserImplBase {
   

    @Override
    public void sayHello(UserRequest request, StreamObserver<UserResponse> responseObserver) {
   
        Map<String, String> otherMap = new HashMap<>();
        otherMap.put("test", "testmap");
        UserResponse response = UserResponse.newBuilder()
                .setName("server:" + request.getName())
                .setAddr("server:" + request.getAddr())
                .setAge(request.getAge())
                .setOtherMsg(UserResponse.OtherMsg.newBuilder()
                        .setExt1("ext1")
                        .setExt2("ext2")
                        .build())
                .putAllOtherMap(otherMap)
                .build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

2.2.3 编写Config

@Configuration
public class GrpcServerConfiguration {
   

    @Value("${grpc.server-port}")
    private int port;

    @Bean
    public Server server() throws Exception {
   
        System.out.println("Starting gRPC on port {}." + port);
        // 构建服务端
        ServerBuilder<?> serverBuilder = ServerBuilder.forPort(port);
        // 添加需要暴露的接口
        this.addService(serverBuilder);
        // start
        Server server = serverBuilder.build().start();
        System.out.println("gRPC server started, listening on {}." + port);

        // 添加服务端关闭的逻辑
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
   
            System.out.println("Shutting down gRPC server.");
            if (server != null) {
   
                // 关闭服务端
                server.shutdown();
            }
            System.out.println("gRPC server shut down successfully.");
        }));

        if (server != null) {
   
            // 服务端启动后直到应用关闭都处于阻塞状态,方便接收请求
            server.awaitTermination();
        }
        return server;
    }

    @Autowired
    private UserServiceImpl userService;

    /**
     * 添加需要暴露的接口
     * @param serverBuilder
     */
    private void addService(ServerBuilder<?> serverBuilder) {
   
        serverBuilder.addService(userService);
    }
}

2.2.4 yaml

server:
  port: 8081
spring:
  application:
    name: spring-boot-jrpc-server
grpc:
  server-port: 18081

2.3 jrpc-client

2.3.1 引入 jrpc-api 依赖

<dependency>
    <groupId>com.example</groupId>
    <artifactId>jrpc-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

2.3.2 编写config

@Configuration
public class GrpcClientConfiguration {
   

    @Value("${server-host}")
    private String host;

    /**
     * gRPC Server的端口
     */
    @Value("${server-port}")
    private int port;

    @Bean
    public ManagedChannel managedChannel() {
   
        // 开启gRPC客户端
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
        System.out.println("gRPC client started, server address: " + host + " , " + port);

        // 添加客户端关闭的逻辑
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
   
            try {
   
                // 调用shutdown方法后等待1秒关闭channel
                managedChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
                System.out.println("gRPC client shut down successfully.");
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }));
        return managedChannel;
    }

    @Autowired
    private ManagedChannel managedChannel;

    @Bean
    public UserGrpc.UserBlockingStub userBlockingStub(ManagedChannel channel) {
   
        // 通过channel获取到服务端的stub
        return UserGrpc.newBlockingStub(managedChannel);
    }
}

2.3.3 yaml

server:
  port: 8080
spring:
  application:
    name: spring-boot-jrpc-client

# 本地测试
server-host: 127.0.0.1
server-port: 18081

2.3.4 测试验证

@RestController("/user")
public class UserController {
   

    @Autowired
    private UserGrpc.UserBlockingStub userBlockingStub;

    @GetMapping("/sayHello")
    public String sayHello(String name, String addr, int age) {
   
        UserRequest request = UserRequest.newBuilder()
                .setName(name)
                .setAddr(addr)
                .setAge(age)
                .build();
        UserResponse response;
        try {
   
            response = userBlockingStub.sayHello(request);
        } catch (StatusRuntimeException e) {
   
            e.printStackTrace();
            return e.getMessage();
        }
        return response.toString();
    }
}

浏览器访问:http://localhost:8080/user/sayHello?name=test&addr=addr&age=99 返回:

name: "server:test" age: 99 addr: "server:addr" otherMsg {
    ext1: "ext1" ext2: "ext2" } otherMap {
    key: "test" value: "testmap" }

相关推荐

  1. RPCGRPC:什么是GRPCGRPC的优缺点、GRPC使用场景

    2023-12-19 08:46:04       42 阅读
  2. 浅谈RPC调用

    2023-12-19 08:46:04       37 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-19 08:46:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-19 08:46:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-19 08:46:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-19 08:46:04       20 阅读

热门阅读

  1. Scroll-view的用法(网页和微信小程序)

    2023-12-19 08:46:04       38 阅读
  2. Flink系列之:Table API Connectors之Raw Format

    2023-12-19 08:46:04       38 阅读
  3. linux常用命令-sed(流编辑器)

    2023-12-19 08:46:04       34 阅读
  4. svn拉取

    2023-12-19 08:46:04       36 阅读
  5. 【重点】【回溯】【DFS】79.单词搜索

    2023-12-19 08:46:04       46 阅读
  6. EasyRule的学习到实践

    2023-12-19 08:46:04       30 阅读