What is gRPC
gRPC is an ultra-high-performance RPC framework that supports all mainstream languages on the market.
-
gRPC can define the interface through protobuf, and the constraints on the interface can be stricter
-
Use protobuf serialization to greatly reduce the amount of transmitted data, thereby significantly improving power consumption, bandwidth, and performance
-
Based on http2 standard design, support two-way stream communication
-
Support asynchronous request, asynchronous return
scenes to be used
- It is necessary to have stricter control over the interface. For example, to provide an interface to the outside of the company, we do not want the client to pass data at will. At this time, we can use gRPC to restrict the interface.
- There are higher requirements for transmission performance. If the message body we transmit is too large, or the scheduling is too frequent and we do not want to affect the performance of the server, we can also consider using it. When using gRPC, our message body is larger than JSON or text transmission data volume Much smaller.
proto syntax
.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 | Using variable length encoding is very inefficient for negative values. If your domain may have negative values, please use sint64 instead | int32 | int | int | int32 | Fixnum or Bignum (as needed) | int | integer |
uint32 | Use variable length encoding | uint32 | int | int/long | uint32 | Fixnum or Bignum (as needed) | uint | integer |
uint64 | Use variable length encoding | uint64 | long | int/long | uint64 | Bignum | head | integer/string |
sint32 | Use variable-length encodings, which are much more efficient than int32 for negative values | int32 | int | int | int32 | Fixnum or Bignum (as needed) | int | integer |
sint64 | Signed integer value using variable length encoding. More efficient than usual int64 when encoding. | int64 | long | int/long | int64 | Bignum | long | integer/string |
fixed32 | Always 4 bytes, this type is more efficient than uint32 if the value is always greater than 228. | uint32 | int | int | uint32 | Fixnum or Bignum (as needed) | uint | integer |
fixed64 | Always 8 bytes, this type is more efficient than uint64 if the value is always greater than 256. | uint64 | long | int/long | uint64 | Bignum | head | integer/string |
sfixed32 | always 4 bytes | int32 | int | int | int32 | Fixnum or Bignum (as needed) | int | integer |
sfixed64 | always 8 bytes | int64 | long | int/long | int64 | Bignum | long | integer/string |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | |
string | A string must be UTF-8 encoded or 7-bit ASCII encoded text. | string | String | str/unicode | string | String (UTF-8) | string | string |
bytes | May contain byte data in any order. | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string |
repeated
The keyword is used when we need to represent a collection
message GetByUidParams{
repeated string uid = 1;
}
Indicates that the kv storage structure can use map
keywords
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 option
name | type | desc |
---|---|---|
java_multiple_files | bool | For True, each message file will have a separate class file; otherwise, all messages are defined in the outerclass file |
java_package | str | This field is optional and is used to identify the package of the generated java file. If not specified, the package defined in proto will be used. If the package is not specified, it will be generated in the root directory; |
java_outer_classname | str | This field is optional and is used to specify the outerclass class name of the java class generated by the proto file. What is an outer class? Simply put, a class file is used to define the java classes corresponding to all messages. This class is outerclass; if not specified, the default is the camel case of the proto file |
exception handling
When we call onError
the method, it means that the server returns abnormally. At this time, 不需要再调用onCompleted
the method
responseObserver.onError(Status.NOT_FOUND.augmentDescription("user not fund").asException());
We only need to catch StatusRuntimeException
exceptions on the client side, this class inheritsRuntimeException
The exception class contains Status
and message
can help us do business operations
integration Spring cloud
++ gRPC
_Nacos
We use SpringCloud as the project carrier to help us manage beans, use gRPC for communication between services, and use Nacos as the registration center to realize the load of gRPC
Note: gRPC supported by Nacos2.0
Compared with Nacos 1.X, Nacos 2.0 has added the gRPC communication method, so 2 ports need to be added. The newly added port is automatically generated with a certain offset on the basis of the configured main port (server.port).
port | Offset from master port | describe |
---|---|---|
9848 | 1000 | The client gRPC requests the server port, which is used by the client to initiate a connection and request to the server |
9849 | 1001 | The server gRPC requests the server port, which is used for synchronization between services, etc. |
Preparation
First prepare the basic scaffolding of a SpringCloud project
Introduce grpc-related dependencies for generating java-side code
<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>
Create a new directory /src/main/proto
and write proto files
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); # 双向流通讯
}
Generate java code
mvn clean compile
The generated code can be seen in the target directory
Server
Introduce server-side dependencies
<!-- 引入 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>
configuration file
grpc:
server:
port: 3333
server:
port: 2223
spring:
application:
name: test-server
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
enabled: true
Implement 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();
}
};
}
}
At this point, a gRPC-based springCloud service is completed
Start the service, and you can see the registration information of the service in the nacos service details
client
Introduce client dependencies
<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>
configuration file
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:
Call gRPC service
@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";
}
}
Start the client to test 5 interfaces
So far we have learned how to integrate Spring cloud
++gRPC
Nacos