Java version of grpc practice 4: client stream

Time:2021-12-1

Welcome to my GitHub

https://github.com/zq2599/blog_demos

Content: classification and summary of all original articles and supporting source code, involving Java, docker, kubernetes, Devops, etc;

Links to the full series of “java version grpc actual combat”

  1. Generate code with proto
  2. Service publishing and invocation
  3. Server stream
  4. Client stream
  5. Bidirectional flow
  6. The client dynamically obtains the server address
  7. Registration discovery based on Eureka

Overview of this article

  • This article is the fourth in the “java version grpc practice” series. The previous article has mastered the server-side stream, which is suitable for obtaining a large amount of data from the server. Today’s goal is to master the client-side stream type services, including the development of both service providers and users;
  • Let’s take a look at the official information on client streaming RPC: the client writes a message sequence and sends it to the server, which also uses streaming. Once the client completes writing the message, it waits for the server to complete reading and return its response;
  • This paper consists of the following parts:
  1. Summarize several important knowledge points in advance and focus on them later in the development process;
  2. Define the grpc interface of the client stream type in the proto file, and then generate java code through proto;
  3. Develop server applications;
  4. Develop client applications;
  5. verification;

Advance summary

In order to highlight the key points, several key knowledge points are given in advance:

  1. The characteristic of client stream is that the requester submits data to the responder in the form of stream;
  2. In an RPC request, the requester can submit data in a continuous stream until the oncompleted method of streamobserver is called;
  3. Normally, when we call a method, the data used inside the method is transmitted through the input parameter, but it is different here. The data to be transmitted from the client to the server has nothing to do with the input parameter of the grpc method, but is related to the return object of the method (the onnext method executing the return object can transmit the data to the server);
  4. After the client uploads the data in thread a, the server’s response is executed in another thread B. therefore, if thread a gets the server’s response, the asynchronous response method of thread B must be executed. There are many waiting methods. I use countdownlatch;
  5. On the server side, the code to be written by the developer is different from the previous web development. Instead of processing and returning the data, it returns a streamobserver instance to the upper framework, which is responsible for the processing logic. The developer can focus on developing the implementation of streamobserver, such as rewriting the onnext method, and the client uploads each data through the stream, The onnext method will be executed once by the outer framework;
  6. If you use idea, remember to check the box in the red box below, otherwise you may encounter Lombok related problems when running the application:

在这里插入图片描述

  • These mentioned above will be fully reflected in the next development process;

Source download

  • The complete source code in this actual combat can be downloaded from GitHub. The address and link information are shown in the table below(https://github.com/zq2599/blog_demos):
name link remarks
Project Home https://github.com/zq2599/blog_demos The project is on the GitHub home page
Git warehouse address (HTTPS) https://github.com/zq2599/blog_demos.git The warehouse address of the source code of the project, HTTPS protocol
Git warehouse address (SSH) [email protected]:zq2599/blog_demos.git The project source code warehouse address, SSH protocol
  • There are multiple folders in this git project. The source code of the Java version of grpc practical combat series is ingrpc-tutorialsFolder, as shown in the red box below:

在这里插入图片描述

  • grpc-tutorialsThere are multiple directories under the folder. The server code corresponding to this article is inclient-stream-server-sideDirectory, the client code is inclient-stream-client-sideDirectory, as shown below:

在这里插入图片描述

Define the grpc interface of the client stream type in the proto file

  • The first thing to do is to define the grpc interface and open itmall.proto, add new methods and related data structures in it. What needs to be paid attention to is that the input parameter of addtocart method is added in front of productorderstreamModifier, representing that the method is a client stream type:
//Grpc service, which is a shopping cart service in an online mall
service CartService {
    //Client streaming: add multiple items to shopping cart
    rpc AddToCart (stream ProductOrder) returns (AddCartReply) {}
}

//Product information when submitting shopping cart
message ProductOrder {
    //Item ID
    int32 productId = 1;
    //Quantity of goods
    int32 number = 2;
}

//Data structure for submitting shopping cart return results
message AddCartReply {
    //Return code
    int32 code = 1;
    //Description information
    string message = 2;
}
  • Double click the task in the red box below to generate java code:

在这里插入图片描述

  • Generate the file in the red box below:

在这里插入图片描述

  • Next, develop the server;

Developing server applications

  • Under the parent project grpc tutorials, create a new project namedclient-stream-server-sideThe content of build.gradle is as follows:
//Using the springboot plug-in
plugins {
    id 'org.springframework.boot'
}

dependencies {
    implementation 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter'
    //As a grpc service provider, you need to use this library
    implementation 'net.devh:grpc-server-spring-boot-starter'
    //Projects that rely on automatic source code generation
    implementation project(':grpc-lib')
    //The annotation processor will not be passed. Modules that use Lombok generated code need to declare the annotation processor themselves
    annotationProcessor 'org.projectlombok:lombok'
}
  • Configuration file application.yml:
spring:
  application:
    name: client-stream-server-side
#For the configuration related to grpc, only the service port number needs to be configured here
grpc:
  server:
    port: 9900
  • The code of the startup class clientstreamserversideapplication.java will not be pasted. It is just an ordinary springboot startup class;

  • The key point is grpcserverservice.java, which provides grpc service. Please read the code in combination with the fifth point of the previous summary. What we need to do is to return an anonymous class to the upper framework. When the onnext and oncompleted methods are called is determined by the upper framework. In addition, the member variable totalcount is prepared to record the total number:

package com.bolingcavalry.grpctutorials;

import com.bolingcavalry.grpctutorials.lib.AddCartReply;
import com.bolingcavalry.grpctutorials.lib.CartServiceGrpc;
import com.bolingcavalry.grpctutorials.lib.ProductOrder;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService
@Slf4j
public class GrpcServerService extends CartServiceGrpc.CartServiceImplBase {

    @Override
    public StreamObserver addToCart(StreamObserver responseObserver) {
        //Returns an anonymous class for use by the upper framework
        return new StreamObserver() {

            //Record the total amount of products processed
            private int totalCount = 0;

            @Override
            public void onNext(ProductOrder value) {
                Log.info ("processing item [{}], quantity [{}]",
                        value.getProductId(),
                        value.getNumber());

                //Increase total
                totalCount += value.getNumber();
            }

            @Override
            public void onError(Throwable t) {
                Log. Error ("add shopping cart exception", t);
            }

            @Override
            public void onCompleted() {
                Log.info ("add shopping cart, total [{}] items", totalcount);
                responseObserver.onNext(AddCartReply.newBuilder()
                                                    .setCode(10000)
                                                    . setMessage (string. Format ("add shopping cart, total [% D] items", totalcount))
                                                    .build());
                responseObserver.onCompleted();
            }
        };
    }
}

Developing client applications

  • Under the parent project grpc tutorials, create a new project namedclient-stream-server-sideThe content of build.gradle is as follows:
plugins {
    id 'org.springframework.boot'
}

dependencies {
    implementation 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'net.devh:grpc-client-spring-boot-starter'
    implementation project(':grpc-lib')
}
  • Configure the file application.yml and set your own web port number and server address:
server:
  port: 8082
spring:
  application:
    name: client-stream-client-side

grpc:
  client:
    #The name of grpc configuration. Grpcclient annotation will be used
    client-stream-server-side:
      #Grpc server address
      address: 'static://127.0.0.1:9900'
      enableKeepAlive: true
      keepAliveWithoutCalls: true
      negotiationType: plaintext
  • The code of the startup class clientstreamclientsideapplication.java will not be pasted. It is just an ordinary springboot startup class;
  • Under normal circumstances, we use streamobserver to process the server response. Because it is an asynchronous response, we need additional methods to fetch business data from streamobserver, so we set a new interface, inherit from streamobserver and add a new interfacegetExtraMethod can return a string object. See the detailed usage later:
package com.bolingcavalry.grpctutorials;

import io.grpc.stub.StreamObserver;

public interface ExtendResponseObserver extends StreamObserver {
    String getExtra();
}
  • Here’s the play. Let’s see how to remotely call the grpc interface of client stream type. Points 2, 3 and 4 mentioned in the previous summary will be involved. Detailed comments have been added in the code:
package com.bolingcavalry.grpctutorials;

import com.bolingcavalry.grpctutorials.lib.AddCartReply;
import com.bolingcavalry.grpctutorials.lib.CartServiceGrpc;
import com.bolingcavalry.grpctutorials.lib.ProductOrder;
import io.grpc.stub.StreamObserver;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class GrpcClientService {

    @GrpcClient("client-stream-server-side")
    private CartServiceGrpc.CartServiceStub cartServiceStub;

    public String addToCart(int count) {
        
        CountDownLatch countDownLatch = new CountDownLatch(1);
        
        //Onnext and oncompleted of responseobserver will be executed in another thread,
        //Extendresponseobserver inherits from streamobserver
        ExtendResponseObserver responseObserver = new ExtendResponseObserver() {

            String extraStr;

            @Override
            public String getExtra() {
                return extraStr;
            }

            private int code;

            private String message;

            @Override
            public void onNext(AddCartReply value) {
                log.info("on next");
                code = value.getCode();
                message = value.getMessage();
            }

            @Override
            public void onError(Throwable t) {
                log.error("gRPC request error", t);
                extraStr = "gRPC error, " + t.getMessage();
                countDownLatch.countDown();
            }

            @Override
            public void onCompleted() {
                log.info("on complete");
                Extrastr = string. Format ("return code [% D], return information:% s", code, message);
                countDownLatch.countDown();
            }
        };
        
        //Remote call. At this time, the data has not been given to the server
        StreamObserver requestObserver = cartServiceStub.addToCart(responseObserver);
        
        for(int i=0; i
  • Finally, make a web interface to verify the remote call through the web request:
package com.bolingcavalry.grpctutorials;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
public class GrpcClientController {

    @Autowired
    private GrpcClientService grpcClientService;

    @RequestMapping("/")
    public String printMessage(@RequestParam(defaultValue = "1") int count) {
        return grpcClientService.addToCart(count);
    }
}
  • After coding, start verification;

verification

  • Start the client streamserversideapplication on the server:

在这里插入图片描述

  • Start client streamclientsideapplication:

在这里插入图片描述

在这里插入图片描述

  • The following is the server log. It can be seen that each data of the client is processed one by one:

在这里插入图片描述

  • The following is the client log. It can be seen that due to the function of countdownlatch, the thread initiating the grpc request has been waiting for responseobserver.oncompleted, and will not continue to execute until another thread is executed:

在这里插入图片描述

  • So far, the grpc service of client stream type and its client development have been completed. This asynchronous operation is still different from the synchronous web interface we usually develop. I hope this paper can bring you some references. In the next article, we will practice the last type: two-way streaming;

You’re not alone. Xinchen’s original accompanies you all the way

  1. Java series
  2. Spring collection
  3. Docker series
  4. Kubernetes series
  5. Database + middleware series
  6. Devops series

Welcome to the official account: programmer Xin Chen

Wechat search “programmer Xinchen”, I’m Xinchen, looking forward to traveling with you in the Java World
https://github.com/zq2599/blog_demos