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.;
"Java version of gRPC combat" full series of links
- Use proto to generate code
- Service release and call
- server stream
- client stream
- Bidirectional stream
- client dynamically obtains the server address
- Based on eureka registration discovery
Overview of this article
- This article is the fourth in the "java version of gRPC combat" series. The previous article has mastered server-side flow, which is suitable for scenarios where large amounts of data are obtained from the server-side. Today's goal is to master client-side flow-type services, including service providers and users. Development on both sides;
- Let's take a look at the introduction of client streaming RPC in official information: the client writes a sequence of messages and sends it to the server, also using streams. Once the client finishes writing the message, it waits for the server to finish reading and return its response;
- This article consists of the following parts:
- Summarize several important knowledge points in advance, and focus on these points in the later development process;
- Define the gRPC interface of the client stream type in the proto file, and then generate java code through proto;
- Develop server-side applications;
- Develop client applications;
- verify;
Summary in advance
In order to highlight the key points, here are a few key knowledge points in advance:
- The characteristic of the client stream is that the requester submits data to the responder in the form of a stream;
- In an RPC request, the requester can continuously submit data through a stream, until the onCompleted method of StreamObserver is called, the data submission is completed;
- Usually when we call a method, the data used in the method is passed in through the input parameters, but here is not the same, the data that the client sends to the server has nothing to do with the input parameters of the gRPC method, but is related to the return object of the method (Execute the onNext method of the returned object to pass the data to the server);
- After the client finishes uploading data in thread A, the server's response is executed in another thread B. Therefore, if thread A gets the server response, the asynchronous response method of thread B must be executed. There are many ways to wait. , I use CountDownLatch;
- On the server side, the code that the developer has to write is different from the previous web development. Instead of processing the data and returning it, it returns an instance of StreamObserver to the upper framework. The framework is responsible for the processing logic. Developers can focus on the implementation of StreamObserver. For example, to rewrite the onNext method, each time the client uploads a piece of data through the stream, the onNext method will be executed by the outer framework;
- If you are using IDEA, remember to check the box in the red box below, otherwise you may encounter problems related to lombok when running the application:
The above mentioned will be fully reflected in the next development process;
Source download
- The complete source code in this actual combat can be downloaded on GitHub. The address and link information are shown in the following table ( https://github.com/zq2599/blog_demos):
name | Link | Remark |
---|---|---|
Project homepage | https://github.com/zq2599/blog_demos | The project's homepage on GitHub |
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) | git@github.com:zq2599/blog_demos.git | The warehouse address of the source code of the project, ssh protocol |
- There are multiple folders in this git project. The source code of the "java version of gRPC combat" series is under the <font color="blue">grpc-tutorials</font> folder, as shown in the red box below:
- There are multiple directories under the <font color="blue">grpc-tutorials</font> folder. The server code for this article is in <font color="blue">client-stream-server-side</font > In the directory, the client code is in the <font color="blue">client-stream-client-side</font> directory, 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, open <font color="blue">mall.proto</font>, and add new methods and related data structures in it. What needs to be focused is to add before the input parameter ProductOrder of the AddToCart method Modified by <font color="red">stream</font>, it means that the method is a client-side stream type:
// gRPC服务,这是个在线商城的购物车服务
service CartService {
// 客户端流式:添加多个商品到购物车
rpc AddToCart (stream ProductOrder) returns (AddCartReply) {}
}
// 提交购物车时的产品信息
message ProductOrder {
// 商品ID
int32 productId = 1;
// 商品数量
int32 number = 2;
}
// 提交购物车返回结果的数据结构
message AddCartReply {
// 返回码
int32 code = 1;
// 描述信息
string message = 2;
}
- Double-click the task in the red box in the figure below to generate java code:
- Generate the file in the red box below:
- Next, develop the server;
Develop server-side applications
- Create a new module named <font color="blue">client-stream-server-side</font> under the parent project grpc-turtorials, and its build.gradle content is as follows:
// 使用springboot插件
plugins {
id 'org.springframework.boot'
}
dependencies {
implementation 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter'
// 作为gRPC服务提供方,需要用到此库
implementation 'net.devh:grpc-server-spring-boot-starter'
// 依赖自动生成源码的工程
implementation project(':grpc-lib')
// annotationProcessor不会传递,使用了lombok生成代码的模块,需要自己声明annotationProcessor
annotationProcessor 'org.projectlombok:lombok'
}
- Configuration file application.yml:
spring:
application:
name: client-stream-server-side
# gRPC有关的配置,这里只需要配置服务端口号
grpc:
server:
port: 9900
- The code for the startup class ClientStreamServerSideApplication.java will not be posted, just the normal springboot startup class;
- The key point is GrpcServerService.java that provides grpc service. Please read the code in conjunction with the fifth point of the previous summary. What we have to do is to return an anonymous class to the upper framework. As for when the onNext and onCompleted methods are called, the upper framework decides Yes, in addition, the member variable totalCount is also prepared, so that the total number can be recorded:
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<ProductOrder> addToCart(StreamObserver<AddCartReply> responseObserver) {
// 返回匿名类,给上层框架使用
return new StreamObserver<ProductOrder>() {
// 记录处理产品的总量
private int totalCount = 0;
@Override
public void onNext(ProductOrder value) {
log.info("正在处理商品[{}],数量为[{}]",
value.getProductId(),
value.getNumber());
// 增加总量
totalCount += value.getNumber();
}
@Override
public void onError(Throwable t) {
log.error("添加购物车异常", t);
}
@Override
public void onCompleted() {
log.info("添加购物车完成,共计[{}]件商品", totalCount);
responseObserver.onNext(AddCartReply.newBuilder()
.setCode(10000)
.setMessage(String.format("添加购物车完成,共计[%d]件商品", totalCount))
.build());
responseObserver.onCompleted();
}
};
}
}
Develop client applications
- Create a new module named <font color="blue">client-stream-server-side</font> under the parent project grpc-turtorials, and its build.gradle content 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')
}
- Configuration file application.yml, set your own web port number and server address:
server:
port: 8082
spring:
application:
name: client-stream-client-side
grpc:
client:
# gRPC配置的名字,GrpcClient注解会用到
client-stream-server-side:
# gRPC服务端地址
address: 'static://127.0.0.1:9900'
enableKeepAlive: true
keepAliveWithoutCalls: true
negotiationType: plaintext
- The code for the startup class ClientStreamClientSideApplication.java will not be posted, just the normal springboot startup class;
- Under normal circumstances, we use StreamObserver to process the server response. Because it is an asynchronous response, an additional method is needed to retrieve the business data from the StreamObserver, so we set a new interface, inherited from StreamObserver, and add <font color="blue"> The getExtra</font> method can return a String object. The detailed usage will be seen later:
package com.bolingcavalry.grpctutorials;
import io.grpc.stub.StreamObserver;
public interface ExtendResponseObserver<T> extends StreamObserver<T> {
String getExtra();
}
- Here comes the highlight, let’s take a look at how to remotely call the client stream type gRPC interface. The points 2, 3, and 4 mentioned in the previous summary will all be involved. Detailed comments have been added to 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);
// responseObserver的onNext和onCompleted会在另一个线程中被执行,
// ExtendResponseObserver继承自StreamObserver
ExtendResponseObserver<AddCartReply> responseObserver = new ExtendResponseObserver<AddCartReply>() {
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("返回码[%d],返回信息:%s" , code, message);
countDownLatch.countDown();
}
};
// 远程调用,此时数据还没有给到服务端
StreamObserver<ProductOrder> requestObserver = cartServiceStub.addToCart(responseObserver);
for(int i=0; i<count; i++) {
// 发送一笔数据到服务端
requestObserver.onNext(build(101 + i, 1 + i));
}
// 客户端告诉服务端:数据已经发完了
requestObserver.onCompleted();
try {
// 开始等待,如果服务端处理完成,那么responseObserver的onCompleted方法会在另一个线程被执行,
// 那里会执行countDownLatch的countDown方法,一但countDown被执行,下面的await就执行完毕了,
// await的超时时间设置为2秒
countDownLatch.await(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("countDownLatch await error", e);
}
log.info("service finish");
// 服务端返回的内容被放置在requestObserver中,从getExtra方法可以取得
return responseObserver.getExtra();
}
/**
* 创建ProductOrder对象
* @param productId
* @param num
* @return
*/
private static ProductOrder build(int productId, int num) {
return ProductOrder.newBuilder().setProductId(productId).setNumber(num).build();
}
}
- Finally, a web interface can be used to verify remote calls through web requests:
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);
}
}
- Encoding is complete, start verification;
verify
- Start the server ClientStreamServerSideApplication:
- Start the client ClientStreamClientSideApplication:
- The browser enters <font color="blue"> http://localhost:8082/?count=100 </font>, the response is as follows, it can be seen that the remote call to the gRPC service is successful:
- The following is the server log, it can be seen that each data of the client has been processed one by one:
- The following is the client log. It can be seen that due to the role of CountDownLatch, the thread that initiated the gRPC request has been waiting for responseObserver.onCompleted to be executed in another thread before continuing:
- At this point, the client stream type gRPC service and its client development have been completed. This asynchronous operation is still different from our usual development of synchronous web interfaces. I hope this article can bring you some reference. The last type of actual combat: two-way flow;
You are not alone, Xinchen and original are with you all the way
- Java series
- Spring series
- Docker series
- kubernetes series
- Database + Middleware Series
- DevOps series
Welcome to pay attention to the public account: programmer Xin Chen
Search "Programmer Xin Chen" on WeChat, I am Xin Chen, and I look forward to traveling the Java world with you...
https://github.com/zq2599/blog_demos
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。