【橘子分布式】gRPC(编程篇-中)

一、简介

我们之前已经完成了对于api模块的开发,也就是已经生成了基础的类和对应的接口,现在我们需要完成的是client和server端的开发。其实如同thrift一样,现在要做的就是实现我们之前定义的service里面的hello方法,里面写我们的业务逻辑,然后通过grpc的server发布暴露出去给客户端使用。

// 定义服务
service HelloService{/* 简单rpc,参数为HelloRequest类型,返回类型为HelloResponse */rpc hello(HelloRequest) returns (HelloResponse){}
}

ok,我们就先来实现serve模块,然后再实现client模块。

二、server模块

我们创建一个新的模块叫做rpc-grpc-service,并且在其pom依赖中引入api这个公共模块。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.levi</groupId><artifactId>rpc</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>rpc-grpc-service</artifactId><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>com.levi</groupId><artifactId>rpc-grpc-api</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies></project>

1、业务实现

ok,我们前面说过了,我们其实要实现的业务接口或者说重写的接口实现方法是HelloServiceGrpc.HelloServiceImplBase这个Base的内部类,在这个里面我们覆盖我们的业务方法。
我们重新来回顾一下我们的这个接口和方法。

// 定义请求接口参数
message HelloRequest{string name = 1;
}// 定义接口响应参数
message HelloResponse{string result = 1;
}// 定义服务
service HelloService{/* 简单rpc,参数为HelloRequest类型,返回类型为HelloResponse */rpc hello(HelloRequest) returns (HelloResponse){}
}

我们看到我们的请求类里面是一个参数name,响应类里面是一个参数result,并且接口的方法叫做hello。ok,我们就来实现覆盖这个方法。

/ 服务端实现类
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {/*1. 接受client提交的参数  request.getParameter()2. 业务处理 service+dao 调用对应的业务功能。3. 提供返回值*/@Overridepublic void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {}
}
因为HelloServiceGrpc.HelloServiceImplBase不是一个接口,所以我们要继承然后覆盖方法。并且我们看到这个方法和我们当初定义的略有不同
第一个参数没毛病就是HelloProto.HelloRequest,但是这个方法没有返回值,他是个void。这就是grpc的规范,他的返回是通过第二个参数
StreamObserver<HelloProto.HelloResponse> responseObserver来给客户端返回的,因为grpc有流式的返回,所以它是通过这个返回的,
如果弄成返回值就不方便以流的形式不断的推给客户端了。而且responseObserver的泛型就是我们定义的返回类型HelloProto.HelloResponse

于是我们就来实现这个方法。

/ 服务端实现类
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {/*1. 接受client提交的参数  request.getParameter()2. 业务处理 service+dao 调用对应的业务功能。3. 提供返回值*/@Overridepublic void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {//1.接受client的请求参数,获取我们定义的那个属性nameString name = request.getName();//2.业务处理System.out.println("name parameter "+name);//3.封装响应//3.1 创建相应对象的构造者HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();//3.2 填充数据,填充返回值内容builder.setResult("hello method invoke ok");//3.3 封装响应HelloProto.HelloResponse helloResponse = builder.build();// 4. 响应clientresponseObserver.onNext(helloResponse);// 5. 响应完成responseObserver.onCompleted();}
}

2、服务发布

现在我们实现了我们的业务,我们就要把这个服务接口发布出去给客户端做rpc调用。
我们定义一个服务类,然后实现server,并且暴露端口。

package com.levi;import com.levi.service.HelloServiceImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;import java.io.IOException;public class GrpcServer1 {public static void main(String[] args) throws IOException, InterruptedException {//1. 绑定端口ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000);//2. 发布服务,这里可能会发布很多业务,我们这里就是一个HelloServiceImpl,实际可能还会有别的业务serverBuilder.addService(new HelloServiceImpl());//serverBuilder.addService(new UserServiceImpl());//3. 创建服务对象Server server = serverBuilder.build();// 启动服务server.start();// 阻塞等待客户端的连接访问,底层其实就是nettyserver.awaitTermination();;}
}

此时我们就暴露出去我们的一个helo的业务实现了。

三、client模块

我们创建一个名为rpc-grpc-client的模块,并且引入api公共模块。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.levi</groupId><artifactId>rpc</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>rpc-grpc-client</artifactId><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>com.levi</groupId><artifactId>rpc-grpc-api</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies></project>

我们现在已经把服务端的东西暴露在了9000这个端口,现在就可以在客户端通过grpc的stub代理来访问了。

package com.levi;import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;// client通过代理对象完成远端对象的调用
public class GrpcClient1 {public static void main(String[] args) {//1.创建通信的管道,usePlaintext以普通文本进行访问ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build();//2.获得代理对象 stub进行调用try {// 我们这里以阻塞的形式调用,也就是一直等返回值回来才往下走,其实这里就是获取的rpc调用的代理类,grpc给我们提供的就是stub这个,本质是一个东西HelloServiceGrpc.HelloServiceBlockingStub helloService = HelloServiceGrpc.newBlockingStub(managedChannel);//3. 完成RPC调用//3.1 准备参数HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();builder.setName("hello");HelloProto.HelloRequest helloRequest = builder.build();//3.1 进行功能rpc调用,获取相应的内容,像本地调用那样调用远程服务HelloProto.HelloResponse helloResponse = helloService.hello(helloRequest);String result = helloResponse.getResult();System.out.println("result = " + result);} catch (Exception e) {throw new RuntimeException(e);}finally {// 4. 关闭通道managedChannel.shutdown();}}
}

成功返回。
在这里插入图片描述

四、多值传递

我们之前在学习proto语法的时候提到过一个关键字repeated关键字,我们当时说被这个关键字修饰的属性是一个集合类型的字段,grpc会为它生成集合类型的get set方法,我们来做一个测试。

1、proto编写

我们重新定义一个proto文件的message和方法。

// 定义proto文件版本号
syntax = "proto3";// 生成一个java类即可
option java_multiple_files = false;
// 生成的java类的包名
option java_package = "com.levi";
// 外部类,这里就是HelloProto,实际开发你可以有多个proto管理不同业务类,然后各自的外部类都可以。比如OrderService就是Order.proto 外部类就是OrderProto
option java_outer_classname = "HelloProto";// 定义请求接口参数
message HelloRequest{string name = 1;
}// 定义接口响应参数
message HelloResponse{string result = 1;
}message ManyHelloRequest{repeated string names = 1;
}message ManyHelloResponse{repeated string result = 1;
}// 定义服务
service HelloService{/* 简单rpc,参数为HelloRequest类型,返回类型为HelloResponse */rpc hello(HelloRequest) returns (HelloResponse){}/* 服务端流式rpc,参数为ManyHelloRequest类型,返回类型为ManyHelloResponse */rpc manyHello(ManyHelloRequest) returns (ManyHelloResponse){}
}

我们在原来的基础上添加

message ManyHelloRequest{repeated string names = 1;
}message ManyHelloResponse{repeated string result = 1;
}
和一个
rpc manyHello(ManyHelloRequest) returns (ManyHelloResponse){}

旨在请求多个名字,返回也是多个。我们来编译结果。
在这里插入图片描述
我们看到message里面没问题,生成了我们要的。当然之前的那个也保留了。
在这里插入图片描述
相应的service里面的也没问题。我们就来修改一下我们的实现。当然为了保险最好刷新一下server和client的pom,重新引入一下api模块,idea有时候会抽风。

2、server端改写

package com.levi.service;import com.google.protobuf.ProtocolStringList;
import com.levi.HelloProto;
import com.levi.HelloServiceGrpc;
import io.grpc.stub.StreamObserver;
import org.slf4j.LoggerFactory;import java.util.ArrayList;
import java.util.List;// 服务端实现类
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {private static final String RES_PREFIX = "server#";@Overridepublic void manyHello(HelloProto.ManyHelloRequest request, StreamObserver<HelloProto.ManyHelloResponse> responseObserver) {//1.接受client的请求参数,我们看到此时就是一个nameList的集合了,因为它被repeated修饰了,当然他的类型是ProtocolStringList,是grpc自己的类型ProtocolStringList requestNamesList = request.getNamesList();//2.业务处理System.out.println("请求参数为:" + requestNamesList);// 给返回值的name都加一个前缀List<String> responseNamesList = new ArrayList<>();for (String requestName : requestNamesList) {responseNamesList.add(RES_PREFIX + requestName);}//3.封装响应//3.1 创建相应对象的构造者HelloProto.ManyHelloResponse.Builder builder = HelloProto.ManyHelloResponse.newBuilder();//3.2 填充数据,多个值要通过addAllResult,或者是下标的方式添加builder.addAllResult(responseNamesList);
//        for (int i = 0; i < requestNamesList.size(); i++) {
//            builder.setResult(i, requestNamesList.get(i));
//        }//3.3 封装响应HelloProto.ManyHelloResponse helloResponse = builder.build();// 4. 响应clientresponseObserver.onNext(helloResponse);// 5. 响应完成responseObserver.onCompleted();}/*1. 接受client提交的参数  request.getParameter()2. 业务处理 service+dao 调用对应的业务功能。3. 提供返回值*/@Overridepublic void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {//1.接受client的请求参数String name = request.getName();//2.业务处理System.out.println("name parameter "+name);//3.封装响应//3.1 创建相应对象的构造者HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();//3.2 填充数据builder.setResult("hello method invoke ok");//3.3 封装响应HelloProto.HelloResponse helloResponse = builder.build();// 4. 响应clientresponseObserver.onNext(helloResponse);// 5. 响应完成responseObserver.onCompleted();}
}

然后服务端不用改,还是暴露注册出去HelloServiceImpl。

package com.levi;import com.levi.service.HelloServiceImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;import java.io.IOException;public class GrpcServer1 {public static void main(String[] args) throws IOException, InterruptedException {//1. 绑定端口ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000);//2. 发布服务serverBuilder.addService(new HelloServiceImpl());//serverBuilder.addService(new UserServiceImpl());//3. 创建服务对象Server server = serverBuilder.build();// 启动服务server.start();// 阻塞等待server.awaitTermination();;}
}

此时我们需要来修改客户端代码。

3、cilent端改写

package com.levi;import com.google.protobuf.ProtocolStringList;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;import java.util.List;// client通过代理对象完成远端对象的调用
public class GrpcClient2 {public static void main(String[] args) {//1.创建通信的管道ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build();//2.获得代理对象 stub进行调用try {// 我们这里以阻塞的形式调用,也就是一直等返回值回来才往下走HelloServiceGrpc.HelloServiceBlockingStub helloService = HelloServiceGrpc.newBlockingStub(managedChannel);//3. 完成RPC调用//3.1 准备参数HelloProto.ManyHelloRequest.Builder builder = HelloProto.ManyHelloRequest.newBuilder();// 多值参数要这样添加或者以下标形式builder.addAllNames(List.of("levi","tom","jerry"));HelloProto.ManyHelloRequest helloRequest = builder.build();//3.1 进行功能rpc调用,获取相应的内容,像本地调用那样调用远程服务HelloProto.ManyHelloResponse helloResponse = helloService.manyHello(helloRequest);ProtocolStringList resultList = helloResponse.getResultList();System.out.println("resultList = " + resultList);} catch (Exception e) {throw new RuntimeException(e);}finally {// 4. 关闭通道managedChannel.shutdown();}}
}

我们启动服务端,然后客户端去请求。
在这里插入图片描述
没问题,这就是多值repeated关键字的使用方式。

五、关于服务端响应

我们在服务端响应客户端的时候用的是一个StreamObserver<HelloProto.ManyHelloResponse> responseObserver这个类给客户端响应的。我们简单解释一下这个操作。

public void manyHello(HelloProto.ManyHelloRequest request, StreamObserver<HelloProto.ManyHelloResponse> responseObserver) {...... 省略无关代码// 4. 响应client,这里其实就是把数据返回给了客户端responseObserver.onNext(helloResponse);// 5. 响应完成,这个操作其实是给这个通道设置一个标识,告诉客户端服务端这边传完了,客户端就会拿到数据开始继续往下走// 如果没有这个通知,客户端会一直缓存服务端的数据不会做解析返回。客户端也一直阻塞着。客户端会监听这个通知事件。responseObserver.onCompleted();}

相应的其实客户端给服务端也会有类似的操作,因为grpc是双向流,势必涉及客户端给服务端的操作。这个等我们后面再说。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/89760.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/89760.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 项目中数据同步之binlog和MQ

在 Spring Boot 项目中&#xff0c;“监听 binlog” 和 “业务代码中集成 MQ” 是实现数据同步、事件驱动的两种主流方法。 简单来说&#xff0c;这个选择可以概括为&#xff1a; 监听 Binlog (如使用 Canal)&#xff1a;像一个数据库的贴身秘书&#xff0c;它忠实地记录数据库…

MySQL 写入性能优化全攻略(附 GitHub 面试题项目链接)

面试中你可能会遇到这样的问题&#xff1a; &#x1f4ac; “假设你的接口一天收到百万级请求&#xff0c;MySQL 撑得住吗&#xff1f;你会怎么优化写入性能&#xff1f;” 刚开始我也懵过&#xff0c;后来不断复盘与总结&#xff0c;现在我可以用结构化方式给出一个相对完整的…

用Dynamic chunk去干掉tokenizer?

一般你们下AR模型的时候&#xff0c;都有这个&#xff0c;也就是tokenzier&#xff0c;tokenizer是干啥的&#xff0c;其实就是你的分词字典不光有specal的token对应的还有实际的对应的分词对应的代码&#xff0c;比如&#xff1a;也有tokenzier没显示的&#xff0c;比如&#…

Linux系统日志管理入门:journalctl命令完全指南

Linux系统日志管理入门&#xff1a;journalctl命令完全指南前言一、journalctl介绍二、基础使用&#xff1a;快速上手1. 查看全部日志2. 查看本次启动的日志3. 按时间筛选日志4. 按服务&#xff08;单元&#xff09;过滤日志三、常用参数与场景四、实战案例&#xff1a;解决实际…

神经网络的基本骨架——nn.Module的使用(torch.nn库)

在 PyTorch 中&#xff0c;nn.Module 是所有神经网络模块的基类&#xff0c;用于构建和组织深度学习模型。它提供了一系列工具和功能&#xff0c;使模型的定义、训练和部署更加高效和灵活。nn Neural Network&#xff08;神经网络&#xff09;核心作用&#xff1a;模块化设计&…

静态住宅IP和节点有什么区别?哪种更适合你的需求?

在跨境电商、社媒运营等业务中&#xff0c;“静态住宅IP”和“节点”常被混淆使用&#xff0c;但两者代表网络架构中不同层级的资源。选错可能导致账号风控、业务效率低下。IPdodo将在本篇文章中&#xff0c;从本质、业务场景到选择策略&#xff0c;为您一文道清两者之间的区别…

AI编程工具对比:Cursor、GitHub Copilot与Claude Code

文章目录AI编程工具对比&#xff1a;Cursor、GitHub Copilot与Claude Code一、产品定位与核心架构1.1 Cursor&#xff1a;AI原生IDE的代表1.2 GitHub Copilot&#xff1a;代码补全的行业标杆1.3 Claude Code&#xff1a;终端Agent的革新者二、核心功能深度对比2.1 代码生成与理…

车载传统ECU---MCU软件架构设计指南

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

SCSAI工业智能体的核心特征

一个覆盖工业全场景、由多个垂直领域智能体构成的开放生态平台。每个智能体都是具备独立能力的 “数字单元”&#xff0c;同时又能通过平台实现互联、协作与进化。一、工业智能体平台的核心定位本质&#xff1a;工业领域的 “智能体操作系统 应用市场”&#xff0c;为各类工业…

Java大视界:Java大数据在智能医疗电子健康档案数据挖掘与健康服务创新>

> 本文通过完整代码示例,揭秘如何用Java大数据技术挖掘电子健康档案价值,实现疾病预测、个性化健康管理等创新服务。 ### 一、智能医疗时代的数据金矿 电子健康档案(EHR)作为医疗数字化的核心载体,包含海量患者全生命周期健康数据。据统计,全球医疗数据量正以每年…

家庭KTV v1.1.9 | 曲库丰富,无限制免费K歌

家庭KTV是一款专门为家庭娱乐设计的K歌软件&#xff0c;旨在让用户在家中也能享受到与KTV相同的高质量K歌体验。该应用拥有丰富的歌曲库&#xff0c;包括经典老歌和当下流行热曲等多种类型&#xff0c;满足不同年龄段用户的需求。家庭KTV支持多种点歌方式&#xff0c;如按照歌手…

vscode 一直连不上远程,网络是通的,ssh 也能直接登录远程

vscode 一直连不上远程&#xff0c;网络是通的&#xff0c;ssh 也能直接登录远程&#xff0c;但vscode 死活连不上 解决办法&#xff1a; 取消勾选remote.SSH.useExecServer 打开 VS Code 设置&#xff08;Ctrl, → 搜索 useExecServer&#xff09; 取消对应的勾选即可

前端面试专栏-工程化:28.团队协作与版本控制(Git)

&#x1f525; 欢迎来到前端面试通关指南专栏&#xff01;从js精讲到框架到实战&#xff0c;渐进系统化学习&#xff0c;坚持解锁新技能&#xff0c;祝你轻松拿下心仪offer。 前端面试通关指南专栏主页 前端面试专栏规划详情 项目实战与工程化模块-团队协作与版本控制&#xff…

无标记点动捕:如何突破传统娱乐边界,打造沉浸式交互体验

你能想象在游戏交互中&#xff0c;你的动作和表情可以不用佩戴任何设备就实时映射在虚拟角色上吗&#xff1f;在传统娱乐中&#xff0c;用户体验常被设备束缚——手柄、传感器、标记点让用户无法彻底投入。而无标记点动捕技术作为一种将用户肢体转化为虚拟世界的“自然控制器”…

C#监听txt文档获取新数据

目录前言一、监听txt文档增加数据二、其他功能1. 设置开机自启动2. 禁止控制台窗口关闭按钮3. 阻止Ctrl C中断4. 防止程序退出&#xff08;无限循环&#xff09;总结前言 之前有个需求就是监听文件夹中最新的txt文档获取最新数据&#xff0c;还有其他功能&#xff0c;比如&am…

程序员管理与AIStarter开发:如何避免需求Bug,提升项目效率

大家好&#xff0c;我是熊哥&#xff01;今天聊聊程序员管理和AIStarter开发中的经验教训。创业公司项目常因需求不清出Bug&#xff0c;比如“管理员删管理员”这种低级错误&#xff0c;引发用户不满。熊哥亲测&#xff1a;程序员管理关键在于明确需求&#xff01;通过整理需求…

网络爬虫概念初解

大家好! 网络爬虫&#xff08;Web Crawler&#xff09;是一种自动化程序&#xff0c;能够模拟人类浏览行为&#xff0c;按照预设规则从互联网上抓取、解析和存储数据。它像一只“数字蜘蛛”&#xff0c;沿着网页链接爬行&#xff0c;高效采集目标信息。以下是核心要点&#xff…

Pytorch 使用报错 RuntimeError: Caught RuntimeError in DataLoader worker process 0.

这个错误是可能是由于在DataLoader的工作进程中尝试访问CUDA设备导致的。PyTorch的DataLoader使用多进程加载数据&#xff0c;而CUDA上下文不能在子进程中直接使用。修改前的代码为&#xff1a;def prepare_data(file_path):# 读取Excel文件df pd.read_excel(file_path, heade…

产品经理如何描述用户故事

作为资深产品经理&#xff0c;描述用户故事需超越基础模板&#xff0c;将其转化为驱动产品决策的战略工具。以下是融合实战经验的深度方法论&#xff0c;附高阶技巧和反例分析&#xff1a;一、用户故事的本质&#xff1a;需求的三维锚点 #mermaid-svg-AgAM5YJT6aKoD1EV {font-f…

Vue 结合 Zabbix API 获取服务器 CPU、内存、GPU 等数据

一、简介 Vue 结合 Zabbix API 可以实现对服务器 CPU、内存、GPU 等监控数据的动态获取与展示。Zabbix 是一款开源的监控工具&#xff0c;提供丰富的 API 接口供开发者调用。通过 Vue 前端框架&#xff0c;可以将 Zabbix 返回的数据以图表或表格形式直观呈现&#xff0c;便于运…