超高性能rpc框架之gRPC 快速整合gRPC+nacos+springCloud

什么是gRPC

gRPC是一个超高性能的RPC框架,对市面上主流语言都提供了支持。

  • gRPC可通过protobuf来定义接口,可以对接口的约束更加严格

  • 使用protobuf序列化,大幅减小传输数据量,从而对功耗,带宽,性能都有显著提升

  • 基于http2标准设计,支持双向流通讯

  • 支持异步请求,异步返回

使用场景

  • 需要对接口有更加严格的管控,如对公司外部提供接口,我们不希望客户端随意传递数据,这时我们就可以使用gRPC来对接口约束。
  • 对传输性能有更高的要求,如果我们传输的消息体过大,或调度过于频繁不希望影响服务器的性能时,也可以考虑使用,使用gRPC时我们的消息体比JSON或者文本传输数据量要小的多。

proto语法

.proto Type Notes C++ Type Java Type Python Type[2] Go Type Ruby Type C# Type PHP Type
double double double float float64 Float double float
float float float float float32 Float float float
int32 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer
uint32 使用变长编码 uint32 int int/long uint32 Fixnum 或者 Bignum(根据需要) uint integer
uint64 使用变长编码 uint64 long int/long uint64 Bignum ulong integer/string
sint32 使用变长编码,这些编码在负值时比int32高效的多 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer
sint64 使用变长编码,有符号的整型值。编码时比通常的int64高效。 int64 long int/long int64 Bignum long integer/string
fixed32 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 uint32 int int uint32 Fixnum 或者 Bignum(根据需要) uint integer
fixed64 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 uint64 long int/long uint64 Bignum ulong integer/string
sfixed32 总是4个字节 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer
sfixed64 总是8个字节 int64 long int/long int64 Bignum long integer/string
bool bool boolean bool bool TrueClass/FalseClass bool boolean
string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 string String str/unicode string String (UTF-8) string string
bytes 可能包含任意顺序的字节数据。 string ByteString str []byte String (ASCII-8BIT) ByteString string

当我们需要表示一个集合时使用repeated关键字

message GetByUidParams{
  repeated string uid = 1;
}

表示kv存储结构可使用map关键字

map<key_type,value_type> field_name = num;

message UserInfo{
  string uid = 1;
  string avatar = 2;
  string phone = 3;
  string nickName = 4;
  uint64 authTime = 5;
  uint64 loginTime = 6;
  uint64 recommendId = 7;
  string realName = 8;
  string email = 9;
}
message UserInfoMap {
    map<string,UserInfo> infos = 1;
    map<string,string> infomap = 2;
}

proto option选项

name type desc
java_multiple_files bool 为True每一个message文件都会有一个单独的class文件;否则,message全部定义在outerclass文件里
java_package str 该字段是option的,用于标识生成的java文件的package。如果没有指定,则使用proto里定义的package,如果package也没有指定,那就会生成在根目录下;
java_outer_classname str 该字段是option的,用于指定proto文件生成的java类的outerclass类名。什么是outerclass?简单来说就是用一个class文件来定义所有的message对应的java类。这个class就是outerclass;如果没有指定,默认是proto文件的驼峰式

异常处理

当我们调用onError方法时表示服务端异常返回,此时不需要再调用onCompleted方法

responseObserver.onError(Status.NOT_FOUND.augmentDescription("user not fund").asException());

我们在client端只需要捕获StatusRuntimeException异常即可,该类继承RuntimeException

该异常类包含的Statusmessage都可以帮助我们做业务操作

整合Spring cloud+gRPC+Nacos

我们使用SpringCloud作为项目载体帮我们管理Bean,使用gRPC进行服务间通讯,使用Nacos做注册中心来实现gRPC的负载

注:Nacos2.0支持的gRPC

Nacos2.0版本相比1.X新增了gRPC的通信方式,因此需要增加2个端口。新增端口是在配置的主端口(server.port)基础上,进行一定偏移量自动生成。

端口 与主端口的偏移量 描述
9848 1000 客户端gRPC请求服务端端口,用于客户端向服务端发起连接和请求
9849 1001 服务端gRPC请求服务端端口,用于服务间同步等

准备工作

首先准备一个SpringCloud项目的基础脚手架

引入grpc相关依赖,用于生成java端代码

<properties>
    <!-- 依赖相关配置 -->
    <io.grpc.version>1.30.0</io.grpc.version>
    <!-- 插件相关配置 -->
    <os-maven-plugin.version>1.6.2</os-maven-plugin.version>
    <protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version>
</properties>
<dependencies>
    <!-- 引入 gRPC Protobuf 依赖,因为使用它作为序列化库 -->
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>${io.grpc.version}</version>
    </dependency>
    <!-- 引入 gRPC Stub 依赖,因为使用它作为 gRPC 客户端库 -->
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>${io.grpc.version}</version>
    </dependency>
</dependencies>
<build>
    <extensions>
        <!-- os-maven-plugin 插件,从 OS 系统中获取参数 -->
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>${os-maven-plugin.version}</version>
        </extension>
    </extensions>
    <plugins>
        <!-- protobuf-maven-plugin 插件,通过 protobuf 文件,生成 Service 和 Message 类 -->
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>${protobuf-maven-plugin.version}</version>
            <configuration>
                <pluginId>grpc-java</pluginId>
                <protocArtifact>com.google.protobuf:protoc:3.9.1:exe:${os.detected.classifier}</protocArtifact>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:${io.grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

新建目录/src/main/proto 攥写proto文件

UserService.proto

syntax = "proto3";
option java_multiple_files = true;

package com.test.grpc.api;

message UserGetRequest {
    int32 id = 1;
}

message UserGetResponse {
    int32 id = 1;
    string name = 2;
    int32 gender = 3;
}

message UserCreateRequest {
    string name = 1;
    int32 gender = 2;
}

message UserCreateResponse {
    int32 id = 1;
}

service UserService {
    rpc get(UserGetRequest) returns (UserGetResponse);
    rpc create(UserCreateRequest) returns (UserCreateResponse);
    rpc getIter(UserGetRequest) returns (stream UserGetResponse);   # 流返回
    rpc callAStream(stream UserGetRequest) returns (stream UserGetResponse);  # 双向流通讯
}

生成java端代码

mvn clean compile

生成的代码可在target目录下看到

image-20211012115632695

服务端

引入服务端依赖

<!-- 引入 gRPC Server Starter 依赖,实现对 gRPC 的自动配置 -->
<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-server-spring-boot-starter</artifactId>
    <version>2.12.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.example</groupId>
    <artifactId>api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--必备: 注册中心客户端-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--如没有使用sentinel则需要引入hystrix-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

配置文件

grpc:
  server:
    port: 3333
server:
  port: 2223
spring:
  application:
    name: test-server
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        enabled: true

实现UserService

@GrpcService
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
    
    

    @Override
    public void get(UserGetRequest request, StreamObserver<UserGetResponse> responseObserver) {
    
    
        responseObserver.onNext(UserGetResponse.newBuilder().setName("aa").setGender(1).build());
        responseObserver.onCompleted();
    }

    @Override
    public void create(UserCreateRequest request, StreamObserver<UserCreateResponse> responseObserver) {
    
    
        responseObserver.onNext(UserCreateResponse.newBuilder().setId(1).build());
        responseObserver.onCompleted();
    }

    @SneakyThrows
    @Override
    public void getIter(UserGetRequest request, StreamObserver<UserGetResponse> responseObserver) {
    
    
        responseObserver.onNext(UserGetResponse.newBuilder().setId(1).build());
        TimeUnit.SECONDS.sleep(1);
        responseObserver.onNext(UserGetResponse.newBuilder().setId(2).build());
        TimeUnit.SECONDS.sleep(3);
        responseObserver.onNext(UserGetResponse.newBuilder().setId(3).build());
        responseObserver.onCompleted();
    }

    @Override
    public StreamObserver<UserGetRequest> callAStream(StreamObserver<UserGetResponse> responseObserver) {
    
    
        return new StreamObserver<UserGetRequest>() {
    
    
            @Override
            public void onNext(UserGetRequest value) {
    
    
                responseObserver.onNext(UserGetResponse.newBuilder().setId(value.getId()).build());
                System.out.println(value.toString());
            }

            @Override
            public void onError(Throwable t) {
    
    
                t.printStackTrace();
            }

            @Override
            public void onCompleted() {
    
    
                responseObserver.onCompleted();
            }
        };
    }
}

至此,一个基于gRPC的springCloud服务就完成了

启动服务,在nacos服务详情中就可以看到该服务的注册信息

image-20211012141706549

客户端

引入客户端依赖

<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-client-spring-boot-starter</artifactId>
    <version>2.12.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.example</groupId>
    <artifactId>api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--必备: 注册中心客户端-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

配置文件

server:
  port: 2222
grpc:
  client:
    test-server:  # 服务名称
      negotiation-type: plaintext
      enableKeepAlive: true
      keepAliveWithoutCalls: true
spring:
  application:
    name: test-client
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace:

调用gRPC服务

@RestController
@Slf4j
public class TestController {
    
    

    // 阻塞gRPC
    @GrpcClient("test-server")
    private UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub;
    // 非阻塞gRPC 获得Future对象
    @GrpcClient("test-server")
    private UserServiceGrpc.UserServiceFutureStub userServiceFutureStub;
    // 双向流通讯
    @GrpcClient("test-server")
    private UserServiceGrpc.UserServiceStub userServiceStub;

    @GetMapping("test1")
    public String test1() {
    
    
        // 构建请求数据
        UserGetRequest request = UserGetRequest.newBuilder().setId(1).build();
        // 执行gRPC请求
        UserGetResponse userGetResponse = userServiceBlockingStub.get(request);
        return userGetResponse.toString();
    }

    @SneakyThrows
    @GetMapping("test2")
    public String test2() {
    
    
        // 构建请求数据
        UserGetRequest request = UserGetRequest.newBuilder().setId(1).build();
        // 执行gRPC请求
        ListenableFuture<UserGetResponse> future = userServiceFutureStub.get(request);
        return future.get().toString();
    }

    @GetMapping("test3")
    public String test3() {
    
    
        // 构建请求数据
        UserGetRequest request = UserGetRequest.newBuilder().setId(1).build();
        // 执行gRPC请求
        Iterator<UserGetResponse> iter = userServiceBlockingStub.getIter(request);
        // 阻塞等待
        while (iter.hasNext()) {
    
    
            System.out.println(iter.next().toString());
        }
        return "1";
    }

    private StreamObserver<UserGetRequest> requestObserver = null;

    @GetMapping("test4")
    public String test4() {
    
    
        // 创建连接
        ClientCallStreamObserver<UserGetResponse> responseObserver = new ClientCallStreamObserver<UserGetResponse>() {
    
    
            @Override
            public void onNext(UserGetResponse value) {
    
    
                log.info("on next:{}", value.toString());
            }

            @Override
            public void onError(Throwable t) {
    
    
                t.printStackTrace();
            }

            @Override
            public void onCompleted() {
    
    
                requestObserver.onCompleted();
            }

            @Override
            public boolean isReady() {
    
    
                return true;
            }

            @Override
            public void setOnReadyHandler(Runnable onReadyHandler) {
    
    

            }

            @Override
            public void disableAutoInboundFlowControl() {
    
    

            }

            @Override
            public void request(int count) {
    
    

            }

            @Override
            public void setMessageCompression(boolean enable) {
    
    

            }

            @Override
            public void cancel(@Nullable String message, @Nullable Throwable cause) {
    
    
                log.info("cancel:{}", message);
            }
        };
        this.requestObserver = userServiceStub.callAStream(responseObserver);
        return "1";
    }

    @GetMapping("test5")
    public String test5() {
    
    
        // 发送消息
        requestObserver.onNext(UserGetRequest.newBuilder().setId(1).build());
        return "1";
    }

}

启动客户端测试5个接口


至此我们就学会了如何整合Spring cloud+gRPC+Nacos

猜你喜欢

转载自blog.csdn.net/qq_21046665/article/details/120722942