Grpc的原理
一个RPC框架必须有两个基础的组成部分:数据的序列化和进程数据通信的交互方式。对于序列化gRPC采用了自家公司开源的Protobuf。什么是Protobuf?
Google Protocol Buffer(简称 Protobuf) 是一种与语言无关,平台无关的可扩展机制,用于序列化结构化数据。
使用Protocol Buffers 可以一次定义结构化的数据,然后可以使用特殊生成的源代码轻松地在各种数据流中使用各种语言编写和读取结构化数据。
而关于进程间的通讯,无疑是Socket。Java方面gRPC同样使用了成熟的开源框架Netty。使用Netty Channel作为数据通道。传输协议使用了HTTP2。
通过以上的分析,我们可以将一个完整的gRPC流程总结为以下几步:
通过.proto文件定义传输的接口和消息体。
通过protocol编译器生成server端和client端的stub程序。
将请求封装成HTTP2的Stream。
通过Channel作为数据通信通道使用Socket进行数据传输。
使用Java 结合 Grpc 实现远程服务调用
下面创建一个SpringBoot 多模块项目 与 Grpc 结合实现远程服务调用
项目结构:
文章图片
SpringBoot-Grpc-Lib
定义了两个.proto文件用于测试,并配置Gradle 插件,以便快速生成Java相关的实体类。
user.proto
syntax = "proto3";
option java_package = "com.dashuai.learning.grpc.lib.proto";
service User {
rpc SaveUser (UserData) returns (UserResponse) {
}
rpc GetUser (UserRequest) returns (UserData) {
}}
message UserData {
int32 id = 1;
string name = 2;
string sex = 3;
int32 age = 4;
string remark = 5;
}
message UserRequest {
int32 id = 1;
}
message UserResponse {
bool isSuccess = 1;
}
上述声明了两个RPC服务,分别是SaveUser()和GetUser(),顾名思义,保存一个用户和获取一个用户信息。
该模块项目中,我配置了protobuf-gradle-plugin插件方便构建生成对应的Java类,build.gradle如下:
apply plugin: 'com.google.protobuf'
buildscript {
repositories {
mavenLocal()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.1'
}
}protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.5.1"
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.16.1'
}
}
//配置生成输出目录
generatedFilesBaseDir = "$projectDir/src"
generateProtoTasks {
all()*.plugins {
grpc { outputSubDir = "java" }
}
}
}
sourceSets {
main {
proto {
// In addition to the default 'src/main/proto'
srcDir 'src/main/protobuf'
srcDir 'src/main/protocolbuffers'
include '**/*.protodevel'
}
java {
}
}
test {
proto {
// In addition to the default 'src/test/proto'
srcDir 'src/test/protocolbuffers'
}
}
}
配置无误后,点击右侧Tasks的GenerateProto生成对应的Java类
文章图片
上述配置的.proto对应生成的Java文件如下图:
文章图片
到此,服务的定义就完成了,下面分别对定义的服务进行实现。
SpringBoot-Grpc-Server
对应的服务端,实现服务所做业务,业务实现代码如下,使用一个全局Map存放用户信息,模拟数据库保存。
@GrpcService(UserOuterClass.class)
public class UserService extends UserGrpc.UserImplBase {private final static Logger log = LoggerFactory.getLogger(UserService.class);
private Map map = new HashMap<>();
@Override
public void saveUser(UserOuterClass.UserData request, StreamObserver responseObserver) {
try {
map.put(request.getId(), request);
} catch (Exception e) {
log.error("保存失败了!msg:{}", e.getMessage());
}
UserOuterClass.UserResponse.Builder builder = UserOuterClass.UserResponse.newBuilder().setIsSuccess(true);
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}@Override
public void getUser(UserOuterClass.UserRequest request, StreamObserver responseObserver) {
responseObserver.onNext(map.get(request.getId()));
responseObserver.onCompleted();
}
}
SpringBoot-Grpc-Client
对应的客户端,调用对应的服务
@Service
@Slf4j
public class UserClientService {
@GrpcClient("local-grpc-server")
private Channel serverChannel;
public boolean saveUser(UserOuterClass.UserData userData) {
UserGrpc.UserBlockingStub userBlockingStub = UserGrpc.newBlockingStub(serverChannel);
System.out.println(JSONParseUtils.object2JsonString(userData));
UserOuterClass.UserResponse response = userBlockingStub.saveUser(userData);
return response.getIsSuccess();
}public UserOuterClass.UserData getUserData(int id) {
UserGrpc.UserBlockingStub userBlockingStub = UserGrpc.newBlockingStub(serverChannel);
UserOuterClass.UserData userData = https://www.it610.com/article/userBlockingStub.getUser(UserOuterClass.UserRequest.newBuilder().setId(id).build());
System.out.println(JSONParseUtils.object2JsonString(userData));
return userData;
}
}
使用两个API调用测试,将请求体转换为JSON格式,在使用JsonFormat转换成对应的对象进行服务的调用,
这里我只是为了测试而这样做,实际使用可能有所区别。
@PostMapping(value = "https://www.it610.com/user", produces = "application/json;
charset=UTF-8")
public boolean saveUser(HttpServletRequest request) throws IOException {
String json = new String(IoUtils.toByteArray(request.getInputStream()), request.getCharacterEncoding());
return userClientService.saveUser(ProtobufUtils.jsonToPf(json, UserOuterClass.UserData.newBuilder()));
}@GetMapping(value = "https://www.it610.com/user/{id}")
@ApiOperation(value = "https://www.it610.com/article/获取User实例", notes = "获取用户信息", response = UserOuterClass.UserData.class)
@ApiImplicitParam(name = "id", value = "https://www.it610.com/article/用户id", required = true, dataType = "Integer")
public ApiResult getUser(@PathVariable Integer id) throws InvalidProtocolBufferException {
UserOuterClass.UserData userData = https://www.it610.com/article/userClientService.getUserData(id);
return ApiResult.prepare().success(ProtobufUtils.pfToJson(userData));
}
调用测试,录入一个用户信息:
文章图片
查询用户信息:
文章图片
到此,调用流程演示完毕!
测试项目的源码已上传到Github:
https://github.com/liaozihong/SpringBoot-Learning/tree/master/SpringBoot-Grpc
【微服务|Java 使用 Grpc 快速入门】参考链接:
https://www.jianshu.com/p/30ef9b3780d9
Protocol Buffers 3 简明教程
https://juejin.im/entry/59bb30f76fb9a00a616f1b73
推荐阅读
- 微服务|微服务系列:服务发现与注册-----Eureka(面试突击!你想了解的Eureka都在这里.持续更新中......)
- 每日一书|每日一书丨学习微服务最好的方式(阅读《微服务架构设计模式》)
- 第五节:SpringBoot常用注解介绍
- 第四节:SpringBoot中web模版数据渲染展示
- SpringBoot2022【草稿】
- 聊聊springboot项目全局异常处理那些事儿
- 第一节:创建SpringBoot项目并运行HelloWorld
- springboot管理系统[基于员工角色和文件权限的分级的后台管理系统源码]
- SpringBoot之@ComponentScan和@SpringBootApplication扫描覆盖问题
- mybatis|记mybatis查询null字段导致的NPE