什么是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目录下看到

grpc 高效传送大数据 grpc 传输大文件性能_cloud

服务端

引入服务端依赖

<!-- 引入 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服务详情中就可以看到该服务的注册信息

grpc 高效传送大数据 grpc 传输大文件性能_grpc_02

客户端

引入客户端依赖

<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