gRPC of ultra-high performance rpc framework quickly integrates gRPC+nacos+springCloud

What is gRPC

gRPC is an ultra-high-performance RPC framework that supports all mainstream languages ​​on the market.

  • gRPC can define the interface through protobuf, and the constraints on the interface can be stricter

  • Use protobuf serialization to greatly reduce the amount of transmitted data, thereby significantly improving power consumption, bandwidth, and performance

  • Based on http2 standard design, support two-way stream communication

  • Support asynchronous request, asynchronous return

scenes to be used

  • It is necessary to have stricter control over the interface. For example, to provide an interface to the outside of the company, we do not want the client to pass data at will. At this time, we can use gRPC to restrict the interface.
  • There are higher requirements for transmission performance. If the message body we transmit is too large, or the scheduling is too frequent and we do not want to affect the performance of the server, we can also consider using it. When using gRPC, our message body is larger than JSON or text transmission data volume Much smaller.

proto syntax

.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 Using variable length encoding is very inefficient for negative values. If your domain may have negative values, please use sint64 instead int32 int int int32 Fixnum or Bignum (as needed) int integer
uint32 Use variable length encoding uint32 int int/long uint32 Fixnum or Bignum (as needed) uint integer
uint64 Use variable length encoding uint64 long int/long uint64 Bignum head integer/string
sint32 Use variable-length encodings, which are much more efficient than int32 for negative values int32 int int int32 Fixnum or Bignum (as needed) int integer
sint64 Signed integer value using variable length encoding. More efficient than usual int64 when encoding. int64 long int/long int64 Bignum long integer/string
fixed32 Always 4 bytes, this type is more efficient than uint32 if the value is always greater than 228. uint32 int int uint32 Fixnum or Bignum (as needed) uint integer
fixed64 Always 8 bytes, this type is more efficient than uint64 if the value is always greater than 256. uint64 long int/long uint64 Bignum head integer/string
sfixed32 always 4 bytes int32 int int int32 Fixnum or Bignum (as needed) int integer
sfixed64 always 8 bytes int64 long int/long int64 Bignum long integer/string
bool bool boolean bool bool TrueClass/FalseClass bool boolean
string A string must be UTF-8 encoded or 7-bit ASCII encoded text. string String str/unicode string String (UTF-8) string string
bytes May contain byte data in any order. string ByteString str []byte String (ASCII-8BIT) ByteString string

repeatedThe keyword is used when we need to represent a collection

message GetByUidParams{
  repeated string uid = 1;
}

Indicates that the kv storage structure can use mapkeywords

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 option

name type desc
java_multiple_files bool For True, each message file will have a separate class file; otherwise, all messages are defined in the outerclass file
java_package str This field is optional and is used to identify the package of the generated java file. If not specified, the package defined in proto will be used. If the package is not specified, it will be generated in the root directory;
java_outer_classname str This field is optional and is used to specify the outerclass class name of the java class generated by the proto file. What is an outer class? Simply put, a class file is used to define the java classes corresponding to all messages. This class is outerclass; if not specified, the default is the camel case of the proto file

exception handling

When we call onErrorthe method, it means that the server returns abnormally. At this time, 不需要再调用onCompletedthe method

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

We only need to catch StatusRuntimeExceptionexceptions on the client side, this class inheritsRuntimeException

The exception class contains Statusand messagecan help us do business operations

integration Spring cloud++ gRPC_Nacos

We use SpringCloud as the project carrier to help us manage beans, use gRPC for communication between services, and use Nacos as the registration center to realize the load of gRPC

Note: gRPC supported by Nacos2.0

Compared with Nacos 1.X, Nacos 2.0 has added the gRPC communication method, so 2 ports need to be added. The newly added port is automatically generated with a certain offset on the basis of the configured main port (server.port).

port Offset from master port describe
9848 1000 The client gRPC requests the server port, which is used by the client to initiate a connection and request to the server
9849 1001 The server gRPC requests the server port, which is used for synchronization between services, etc.

Preparation

First prepare the basic scaffolding of a SpringCloud project

Introduce grpc-related dependencies for generating java-side code

<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>

Create a new directory /src/main/protoand write proto files

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);  # 双向流通讯
}

Generate java code

mvn clean compile

The generated code can be seen in the target directory

image-20211012115632695

Server

Introduce server-side dependencies

<!-- 引入 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>

configuration file

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

Implement 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();
            }
        };
    }
}

At this point, a gRPC-based springCloud service is completed

Start the service, and you can see the registration information of the service in the nacos service details

image-20211012141706549

client

Introduce client dependencies

<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>

configuration file

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:

Call gRPC service

@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";
    }

}

Start the client to test 5 interfaces


So far we have learned how to integrate Spring cloud++gRPCNacos

Guess you like

Origin blog.csdn.net/qq_21046665/article/details/120722942