SpringBoot基于gRPC进行RPC调用
SpringBoot基于gRPC进行RPC调用
- 一、gRPC
- 1.1 什么是gRPC?
- 1.2 如何编写proto
- 1.3 数据类型及对应关系
- 1.4 枚举
- 1.5 数组
- 1.6 map类型
- 1.7 嵌套对象
- 二、SpringBoot gRPC
- 2.1 工程目录
- 2.2 jrpc-api
- 2.2.1 引入gRPC依赖
- 2.2.2 编写 .proto 文件
- 2.2.3 使用插件机制生产proto相关文件
- 2.2 jrpc-server
- 2.2.1 引入 `jrpc-api` 依赖
- 2.2.2 编写impl
- 2.2.3 编写Config
- 2.2.4 yaml
- 2.3 jrpc-client
- 2.3.1 引入 `jrpc-api` 依赖
- 2.3.2 编写config
- 2.3.3 yaml
- 2.3.4 测试验证
一、gRPC
1.1 什么是gRPC?
In gRPC, a client application can directly call a method on a server application on a different machine as if it were a local object, making it easier for you to create distributed applications and services. As in many RPC systems, gRPC is based around the idea of defining a service, specifying the methods that can be called remotely with their parameters and return types. On the server side, the server implements this interface and runs a gRPC server to handle client calls. On the client side, the client has a stub (referred to as just a client in some languages) that provides the same methods as the server.
在 gRPC 中,客户端应用程序可以直接调用服务器应用程序上的方法 在另一台机器上,就好像它是本地对象一样,使你更容易 创建分布式应用程序和服务。与许多 RPC 系统一样,gRPC 是 基于定义服务的思想,指定可以 使用其参数和返回类型进行远程调用。在服务器端, server 实现此接口并运行 gRPC 服务器来处理客户端调用。 在客户端,客户端有一个存根(在某些客户端中称为客户端 languages),它提供与服务器相同的方法。

gRPC 客户端和服务器可以在各种 环境 - 从 Google 内部的服务器到您自己的桌面 - 并且可以 用 gRPC 支持的任何语言编写。因此,例如,您可以轻松地 在 Java 中创建一个 gRPC 服务器,客户端使用 Go、Python 或 Ruby。另外 最新的 Google API 将具有其接口的 gRPC 版本,让您 轻松将 Google 功能构建到您的应用程序中。
gRPC 使用 proto buffers 作为服务定义语言,编写 proto 文件,即可完成服务的定义。

1.2 如何编写proto
syntax = "proto3";option java_multiple_files = true;
// 生成位置
option java_package = "com.lizq.jrpc.api";
option java_outer_classname = "UserService";package user;service User {rpc SayHello (UserRequest) returns (UserResponse) {}
}message UserRequest {string name = 1;int32 age = 2;string addr = 3;
}message UserResponse {string name = 1;int32 age = 2;string addr = 3;OtherMsg otherMsg = 4;map<string, string> otherMap = 5;// 嵌套对象message OtherMsg {string ext1 = 1;string ext2 = 2;}
}
syntax = "proto3";:指定使用的protobuf版本;option java_multiple_files = true;:如果为 false,则只会.java为此文件生成一个.proto文件,以及所有 Java 类/枚举/等。为顶级消息、服务和枚举生成的将嵌套在外部类中。如果为 true,.java将为每个 Java 类/枚举/等生成单独的文件。为顶级消息、服务和枚举生成,并且为此.proto文件生成的包装 Java 类将不包含任何嵌套类/枚举/等。 如果不生成 Java 代码,则此选项无效。package user;:定义本服务的包名,避免不同服务相同消息类型产生冲突;option java_package = "com.lizq.jrpc.api";:生成java文件包名;option java_outer_classname = "UserService";:生成java文件类名称。如果文件中没有明确 java_outer_classname指定,.proto则将通过将.proto文件名转换为驼峰式来构造类名(因此 foo_bar.proto变为FooBar.java)message UserResponse:定义服务的接口名称;rpc SayHello (UserRequest) returns (UserResponse) {}:远程调用方法名,参数及响应类型;message XXXXX{}:定义数据类型;
1.3 数据类型及对应关系
| .proto类型 | Notes | C++ Type | Java/Kotlin | Python |
|---|---|---|---|---|
| double | double | double | float | |
| float | float | float | float | |
| int32 | 使用可变长度编码。对负数进行编码效率低下——如果您的字段可能有负值,请改用 sint32。 | int32 | int | int |
| int64 | 使用可变长度编码。对负数进行编码效率低下——如果您的字段可能有负值,请改用 sint64。 | int64 | long | int/long |
| uint32 | 使用可变长度编码。 | uint32 | int | int/long |
| uint64 | 使用可变长度编码。 | uint64 | long | int/long |
| sint32 | 使用可变长度编码。带符号的 int 值。这些比常规 int32 更有效地编码负数。 | int32 | int | int |
| sint64 | 使用可变长度编码。带符号的 int 值。这些比常规 int64 更有效地编码负数。 | int64 | long | int/long |
| fixed32 | 总是四个字节。如果值通常大于 228,则比 uint32 更有效 | uint32 | int | int/long |
| fixed64 | 总是八个字节。如果值通常大于 256,则比 uint64 更有效。 | uint64 | long | int/long |
| sfixed32 | 总是四个字节。 | int32 | int | int |
| sfixed64 | 总是八个字节。 | int64 | long | int/long |
| bool | bool | boolean | bool | |
| string | 字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本,并且不能超过 232。 | string | String | str/unicode |
| bytes | 可能包含不超过 2 32的任意字节序列。 | string | ByteString | str (Python 2)、bytes (Python 3) |
1.4 枚举
enum Sex {NONE = 0;MAN = 1;WOMAN = 2;
}message UserRequest {string name = 1;int32 age = 2;string addr = 3;Sex sex = 4;
}
**注意:**第一个枚举的值必须为0,因为0 是默认值,0 必须是第一个,保持和proto2 兼容
1.5 数组
使用 repeated 关键字来定义数组。
message UserRequest {string name = 1;int32 age = 2;string addr = 3;Sex sex = 4;// 定义一个数组repeated string cellphones = 5;
}
1.6 map类型
在开发的过程中经常需要使用关联字段,很自然的想到使用map,protobuf也提供了map的类型。
message UserResponse {string name = 1;map<string, string> otherMap = 2;
}
注意: map 字段前面不能是repeated
1.7 嵌套对象
message UserResponse {string name = 1;int32 age = 2;string addr = 3;OtherMsg otherMsg = 4;map<string, string> otherMap = 5;// 嵌套对象message OtherMsg {string ext1 = 1;string ext2 = 2;}
}
二、SpringBoot gRPC
2.1 工程目录

2.2 jrpc-api
2.2.1 引入gRPC依赖
<dependency><groupId>io.grpc</groupId><artifactId>grpc-all</artifactId><version>1.28.1</version>
</dependency>
2.2.2 编写 .proto 文件
syntax = "proto3";option java_multiple_files = true;
// 生成位置
option java_package = "com.lizq.jrpc.api";
option java_outer_classname = "UserService";package user;service User {rpc SayHello (UserRequest) returns (UserResponse) {}
}message UserRequest {string name = 1;int32 age = 2;string addr = 3;
}message UserResponse {string name = 1;int32 age = 2;string addr = 3;OtherMsg otherMsg = 4;map<string, string> otherMap = 5;// 嵌套对象message OtherMsg {string ext1 = 1;string ext2 = 2;}
}
2.2.3 使用插件机制生产proto相关文件
在 jrpc-api pom.xml 中添加如下:
<build><extensions><extension><groupId>kr.motd.maven</groupId><artifactId>os-maven-plugin</artifactId><version>1.6.2</version></extension></extensions><plugins><plugin><groupId>org.xolstice.maven.plugins</groupId><artifactId>protobuf-maven-plugin</artifactId><version>0.6.1</version><configuration><!--<!– ${os.detected.classifier} 变量由${os.detected.name} 和 ${os.detected.arch} 组成--><protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact><pluginId>grpc-java</pluginId><pluginArtifact>io.grpc:protoc-gen-grpc-java:1.28.1:exe:${os.detected.classifier}</pluginArtifact><!--protoSourceRoot 默认src/main/proto--><protoSourceRoot>src/main/proto</protoSourceRoot></configuration><executions><execution><goals><goal>compile</goal><goal>compile-custom</goal></goals></execution></executions></plugin></plugins>
</build>
执行命令生产proto相关文件

将生成的文件拷贝到工程中,如下:

2.2 jrpc-server
jrpc-server 为 springboot 项目。
2.2.1 引入 jrpc-api 依赖
<dependency><groupId>com.example</groupId><artifactId>jrpc-api</artifactId><version>1.0.0-SNAPSHOT</version>
</dependency>
2.2.2 编写impl
@Service
public class UserServiceImpl extends UserGrpc.UserImplBase {@Overridepublic void sayHello(UserRequest request, StreamObserver<UserResponse> responseObserver) {Map<String, String> otherMap = new HashMap<>();otherMap.put("test", "testmap");UserResponse response = UserResponse.newBuilder().setName("server:" + request.getName()).setAddr("server:" + request.getAddr()).setAge(request.getAge()).setOtherMsg(UserResponse.OtherMsg.newBuilder().setExt1("ext1").setExt2("ext2").build()).putAllOtherMap(otherMap).build();responseObserver.onNext(response);responseObserver.onCompleted();}
}
2.2.3 编写Config
@Configuration
public class GrpcServerConfiguration {@Value("${grpc.server-port}")private int port;@Beanpublic Server server() throws Exception {System.out.println("Starting gRPC on port {}." + port);// 构建服务端ServerBuilder<?> serverBuilder = ServerBuilder.forPort(port);// 添加需要暴露的接口this.addService(serverBuilder);// startServer server = serverBuilder.build().start();System.out.println("gRPC server started, listening on {}." + port);// 添加服务端关闭的逻辑Runtime.getRuntime().addShutdownHook(new Thread(() -> {System.out.println("Shutting down gRPC server.");if (server != null) {// 关闭服务端server.shutdown();}System.out.println("gRPC server shut down successfully.");}));if (server != null) {// 服务端启动后直到应用关闭都处于阻塞状态,方便接收请求server.awaitTermination();}return server;}@Autowiredprivate UserServiceImpl userService;/*** 添加需要暴露的接口* @param serverBuilder*/private void addService(ServerBuilder<?> serverBuilder) {serverBuilder.addService(userService);}
}
2.2.4 yaml
server:port: 8081
spring:application:name: spring-boot-jrpc-server
grpc:server-port: 18081
2.3 jrpc-client
2.3.1 引入 jrpc-api 依赖
<dependency><groupId>com.example</groupId><artifactId>jrpc-api</artifactId><version>1.0.0-SNAPSHOT</version>
</dependency>
2.3.2 编写config
@Configuration
public class GrpcClientConfiguration {@Value("${server-host}")private String host;/*** gRPC Server的端口*/@Value("${server-port}")private int port;@Beanpublic ManagedChannel managedChannel() {// 开启gRPC客户端ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();System.out.println("gRPC client started, server address: " + host + " , " + port);// 添加客户端关闭的逻辑Runtime.getRuntime().addShutdownHook(new Thread(() -> {try {// 调用shutdown方法后等待1秒关闭channelmanagedChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS);System.out.println("gRPC client shut down successfully.");} catch (InterruptedException e) {e.printStackTrace();}}));return managedChannel;}@Autowiredprivate ManagedChannel managedChannel;@Beanpublic UserGrpc.UserBlockingStub userBlockingStub(ManagedChannel channel) {// 通过channel获取到服务端的stubreturn UserGrpc.newBlockingStub(managedChannel);}
}
2.3.3 yaml
server:port: 8080
spring:application:name: spring-boot-jrpc-client# 本地测试
server-host: 127.0.0.1
server-port: 18081
2.3.4 测试验证
@RestController("/user")
public class UserController {@Autowiredprivate UserGrpc.UserBlockingStub userBlockingStub;@GetMapping("/sayHello")public String sayHello(String name, String addr, int age) {UserRequest request = UserRequest.newBuilder().setName(name).setAddr(addr).setAge(age).build();UserResponse response;try {response = userBlockingStub.sayHello(request);} catch (StatusRuntimeException e) {e.printStackTrace();return e.getMessage();}return response.toString();}
}
浏览器访问:http://localhost:8080/user/sayHello?name=test&addr=addr&age=99 返回:
name: "server:test" age: 99 addr: "server:addr" otherMsg { ext1: "ext1" ext2: "ext2" } otherMap { key: "test" value: "testmap" }
相关文章:
SpringBoot基于gRPC进行RPC调用
SpringBoot基于gRPC进行RPC调用 一、gRPC1.1 什么是gRPC?1.2 如何编写proto1.3 数据类型及对应关系1.4 枚举1.5 数组1.6 map类型1.7 嵌套对象 二、SpringBoot gRPC2.1 工程目录2.2 jrpc-api2.2.1 引入gRPC依赖2.2.2 编写 .proto 文件2.2.3 使用插件机制生产proto相关…...
浏览器的事件循环机制(Event loop)
事件循环 浏览器的进程模型 何为进程? 程序运行需要有它自己专属的内存空间,可以把这块内存空间简单的理解为进程 每个应用至少有一个进程,进程之间相互独立,即使要通信,也需要双方同意。 何为线程? …...
THEMIS---Beta Sprint Summary Essay Blog
Which course does this assignment belong to2301-MUSE社区-CSDN社区云What are the requirements for this assignmentbeta SprintThe goal of this assignmentTo summarize the beta task progress and the teams sprintsTeam NameThemisTop-of-the-line collection of essa…...
Vue中实现分布式动态路由的基本实现步骤介绍
设想一下,我们在做一个体量非常大的项目,这个项目有很多的模块和相当多的页面。当我们想修改一个路由的时候,我们打开了router文件夹下的index.js文件时,一串长到鼠标滚轮需要滚大半天才滚到底的路由简直让人头皮发麻。 在开始之前…...
【Leetcode】计算器
思路 用栈来完成; 考虑到运算关系,先乘除后加减;此外,一般计算式首个数字式正数;判断字符是否为数字,str.isdigit()字符转数字:ord(str) - ord(‘0’)遇到加减符,压栈数字…...
巧妙的使用WPF中的资源
其实,在wpf中,最核心的就是xaml,因为只有xaml,才能体现出用的是wpf,而不是普通的cs文件,cs文件在winform中等等程序都可以使用的,唯独xaml才是wpf中最重要的,最精华的东西࿰…...
多维时序 | MATLAB实现RIME-CNN-BiLSTM-Multihead-Attention多头注意力机制多变量时间序列预测
多维时序 | MATLAB实现RIME-CNN-BiLSTM-Multihead-Attention多头注意力机制多变量时间序列预测 目录 多维时序 | MATLAB实现RIME-CNN-BiLSTM-Multihead-Attention多头注意力机制多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现RIME-…...
【AIGC重塑教育】AI大模型驱动的教育变革与实践
文章目录 🍔现状🛸解决方法✨为什么要使用ai🎆彩蛋 🍔现状 AI正迅猛地改变着我们的生活。根据高盛发布的一份报告,AI有可能取代3亿个全职工作岗位,影响全球18%的工作岗位。在欧美,或许四分之一…...
【力扣100】2.两数相加
添加链接描述 # Definition for singly-linked list. # class ListNode: # def __init__(self, val0, nextNone): # self.val val # self.next next class Solution:def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Op…...
算法leetcode|93. 复原 IP 地址(多语言实现)
文章目录 93. 复原 IP 地址:样例 1:样例 2:样例 3:提示: 分析:题解:rust:go:c:python:java: 93. 复原 IP 地址: 有效 IP …...
TOGAF—架构(Architecture)项目管理
一、简介 1.1概述 架构(Architecture)项目在本质上通常是复杂的。他们需要适当的项目管理来保持正轨并兑现承诺。本指南适用于负责规划和管理架构(Architecture)项目的人员。我们解释了如何用事实上的方法和标准(如PRINCE2或PMBOK)来补充TOGAF架构开发方法(ADM),以加…...
MVVM前端设计模式的发展与应用
在MVC模式中,随着代码量越来越大,主要用来处理各种逻辑和数据转化的Controller首当其冲,变得非常庞大,MVC的简写变成了Massive-View-Controller(意为沉重的Controller) 我曾经接手老项目,sprin…...
redis:二、缓存击穿的定义、解决方案(互斥锁、逻辑过期)的优缺点和适用场景、面试回答模板和缓存雪崩
缓存击穿的定义 缓存击穿是一种现象,具体就是某一个数据过期时,恰好有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮。典型场景就是双十一等抢购活动中,首页广告页面的数据过期,此时刚好大量用户进行请求&…...
php的Url 安全的base64编码解码类
/*** Url安全的Base64编码方法* author JerryLi* version 20231217*/ final class UrlSafeB64Fun{/*** 编码* param string $sData 原始字符串* return string*/static public function encode(string $sData): string{$aTmp base64_encode($sData);return strtr($aTmp, [>…...
安全CDN有什么作用,安全CDN工作原理是什么?
一、CDN的应用场景 CDN技术可以应用于各种类型的网站和应用程序,特别是对于以下几种场景,CDN的作用尤为明显: 1. 高流量网站:对于流量较大的网站,CDN可以将网站的内容分发到全球各地的节点上,从而分担服务…...
Mysql高可用|索引|事务 | 调优
前言 「作者主页」:雪碧有白泡泡 「个人网站」:雪碧的个人网站 文章目录 前言sql语句的执行顺序关键词连接名字解释sql语句 面试坑点存储引擎MYSQL存储引擎 SQL优化索引索引失效索引的数据结构面试坑点 锁事务四大特性事务的隔离级别MVCC 读写分离面试坑…...
电机驱动开发
最近在搞电机驱动程序,感觉很简单,实际操作却发现里面还有很多猫腻(细节)。 电机在嵌入式设备中非常常见,例如云台的转动,都是靠电机来驱动的。 电机常见分步进电机、直流电机,相对来说步进电机…...
基于PaddleNLP的深度学习对文本自动添加标点符号(一)
前言 目前以深度学习对文本自动添加标点符号研究很少,已知的开源项目并不多,详细的介绍就更少了,但对文本自动添加标点符号又在古文识别语音识别上有重大应用。 基于此,本文开始讲解基于PaddleNLP的深度学习对文本自动添加标点符号…...
“Java已死、前端已凉”?尊嘟假嘟?
一、为什么会出现“Java已死、前端已凉”的言论 “Java已死、前端已凉”的言论出现,主要是由于以下几个原因: 技术更新迅速:随着互联网技术的发展,新的编程语言和技术不断涌现。Java和前端技术作为广泛应用的技术,面临…...
双向无线功率传输系统MATLAB仿真
微❤关注“电气仔推送”获得资料(专享优惠) 模型简介: 初级侧转换器通过双向 AC/DC 转换器从电网获取电力,并由直流线电压 Vin 供电,而拾波侧被视为连接到 EV,并由连接到任一存储的单独直流源 Vout 表示或…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
