什么是gRPC
gRPC是一个超高性能的RPC框架,对市面上主流语言都提供了支持。
- gRPC可通过protobuf来定义接口,可以对接口的约束更加严格
- 使用protobuf序列化,大幅减小传输数据量,从而对功耗,带宽,性能都有显著提升
- 基于http2标准设计,支持双向流通讯
- 支持异步请求,异步返回
使用场景
- 需要对接口有更加严格的管控,如对公司外部提供接口,我们不希望客户端随意传递数据,这时我们就可以使用gRPC来对接口约束。
- 对传输性能有更高的要求,如果我们传输的消息体过大,或调度过于频繁不希望影响服务器的性能时,也可以考虑使用,使用gRPC时我们的消息体比JSON或者文本传输数据量要小的多。
proto语法
.proto Type | Notes | C++ Type | Java Type | Python Type[2] | Go Type | Ruby Type | C# Type | PHP Type |
double | double | double | float | float64 | Float | double | float | |
float | float | float | float | float32 | Float | float | float | |
int32 | 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
uint32 | 使用变长编码 | uint32 | int | int/long | uint32 | Fixnum 或者 Bignum(根据需要) | uint | integer |
uint64 | 使用变长编码 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
sint32 | 使用变长编码,这些编码在负值时比int32高效的多 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
sint64 | 使用变长编码,有符号的整型值。编码时比通常的int64高效。 | int64 | long | int/long | int64 | Bignum | long | integer/string |
fixed32 | 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 | uint32 | int | int | uint32 | Fixnum 或者 Bignum(根据需要) | uint | integer |
fixed64 | 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
sfixed32 | 总是4个字节 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
sfixed64 | 总是8个字节 | int64 | long | int/long | int64 | Bignum | long | integer/string |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | |
string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 | string | String | str/unicode | string | String (UTF-8) | string | string |
bytes | 可能包含任意顺序的字节数据。 | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string |
当我们需要表示一个集合时使用repeated
关键字
message GetByUidParams{
repeated string uid = 1;
}
表示kv存储结构可使用map
关键字
map<key_type,value_type> field_name = num;
message UserInfo{
string uid = 1;
string avatar = 2;
string phone = 3;
string nickName = 4;
uint64 authTime = 5;
uint64 loginTime = 6;
uint64 recommendId = 7;
string realName = 8;
string email = 9;
}
message UserInfoMap {
map<string,UserInfo> infos = 1;
map<string,string> infomap = 2;
}
proto option选项
name | type | desc |
java_multiple_files | bool | 为True每一个message文件都会有一个单独的class文件;否则,message全部定义在outerclass文件里 |
java_package | str | 该字段是option的,用于标识生成的java文件的package。如果没有指定,则使用proto里定义的package,如果package也没有指定,那就会生成在根目录下; |
java_outer_classname | str | 该字段是option的,用于指定proto文件生成的java类的outerclass类名。什么是outerclass?简单来说就是用一个class文件来定义所有的message对应的java类。这个class就是outerclass;如果没有指定,默认是proto文件的驼峰式 |
异常处理
当我们调用onError
方法时表示服务端异常返回,此时不需要再调用onCompleted
方法
responseObserver.onError(Status.NOT_FOUND.augmentDescription("user not fund").asException());
我们在client端只需要捕获StatusRuntimeException
异常即可,该类继承RuntimeException
该异常类包含的Status
和message
都可以帮助我们做业务操作
整合Spring cloud
+gRPC
+Nacos
我们使用SpringCloud作为项目载体帮我们管理Bean,使用gRPC进行服务间通讯,使用Nacos做注册中心来实现gRPC的负载
注:Nacos2.0支持的gRPC
Nacos2.0版本相比1.X新增了gRPC的通信方式,因此需要增加2个端口。新增端口是在配置的主端口(server.port)基础上,进行一定偏移量自动生成。
端口 | 与主端口的偏移量 | 描述 |
9848 | 1000 | 客户端gRPC请求服务端端口,用于客户端向服务端发起连接和请求 |
9849 | 1001 | 服务端gRPC请求服务端端口,用于服务间同步等 |
准备工作
首先准备一个SpringCloud项目的基础脚手架
引入grpc相关依赖,用于生成java端代码
<properties>
<!-- 依赖相关配置 -->
<io.grpc.version>1.30.0</io.grpc.version>
<!-- 插件相关配置 -->
<os-maven-plugin.version>1.6.2</os-maven-plugin.version>
<protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version>
</properties>
<dependencies>
<!-- 引入 gRPC Protobuf 依赖,因为使用它作为序列化库 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${io.grpc.version}</version>
</dependency>
<!-- 引入 gRPC Stub 依赖,因为使用它作为 gRPC 客户端库 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${io.grpc.version}</version>
</dependency>
</dependencies>
<build>
<extensions>
<!-- os-maven-plugin 插件,从 OS 系统中获取参数 -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${os-maven-plugin.version}</version>
</extension>
</extensions>
<plugins>
<!-- protobuf-maven-plugin 插件,通过 protobuf 文件,生成 Service 和 Message 类 -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-maven-plugin.version}</version>
<configuration>
<pluginId>grpc-java</pluginId>
<protocArtifact>com.google.protobuf:protoc:3.9.1:exe:${os.detected.classifier}</protocArtifact>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${io.grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
新建目录/src/main/proto
攥写proto文件
UserService.proto
syntax = "proto3";
option java_multiple_files = true;
package com.test.grpc.api;
message UserGetRequest {
int32 id = 1;
}
message UserGetResponse {
int32 id = 1;
string name = 2;
int32 gender = 3;
}
message UserCreateRequest {
string name = 1;
int32 gender = 2;
}
message UserCreateResponse {
int32 id = 1;
}
service UserService {
rpc get(UserGetRequest) returns (UserGetResponse);
rpc create(UserCreateRequest) returns (UserCreateResponse);
rpc getIter(UserGetRequest) returns (stream UserGetResponse); # 流返回
rpc callAStream(stream UserGetRequest) returns (stream UserGetResponse); # 双向流通讯
}
生成java端代码
mvn clean compile
生成的代码可在target目录下看到
服务端
引入服务端依赖
<!-- 引入 gRPC Server Starter 依赖,实现对 gRPC 的自动配置 -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.12.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--必备: 注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--如没有使用sentinel则需要引入hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
配置文件
grpc:
server:
port: 3333
server:
port: 2223
spring:
application:
name: test-server
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
enabled: true
实现UserService
@GrpcService
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
@Override
public void get(UserGetRequest request, StreamObserver<UserGetResponse> responseObserver) {
responseObserver.onNext(UserGetResponse.newBuilder().setName("aa").setGender(1).build());
responseObserver.onCompleted();
}
@Override
public void create(UserCreateRequest request, StreamObserver<UserCreateResponse> responseObserver) {
responseObserver.onNext(UserCreateResponse.newBuilder().setId(1).build());
responseObserver.onCompleted();
}
@SneakyThrows
@Override
public void getIter(UserGetRequest request, StreamObserver<UserGetResponse> responseObserver) {
responseObserver.onNext(UserGetResponse.newBuilder().setId(1).build());
TimeUnit.SECONDS.sleep(1);
responseObserver.onNext(UserGetResponse.newBuilder().setId(2).build());
TimeUnit.SECONDS.sleep(3);
responseObserver.onNext(UserGetResponse.newBuilder().setId(3).build());
responseObserver.onCompleted();
}
@Override
public StreamObserver<UserGetRequest> callAStream(StreamObserver<UserGetResponse> responseObserver) {
return new StreamObserver<UserGetRequest>() {
@Override
public void onNext(UserGetRequest value) {
responseObserver.onNext(UserGetResponse.newBuilder().setId(value.getId()).build());
System.out.println(value.toString());
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
}
至此,一个基于gRPC的springCloud服务就完成了
启动服务,在nacos服务详情中就可以看到该服务的注册信息
客户端
引入客户端依赖
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.12.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--必备: 注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
配置文件
server:
port: 2222
grpc:
client:
test-server: # 服务名称
negotiation-type: plaintext
enableKeepAlive: true
keepAliveWithoutCalls: true
spring:
application:
name: test-client
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace:
调用gRPC服务
@RestController
@Slf4j
public class TestController {
// 阻塞gRPC
@GrpcClient("test-server")
private UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub;
// 非阻塞gRPC 获得Future对象
@GrpcClient("test-server")
private UserServiceGrpc.UserServiceFutureStub userServiceFutureStub;
// 双向流通讯
@GrpcClient("test-server")
private UserServiceGrpc.UserServiceStub userServiceStub;
@GetMapping("test1")
public String test1() {
// 构建请求数据
UserGetRequest request = UserGetRequest.newBuilder().setId(1).build();
// 执行gRPC请求
UserGetResponse userGetResponse = userServiceBlockingStub.get(request);
return userGetResponse.toString();
}
@SneakyThrows
@GetMapping("test2")
public String test2() {
// 构建请求数据
UserGetRequest request = UserGetRequest.newBuilder().setId(1).build();
// 执行gRPC请求
ListenableFuture<UserGetResponse> future = userServiceFutureStub.get(request);
return future.get().toString();
}
@GetMapping("test3")
public String test3() {
// 构建请求数据
UserGetRequest request = UserGetRequest.newBuilder().setId(1).build();
// 执行gRPC请求
Iterator<UserGetResponse> iter = userServiceBlockingStub.getIter(request);
// 阻塞等待
while (iter.hasNext()) {
System.out.println(iter.next().toString());
}
return "1";
}
private StreamObserver<UserGetRequest> requestObserver = null;
@GetMapping("test4")
public String test4() {
// 创建连接
ClientCallStreamObserver<UserGetResponse> responseObserver = new ClientCallStreamObserver<UserGetResponse>() {
@Override
public void onNext(UserGetResponse value) {
log.info("on next:{}", value.toString());
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onCompleted() {
requestObserver.onCompleted();
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setOnReadyHandler(Runnable onReadyHandler) {
}
@Override
public void disableAutoInboundFlowControl() {
}
@Override
public void request(int count) {
}
@Override
public void setMessageCompression(boolean enable) {
}
@Override
public void cancel(@Nullable String message, @Nullable Throwable cause) {
log.info("cancel:{}", message);
}
};
this.requestObserver = userServiceStub.callAStream(responseObserver);
return "1";
}
@GetMapping("test5")
public String test5() {
// 发送消息
requestObserver.onNext(UserGetRequest.newBuilder().setId(1).build());
return "1";
}
}
启动客户端测试5个接口
至此我们就学会了如何整合Spring cloud
+gRPC
+Nacos