java版gRPC实战之五:双向流

  1. 开发客户端应用

  2. 验证

源码下载

  • 本篇实战中的完整源码可在GitHub下载到,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):

| 名称 | 链接 | 备注 |

| :-- | :-- | :-- |

| 项目主页 | https://github.com/zq2599/blog_demos | 该项目在GitHub上的主页 |

| git仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该项目源码的仓库地址,https协议 |

| git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |

  • 这个git项目中有多个文件夹,《java版gRPC实战》系列的源码在grpc-tutorials文件夹下,如下图红框所示:

在这里插入图片描述

  • grpc-tutorials文件夹下有多个目录,本篇文章对应的服务端代码在double-stream-server-side目录下,客户端代码在double-stream-client-side目录下,如下图:

在这里插入图片描述

在proto文件中定义双向流类型的gRPC接口

  • 首先要做的就是定义gRPC接口,打开mall.proto,在里面新增方法和相关的数据结构,需要重点关注的是BatchDeduct方法的入参ProductOrder和返回值DeductReply都添加了stream修饰(ProductOrder是上一章定义的),代表该方法是双向流类型:

// gRPC服务,这是个在线商城的库存服务

service StockService {

// 双向流式:批量扣减库存

rpc BatchDeduct (stream ProductOrder) returns (stream DeductReply) {}

}

// 扣减库存返回结果的数据结构

message DeductReply {

// 返回码

int32 code = 1;

// 描述信息

string message = 2;

}

  • 双击下图红框中的task即可生成java代码:

在这里插入图片描述

  • 生成下图红框中的文件,即服务端定义和返回值数据结构:

在这里插入图片描述

  • 接下来开发服务端;

开发服务端应用

  • 在父工程grpc-turtorials下面新建名为double-stream-server-side的模块,其build.gradle内容如下:

// 使用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’

}

  • 配置文件application.yml:

spring:

application:

name: double-stream-server-side

gRPC有关的配置,这里只需要配置服务端口号

grpc:

server:

port: 9901

  • 启动类DoubleStreamServerSideApplication.java的代码就不贴了,普通的springboot启动类而已;

  • 重点是提供grpc服务的GrpcServerService.java,咱们要做的就是给上层框架返回一个匿名类,至于里面的onNext、onCompleted方法何时被调用是上层框架决定的,另外还准备了成员变量totalCount,这样就可以记录总数了,由于请求参数是流,因此匿名类的onNext会被多次调用,并且由于返回值是流,因此onNext中调用了responseObserver.onNext方法来响应流中的每个请求,这样客户端就不断收到服务端的响应数据(即客户端的onNext方法会被多次调用):

package grpctutorials;

import com.bolingcavalry.grpctutorials.lib.DeductReply;

import com.bolingcavalry.grpctutorials.lib.ProductOrder;

import com.bolingcavalry.grpctutorials.lib.StockServiceGrpc;

import io.grpc.stub.StreamObserver;

import lombok.extern.slf4j.Slf4j;

import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService

@Slf4j

public class GrpcServerService extends StockServiceGrpc.StockServiceImplBase {

@Override

public StreamObserver batchDeduct(StreamObserver responseObserver) {

// 返回匿名类,给上层框架使用

return new StreamObserver() {

private int totalCount = 0;

@Override

public void onNext(ProductOrder value) {

log.info(“正在处理商品[{}],数量为[{}]”,

value.getProductId(),

value.getNumber());

// 增加总量

totalCount += value.getNumber();

int code;

String message;

// 假设单数的都有库存不足的问题

if (0 == value.getNumber() % 2) {

code = 10000;

message = String.format(“商品[%d]扣减库存数[%d]成功”, value.getProductId(), value.getNumber());

} else {

code = 10001;

message = String.format(“商品[%d]扣减库存数[%d]失败”, value.getProductId(), value.getNumber());

}

responseObserver.onNext(DeductReply.newBuilder()

.setCode(code)

.setMessage(message)

.build());

}

@Override

public void onError(Throwable t) {

log.error(“批量减扣库存异常”, t);

}

@Override

public void onCompleted() {

log.info(“批量减扣库存完成,共计[{}]件商品”, totalCount);

responseObserver.onCompleted();

}

};

}

}

开发客户端应用

  • 在父工程grpc-turtorials下面新建名为double-stream-server-side的模块,其build.gradle内容如下:

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

}

  • 配置文件application.yml,设置自己的web端口号和服务端地址:

server:

port: 8082

spring:

application:

name: double-stream-client-side

grpc:

client:

gRPC配置的名字,GrpcClient注解会用到

double-stream-server-side:

gRPC服务端地址

address: ‘static://127.0.0.1:9901’

enableKeepAlive: true

keepAliveWithoutCalls: true

negotiationType: plaintext

  • 启动类DoubleStreamClientSideApplication.java的代码就不贴了,普通的springboot启动类而已;

  • 正常情况下我们都是用StreamObserver处理服务端响应,这里由于是异步响应,需要额外的方法从StreamObserver中取出业务数据,于是定一个新接口,继承自StreamObserver,新增getExtra方法可以返回String对象,详细的用法稍后会看到:

package com.bolingcavalry.grpctutorials;

import io.grpc.stub.StreamObserver;

public interface ExtendResponseObserver extends StreamObserver {

String getExtra();

}

  • 重头戏来了,看看如何远程调用双向流类型的gRPC接口,代码中已经添加详细注释:

package grpctutorials;

import com.bolingcavalry.grpctutorials.lib.DeductReply;

import com.bolingcavalry.grpctutorials.lib.ProductOrder;

import com.bolingcavalry.grpctutorials.lib.StockServiceGrpc;

import io.grpc.stub.StreamObserver;

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(“double-stream-server-side”)

private StockServiceGrpc.StockServiceStub stockServiceStub;

/**

  • 批量减库存

  • @param count

  • @return

*/

public String batchDeduct(int count) {

CountDownLatch countDownLatch = new CountDownLatch(1);

// responseObserver的onNext和onCompleted会在另一个线程中被执行,

// ExtendResponseObserver继承自StreamObserver

ExtendResponseObserver responseObserver = new ExtendResponseObserver() {

// 用stringBuilder保存所有来自服务端的响应

private StringBuilder stringBuilder = new StringBuilder();

@Override

public String getExtra() {

return stringBuilder.toString();

}

/**

  • 客户端的流式请求期间,每一笔请求都会收到服务端的一个响应,

  • 对应每个响应,这里的onNext方法都会被执行一次,入参是响应内容

  • @param value

*/

@Override

public void onNext(DeductReply value) {

log.info(“batch deduct on next”);

// 放入匿名类的成员变量中

stringBuilder.append(String.format(“返回码[%d],返回信息:%s
” , value.getCode(), value.getMessage()));

}

@Override

public void onError(Throwable t) {

log.error(“batch deduct gRPC request error”, t);

stringBuilder.append("batch deduct gRPC error, " + t.getMessage());

countDownLatch.countDown();

}

/**

  • 服务端确认响应完成后,这里的onCompleted方法会被调用

*/

@Override

public void onCompleted() {

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
直到现在。**

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-ND8NFQ0M-1715756619187)]

[外链图片转存中…(img-IAqAJfUO-1715756619188)]

[外链图片转存中…(img-bsrCh7BS-1715756619188)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值