文章目录
- RPC简介
- gPRC简介
- protobuf
- 1.文件编写规范
- 2.字段类型
- 3.定义服务(Services)
- 在Spring Boot中使用grpc
- 1.父工程pom配置
- 2.grpc-api模块
- 2.1.pom配置
- 2.2.proto文件编写
- 2.3.把proto文件编译成class文件
- 3.grpc-server模块
- 3.1.pom文件和application.yaml
- 3.2.实现grpc-api模块的接口
- 3.3.启动服务
- 4.grpc-client模块
- 4.1.pom文件和application.yaml
- 4.2.创建http接口调用grpc-server
- 4.3.启动服务测试
- 项目配套代码
RPC简介
RPC简介:
- RPC(Remote Procedure Call)是一种用于实现分布式系统中不同节点之间通信的协议。它允许一个节点(称为客户端)调用另一个节点(称为服务器)上的远程方法,就像调用本地方法一样。RPC的目标是隐藏底层通信细节,使得远程调用过程对开发者透明。
- 在RPC中,客户端通过发送请求消息给服务器来调用远程方法,服务器接收到请求后执行相应的方法,并将结果返回给客户端。RPC可以跨越不同的网络和操作系统,使得分布式系统中的不同节点能够进行高效的通信和协作。
- RPC的实现方式有多种,常见的包括基于HTTP协议的RESTful API、基于TCP/IP协议的Socket编程、以及基于消息队列的异步通信等。不同的实现方式有不同的特点和适用场景,开发者可以根据具体需求选择合适的RPC框架或协议。
gPRC简介
gprc官网: https://grpc.io/docs/
gRRC的g代表google,gRPC最初是由Google创建的,是一个现代的开源高性能远程过程调用(RPC)框架,可以在任何环境中运行。它可以高效地连接数据中心内和跨数据中心的服务,支持负载平衡、跟踪、运行状况检查和身份验证。它也适用于分布式计算的最后一英里,将设备、移动应用程序和浏览器连接到后端服务。
gRPC的特性
看官方文档的介绍,有以下几点特性:
- grpc可以跨语言使用。支持多种语言 支持C++、Java、Go、Python、Ruby、C#、Node.js、Android - Java、Objective-C、PHP等编程语言
- 基于 IDL ( 接口定义语言(Interface Define Language))文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
- 通信协议基于标准的 HTTP/2 设计,支持·双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC - 在移动端设备上更加省电和节省网络流量;
- 序列化支持 PB(Protocol Buffer)和 JSON,PB - 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
性能
gRPC消息使用一种有效的二进制消息格式protobuf进行序列化。Protobuf在服务器和客户机上的序列化非常快。Protobuf序列化后的消息体积很小,能够有效负载,在移动应用程序等有限带宽场景中显得很重要。与采用文本格式的JSON相比,采用二进制格式的protobuf在速度上可以达到前者的5倍!Auth0网站所做的性能测试结果显示,protobuf和JSON的优势差异在Java、Python等环境中尤为明显。下图是Auth0在两个Spring Boot应用程序间所做的对比测试结果。
- gRPC是为HTTP/2而设计的,它是HTTP的一个主要版本,与HTTP 1.x相比具有显著的性能优势::
- 二进制框架和压缩。HTTP/2协议在发送和接收方面都很紧凑和高效。通过单个TCP连接复用多个HTTP/2调用。多路复用消除了线头阻塞。
代码生成
- gRPC框架都为代码生成提供了一流的支持。gRPC开发的核心文件是*.proto文件 ,它定义了gRPC服务和消息的约定。根据这个文件,gRPC框架将生成服务基类,消息和完整的客户端代码。
- 通过在服务器和客户端之间共享*.proto文件,可以从端到端生成消息和客户端代码。客户端的代码生成消除了客户端和服务器上的重复消息,并为您创建了一个强类型的客户端。无需编写客户端代码,可在具有许多服务的应用程序中节省大量开发时间。
严格的规范
- 不存在具有JSON的HTTP API的正式规范。开发人员不需要讨论URL,HTTP动词和响应代码的最佳格式。(想想,是用Post还是Get好?使用Get还是用Put好?一想到有选择恐惧症的你是不是又开了纠结,然后浪费了大量的时间)
- 该gRPC规范是规定有关gRPC服务必须遵循的格式。gRPC消除了争论并节省了开发人员的时间,因为gPRC在各个平台和实现之间是一致的。
流
- HTTP/2为长期的实时通信流提供了基础。gRPC通过HTTP/2为流媒体提供一流的支持。
- gRPC服务支持所有流组合:
-
- 一元(没有流媒体): 简单rpc 这就是一般的rpc调用,一个请求对象对应一个返回对象。客户端发起一次请求,服务端响应一个数据,即标准RPC通信。 (rpc Method(request) returns (response)){}
-
- 服务器流RPC是指客户端发一个对象,服务器返回一个Stream流式消息(rpc Method(request) returns (stream response)){}
-
- 客户端流RPC,客户端发一个流给服务器,服务器返回一个对象(rpc Method(stream request) returns ( response)){}
-
- 双向流媒体:双向流式rpc 结合客户端流式rpc和服务端流式rpc,可以传入多个对象,返回多个响应对象。应用场景:聊天应用。(rpc Method(stream request) returns (stream response)){}
gRPC非常适合以下场景:
- 微服务:gRPC设计为低延迟和高吞吐量通信。gRPC非常适用于效率至关重要的轻型微服务。
- 点对点实时通信: gRPC对双向流媒体提供出色的支持。gRPC服务可以实时推送消息而无需轮询。
- 多语言混合开发环境: gRPC工具支持所有流行的开发语言,使gRPC成为多语言开发环境的理想选择。
- 网络受限环境:使用Protobuf(一种轻量级消息格式)序列化gRPC消息。gRPC消息始终小于等效的JSON消息。
protobuf
官方文档: https://protobuf.dev/programming-guides/proto3/
- protobuf 即 Protocol Buffers,是一种轻便高效的结构化数据存储格式,与语言、平台无关,可扩展可序列化。
- Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。json、xml都是基于文本格式,protobuf 是以二进制方式存储的,占用空间小,但也带来了可读性差的缺点。
- protobuf 在通信协议和数据存储等领域应用广泛。例如著名的分布式缓存工具 Memcached 的 Go 语言版本groupcache 就使用了 protobuf 作为其 RPC 数据格式。
- Protobuf 在 .proto 定义需要处理的结构化数据,可以通过 protoc 工具,将 .proto 文件转换为C++、Golang、Java、Python 等多种语言的代码,兼容性好,易于使用。
1.文件编写规范
syntax = "proto3";
package main;
message User {
string name = 1;
bool enabled = 2;
repeated int32 roles = 3;
}
逐行解读user.proto
- protobuf 有2个版本,默认版本是 proto2,如果需要 proto3,则需要在非空非注释第一行使用 syntax = “proto3” 标明版本。
- package,即包名声明符是可选的,用来防止不同的消息类型有命名冲突。
- 消息类型 使用 message 关键字定义,User 是类型名,name, enabled, roles 是该类型的 3 个字段,类型分别为 string, bool 和 []int32。字段可以是标量类型,也可以是合成类型。
- 每个字段的修饰符默认是 singular,一般省略不写,repeated 表示字段可重复,即用来表示数组类型。
- 每个字符 =后面的数字称为标识符,每个字段都需要提供一个唯一的标识符。标识符用来在消息的二进制格式中识别各个字段,一旦使用就不能够再改变,标识符的取值范围为 [1, 2^29 - 1] 。
- .proto 文件可以写注释,单行注释 //,多行注释 /* … */
一个 .proto 文件中可以写多个消息类型,即对应多个结构体(struct)。
2.字段类型
基础字段类型
枚举(Enumerations)
举类型适用于提供一组预定义的值,选择其中一个。例如我们将用户状态定义为枚举类型。
enum Status {
ENABLED = 0;
DISABLED = 1;
}
message User {
string name = 1;
string password=2;
Status status = 3;
}
嵌入其它消息体
可以再message里面嵌套message,和java内嵌一个原理
// 返回结果
message LoginResultVo {
Result result = 1; // 状态信息
UserInfo data = 2; // 数据
}
message Result {
int32 code = 1;
string msg = 2;
}
3.定义服务(Services)
如果消息类型是用来远程通信的(Remote Procedure Call, RPC),可以在 .proto 文件中定义 RPC 服务接口。例如我们定义了一个名为 UserServiceApi 的 RPC 服务,提供了login接口,入参是 LoginInfoDTO 类型,返回类型是 LoginResultVo 类型
service UserServiceApi {
rpc login (LoginInfoDTO) returns (LoginResultVo);
}
// 请求参数
message LoginInfoDTO {
string username = 1;
string password=2;
}
在Spring Boot中使用grpc
创建一个聚合项目,分三个子模块
- grpc-api: 封装需要远程调用的API,和dubbo类似
- grpc-server: 远程调用的服务提供者
- grpc-client: 客户端调用者
1.父工程pom配置
<modules>
<module>grpc-api</module>
<module>grpc-server</module>
<module>grpc-client</module>
</modules>
<properties>
<java.version>1.8</java.version>
<!-- grpc依赖-->
<grpc.version>1.53.0</grpc.version>
<grpc.spring.boot.version>2.14.0.RELEASE</grpc.spring.boot.version>
<protoc.version>3.22.0</protoc.version>
<protobuf.java.version>3.21.7</protobuf.java.version>
<!--grpc编译插件-->
<protobuf.plugin.version>0.6.1</protobuf.plugin.version>
<os.maven.plugin.version>1.7.1</os.maven.plugin.version>
<!-- 自定义grpc api模块版本-->
<grpc-api.version>0.0.1-SNAPSHOT</grpc-api.version>
</properties>
<!--统一管理版本号-->
<dependencyManagement>
<dependencies>
<!-- gRpc 依赖 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- gRpc Protobuf -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<!-- gRpc Stub -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<!-- protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.java.version}</version>
</dependency>
<!-- gRpc Server -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>${grpc.spring.boot.version}</version>
</dependency>
<!-- gRpc Client -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>${grpc.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>com.ljm.boot.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>${grpc-api.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
2.grpc-api模块
2.1.pom配置
.proto文件编译成java文件需要引入已下三个依赖和两个插件
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
</dependencies>
<build>
<extensions>
<!-- OS 插件 -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${os.maven.plugin.version}</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf.plugin.version}</version>
<configuration>
<pluginId>grpc-java</pluginId>
<protoSourceRoot>src/main/proto</protoSourceRoot>
<protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
</protocArtifact>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2.2.proto文件编写
- 第一步: 在src.main目录下新建proto目录
- 第二步: 在commom包下面新建result.proto文件封装通用的响应信息
- 第三部: 在user包下面新建user.proto文件,封装用户相关的接口信息
result.proto文件内容如下
syntax = "proto3";
package api.common;
option java_package = "com.ljm.boot.grpc.api.common"; //包名
option java_outer_classname = "ResultProto"; //编译后的类名
option java_multiple_files = true; //生成多个文件
message Result {
bool ok = 1; // 是否成功
int32 code = 2; // 状态码
string message = 3; // 消息
}
user.proto文件如下
syntax = "proto3";
package api.login;
import "api/common/result.proto"; //引入common包下的result.proto模块, api/common路径对应result.proto里的 package api.common;
option java_package = "com.ljm.boot.grpc.api.user"; //包名
option java_outer_classname = "UserServiceProto"; //编译后的类名
option java_multiple_files = true; //生成多个文件
// 接口类
service UserServiceApi {
rpc login (LoginInfoDTO) returns (LoginResultVo);
}
// 请求参数
message LoginInfoDTO {
string username = 1;
string password=2;
}
// 返回结果
message LoginResultVo {
api.common.Result result = 1; // 状态信息
UserInfo data = 2; // 数据
}
message UserInfo {
string token = 1; // token信息
int32 id = 2; // 用户Id
}
2.3.把proto文件编译成class文件
在grpc-api目录下执行install命令
cd grpc-api
mvn clean install
看到如上信息就可以去target目录下找编译后的文件
和接口相关的三个类, xxxApiGrpc、 xxxApiGrpc.xxxImplBase、xxxApiStub
-
xxxApiGrpc: 对应上面编写user.proto文件中service UserServiceApi
-
xxxApiGrpc.xxxImplBase: 抽象类,需要grpc-server模块去具体业务类中继承这个类然后重写方法里写具体业务代码
-
xxxApiGrpc.xxxApiStub :这个类中的login方法就是grpc-client需要调用的接口
-
UserServiceApiGrpc文件就是对应接口编译后service类
-
在 UserServiceApi类下面有个静态类UserServiceApiStub,这个类下面的login方法就是grpc-client需要调用的接口
-
在 UserServiceApi类下面有个抽象类UserServiceApiImplBase,需要grpc-server去继承这个类然后重写方法里写具体业务代码
3.grpc-server模块
3.1.pom文件和application.yaml
pom文件
需要继承父工程
<parent>
<groupId>com.ljm.boot.grpc</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>com.ljm.boot.grpc</groupId>
<artifactId>grpc-api</artifactId>
</dependency>
<!-- gRpc Server -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
</dependency>
</dependencies>
application.yaml
默认端口是9090,可以通过下面配置修改
grpc:
server:
port: 9090 #默认9090
3.2.实现grpc-api模块的接口
- @GrpcService 注解表示这个接口是需要通过grpc调用
- .newBuilder()实例化对象
- onNext()设置返回结果
- onCompleted() 可以理解为return,表示这次调用结束
@GrpcService
public class UserServiceApi extends UserServiceApiGrpc.UserServiceApiImplBase {
@Override
public void login(LoginInfoDTO request, StreamObserver<LoginResultVo> responseObserver) {
LoginResultVo.Builder builder=LoginResultVo.newBuilder();
Result.Builder rBuilder=Result.newBuilder();
if (!"admin".equals(request.getUsername()) || !"123456".equals(request.getPassword())){
rBuilder.setOk(false);
rBuilder.setCode(101);
rBuilder.setMessage("用户名或密码错误!");
}else{
rBuilder.setOk(true);
rBuilder.setCode(200);
rBuilder.setMessage("登录成功!");
builder.setData(UserInfo.newBuilder()
.setToken(UUID.randomUUID().toString())
.setId(1)
.build());
}
builder.setResult(rBuilder);
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}
}
3.3.启动服务
启动类和普通的springboot项目并无区别
@SpringBootApplication
public class GrpcServerApplication {
public static void main(String[] args) {
SpringApplication.run(GrpcServerApplication.class, args);
}
}
运行程序后可以看到gRPC服务已启动,绑定的端口是9090
4.grpc-client模块
4.1.pom文件和application.yaml
pom文件
相比于server模块添加了web服务模块
<parent>
<groupId>com.ljm.boot.grpc</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>com.ljm.boot.grpc</groupId>
<artifactId>grpc-api</artifactId>
</dependency>
<!-- gRpc Client -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
application.yaml文件配置
server:
port: 8037
grpc:
client:
#@GrpcClient注解需要和下面的grpc-server一致
grpc-server:
#grpc服务端的调用地址
address: 'static://127.0.0.1:9090'
enable-keep-alive: true
keep-alive-without-calls: true
#传输类型设置为明文
negotiation-type: plaintext
4.2.创建http接口调用grpc-server
- @GrpcClient注解内的值需要和grpc.client.grpc-server保持一致
@RequestMapping
@RestController
public class UserController {
@GrpcClient("grpc-server")
private UserServiceApiGrpc.UserServiceApiBlockingStub serviceApiBlockingStub;
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
LoginInfoDTO loginInfoDTO = LoginInfoDTO.newBuilder()
.setUsername(username)
.setPassword(password).build();
LoginResultVo loginResultVo = serviceApiBlockingStub.login(loginInfoDTO);
String result;
if (loginResultVo.getResult().getOk()) {
result = String.format("登录成功,token=%s, userId=%d", loginResultVo.getData().getToken(), loginResultVo.getData().getId());
} else {
result = String.format("登录失败,code=%d,msg=%s", loginResultVo.getResult().getCode(), loginResultVo.getResult().getMessage());
}
return result;
}
}
4.3.启动服务测试
@SpringBootApplication
public class GrpcClientApllication {
public static void main(String[] args) {
SpringApplication.run(GrpcClientApllication.class, args);
}
}
通过接口测试工具调用 http://localhost:8037/login接口
设置账号密码和后台一致测试
设置密码错误再测试
由上可以看到结果和预期一致,使用gRPC分页查询数据的在gitee代码中提交了,篇幅有限就不一一概括了。
项目配套代码
gitee代码地址
创作不易,要是觉得我写的对你有点帮助的话,麻烦在gitee上帮我点下 Star
【SpringBoot框架篇】其它文章如下,后续会继续更新。
- 1.搭建第一个springboot项目
- 2.Thymeleaf模板引擎实战
- 3.优化代码,让代码更简洁高效
- 4.集成jta-atomikos实现分布式事务
- 5.分布式锁的实现方式
- 6.docker部署,并挂载配置文件到宿主机上面
- 7.项目发布到生产环境
- 8.搭建自己的spring-boot-starter
- 9.dubbo入门实战
- 10.API接口限流实战
- 11.Spring Data Jpa实战
- 12.使用druid的monitor工具查看sql执行性能
- 13.使用springboot admin对springboot应用进行监控
- 14.mybatis-plus实战
- 15.使用shiro对web应用进行权限认证
- 16.security整合jwt实现对前后端分离的项目进行权限认证
- 17.使用swagger2生成RESTful风格的接口文档
- 18.使用Netty加websocket实现在线聊天功能
- 19.使用spring-session加redis来实现session共享
- 20.自定义@Configuration配置类启用开关
- 21.对springboot框架编译后的jar文件瘦身
- 22.集成RocketMQ实现消息发布和订阅
- 23.集成smart-doc插件零侵入自动生成RESTful格式API文档
- 24.集成FastDFS实现文件的分布式存储
- 25.集成Minio实现文件的私有化对象存储
- 26.集成spring-boot-starter-validation对接口参数校验
- 27.集成mail实现邮件推送带网页样式的消息
- 28.使用JdbcTemplate操作数据库
- 29.Jpa+vue实现单模型的低代码平台
- 30.使用sharding-jdbc实现读写分离和分库分表
- 31.基于分布式锁或xxx-job实现分布式任务调度
- 32.基于注解+redis实现表单防重复提交
- 33.优雅集成i18n实现国际化信息返回
- 34.使用Spring Retry完成任务的重试
- 35.kafka环境搭建和收发消息
- 36.整合Tess4J搭建提供图片文字识别的Web服务
- 37.使用gRPC实现远程服务调用