Spring + Grpc + NacOS 集成

使用Grpc之前需要了解的

Grpc

Grpc与其他普通Rpc相同,都是屏蔽掉远程调用之间的细节,使得远程调用与本地调用一般,HTTP/2 是Grpc的默认使用协议。

HTTP/2与HTTP/1.x

HTTP/1.x是一个文本传输协议,可读性非常好。HTTP/2是一个二进制协议,所有的数据传输并不易读。

image-20250617105900329

Wireshark可以帮我们解析

下面这个是OpenFeign,基于HTTP/1.1

image-20250617110836914

在这里插入图片描述

具体HTTP/1.x和HTTP/2的优缺点可以移步至

Protocol Buffers

ProtoBuf 是google团队开发的用于高效存储和读取结构化数据的工具,你可以理解为另一种格式的JSON,正是因为如此,Java中普通的JSON序列化和反序列化工具对其不起作用,需要使用到

<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>

来针对JSON和ProtoBuf相互转换

proto文件

类似的,你会看到和下面文件相似的

syntax = "proto3";

option java_multiple_files = true;
option java_package = "cn.bwte.grpc";
option java_outer_classname = "TestProto";

// The greeting service definition.
service Test {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
}

}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

可以理解为,定义了proto的协议版本,需要生成的javaClass包名和类名,还有定义的方法调用以及实体。

那我们怎么才能让这玩意生成Java实体呢

 <plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-plugin.version}</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<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>

Maven是个好东西,添加了这个之后,在每次的install都会根据proto生成你定义的实体

理论存在 实践开始

准备阶段

技术选型

目前Spring官方也出了对于Grpc的调用组建,合并在全家桶中,但是对于Java和Spring的要求比较高,需要 Spring Boot 3.4.x and 3.5.x,感兴趣的同学可以Getting Started

本文采用的是grpc-spring,一个第三方Grpc的实现

Grpc接口定义

<properties>
<protobuf.version>3.23.4</protobuf.version>
<protobuf-plugin.version>0.6.1</protobuf-plugin.version>
<grpc.version>1.58.0</grpc.version>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependencies>
插件引入上文说的那个Maven插件,需要用来生成

定义proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "cn.ashes.grpc";
option java_outer_classname = "CustomerProto";

service Customer {
rpc queryCustomerInfo (CustomerRequest) returns (CustomerReplyList) {
}
}
message CustomerReplyList {
repeated CustomerReply replies = 1;
}

message CustomerReply {
int64 id = 1;
string customerCode = 2;
string customerName = 3;
// ...
}


message CustomerRequest {
string customerName = 1;
int32 pageNo = 2;
int32 pageSize = 3;
}

在进行install之后,就会生成对应的GrpcService与实体

服务端实现

实现Service

// 定义为Grpc的Service
@GrpcService
public class CustomerService extends CustomerGrpc.CustomerImplBase {
// CustomerGrpc.CustomerImplBase为生成的需要实现的Service
@Resource
private CustomerController customerController;

@Override
public void queryCustomerInfo(CustomerRequest request, StreamObserver<CustomerReplyList> responseObserver) {
String json;
try {
// 将proto实体转为pojo
json = JsonFormat.printer().print(request);
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
CustomerInfoQuery bean = JSONUtil.toBean(json, CustomerInfoQuery.class);
CustomerInfo customerInfoJsonResult = customerController.queryCustomerInfo(bean);
CustomerReplyList.Builder listBuilder = CustomerReplyList.newBuilder();

customerInfoJsonResult.getData().forEach(customerInfo -> {
String json1 = JSONUtil.toJsonStr(customerInfo);
// 创建一个空的 CustomerReply 构建器
CustomerReply.Builder builder = CustomerReply.newBuilder();

// 使用 JsonFormat 将 JSON 字符串解析为 CustomerReply
try {
JsonFormat.parser().ignoringUnknownFields().merge(json1, builder);
listBuilder.addReplies(builder.build());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
});
responseObserver.onNext(listBuilder.build());
responseObserver.onCompleted();
}
}

服务端事例为调用查询客户信息

grpc:
server:
port: 9099 # 指定我需要的Grpc端口

简单的测试下叭

grpcurl --plaintext localhost:9099 list Customer

grpcurl --plaintext -d '{"customerName": "喵喵"}' localhost:9099 Customer.queryCustomerInfo

你说你没有grpcurl?指路grpcurl

你要是有HomeBrew就更爽了直接brew install grpcurl

理想情况你就会得到正确的反差了

客户端实现

但是我想用其他微服务来调用怎么办捏

注册服务到注册中心会把?那我不教了
下一步就是写客户端

依赖注入

@GrpcClient("Grpc的Service在注册中心的名字")
private CustomerGrpc.CustomerBlockingStub customerBlockingStub;

这里我们用Block的实现来做事例,Grpc给我们三种实现NewBlockingStub 、 newStub 、 newFutureStub

分别代表阻塞调用,纯异步调用客户端,异步带Future调用

CustomerRequest build = CustomerRequest.newBuilder()
.setCustomerName("喵喵")
.setPageNo(1)
.setPageSize(100).build();
CustomerReplyList customerReplyList = customerBlockingStub.queryCustomerInfo(build);

直接调用就可以了

延展阅读

  1. Grpc
  2. HTTP/2对比HTTP/1.1,特性是什么?是如何解决队头阻塞与压缩头部的?
  3. 详解HTTP协议版本(HTTP/1.0、1.1、2.0、3.0区别)
  4. Protocol Buffer 基础知识:Java
  5. grpc-spring
  6. Getting Started
  7. grpcurl
  8. gRPC三种客户端类型实践【Java版】