赞
踩
In gRPC, a client application can directly call a method on a server application on a different machine as if it were a local object, making it easier for you to create distributed applications and services. As in many RPC systems, gRPC is based around the idea of defining a service, specifying the methods that can be called remotely with their parameters and return types. On the server side, the server implements this interface and runs a gRPC server to handle client calls. On the client side, the client has a stub (referred to as just a client in some languages) that provides the same methods as the server.
在 gRPC 中,客户端应用程序可以直接调用服务器应用程序上的方法 在另一台机器上,就好像它是本地对象一样,使你更容易 创建分布式应用程序和服务。与许多 RPC 系统一样,gRPC 是 基于定义服务的思想,指定可以 使用其参数和返回类型进行远程调用。在服务器端, server 实现此接口并运行 gRPC 服务器来处理客户端调用。 在客户端,客户端有一个存根(在某些客户端中称为客户端 languages),它提供与服务器相同的方法。
gRPC 客户端和服务器可以在各种 环境 - 从 Google 内部的服务器到您自己的桌面 - 并且可以 用 gRPC 支持的任何语言编写。因此,例如,您可以轻松地 在 Java 中创建一个 gRPC 服务器,客户端使用 Go、Python 或 Ruby。另外 最新的 Google API 将具有其接口的 gRPC 版本,让您 轻松将 Google 功能构建到您的应用程序中。
gRPC 使用 proto buffers 作为服务定义语言,编写 proto 文件,即可完成服务的定义。
syntax = "proto3"; option java_multiple_files = true; // 生成位置 option java_package = "com.lizq.jrpc.api"; option java_outer_classname = "UserService"; package user; service User { rpc SayHello (UserRequest) returns (UserResponse) {} } message UserRequest { string name = 1; int32 age = 2; string addr = 3; } message UserResponse { string name = 1; int32 age = 2; string addr = 3; OtherMsg otherMsg = 4; map<string, string> otherMap = 5; // 嵌套对象 message OtherMsg { string ext1 = 1; string ext2 = 2; } }
syntax = "proto3";
:指定使用的protobuf版本;option java_multiple_files = true;
:如果为 false,则只会.java为此文件生成一个.proto文件,以及所有 Java 类/枚举/等。为顶级消息、服务和枚举生成的将嵌套在外部类中。如果为 true,.java将为每个 Java 类/枚举/等生成单独的文件。为顶级消息、服务和枚举生成,并且为此.proto文件生成的包装 Java 类将不包含任何嵌套类/枚举/等。 如果不生成 Java 代码,则此选项无效。package user;
:定义本服务的包名,避免不同服务相同消息类型产生冲突;option java_package = "com.lizq.jrpc.api";
:生成java文件包名;option java_outer_classname = "UserService";
:生成java文件类名称。如果文件中没有明确 java_outer_classname指定,.proto则将通过将.proto文件名转换为驼峰式来构造类名(因此 foo_bar.proto变为FooBar.java)message UserResponse
:定义服务的接口名称;rpc SayHello (UserRequest) returns (UserResponse) {}
:远程调用方法名,参数及响应类型;message XXXXX{}
:定义数据类型;.proto类型 | Notes | C++ Type | Java/Kotlin | Python |
---|---|---|---|---|
double | double | double | float | |
float | float | float | float | |
int32 | 使用可变长度编码。对负数进行编码效率低下——如果您的字段可能有负值,请改用 sint32。 | int32 | int | int |
int64 | 使用可变长度编码。对负数进行编码效率低下——如果您的字段可能有负值,请改用 sint64。 | int64 | long | int/long |
uint32 | 使用可变长度编码。 | uint32 | int | int/long |
uint64 | 使用可变长度编码。 | uint64 | long | int/long |
sint32 | 使用可变长度编码。带符号的 int 值。这些比常规 int32 更有效地编码负数。 | int32 | int | int |
sint64 | 使用可变长度编码。带符号的 int 值。这些比常规 int64 更有效地编码负数。 | int64 | long | int/long |
fixed32 | 总是四个字节。如果值通常大于 228,则比 uint32 更有效 | uint32 | int | int/long |
fixed64 | 总是八个字节。如果值通常大于 256,则比 uint64 更有效。 | uint64 | long | int/long |
sfixed32 | 总是四个字节。 | int32 | int | int |
sfixed64 | 总是八个字节。 | int64 | long | int/long |
bool | bool | boolean | bool | |
string | 字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本,并且不能超过 232。 | string | String | str/unicode |
bytes | 可能包含不超过 2 32的任意字节序列。 | string | ByteString | str (Python 2)、bytes (Python 3) |
enum Sex {
NONE = 0;
MAN = 1;
WOMAN = 2;
}
message UserRequest {
string name = 1;
int32 age = 2;
string addr = 3;
Sex sex = 4;
}
**注意:**第一个枚举的值必须为0,因为0 是默认值,0 必须是第一个,保持和proto2 兼容
使用 repeated
关键字来定义数组。
message UserRequest {
string name = 1;
int32 age = 2;
string addr = 3;
Sex sex = 4;
// 定义一个数组
repeated string cellphones = 5;
}
在开发的过程中经常需要使用关联字段,很自然的想到使用map,protobuf也提供了map的类型。
message UserResponse {
string name = 1;
map<string, string> otherMap = 2;
}
注意: map 字段前面不能是repeated
message UserResponse {
string name = 1;
int32 age = 2;
string addr = 3;
OtherMsg otherMsg = 4;
map<string, string> otherMap = 5;
// 嵌套对象
message OtherMsg {
string ext1 = 1;
string ext2 = 2;
}
}
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>1.28.1</version>
</dependency>
syntax = "proto3"; option java_multiple_files = true; // 生成位置 option java_package = "com.lizq.jrpc.api"; option java_outer_classname = "UserService"; package user; service User { rpc SayHello (UserRequest) returns (UserResponse) {} } message UserRequest { string name = 1; int32 age = 2; string addr = 3; } message UserResponse { string name = 1; int32 age = 2; string addr = 3; OtherMsg otherMsg = 4; map<string, string> otherMap = 5; // 嵌套对象 message OtherMsg { string ext1 = 1; string ext2 = 2; } }
在 jrpc-api pom.xml
中添加如下:
<build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.6.2</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <!--<!– ${os.detected.classifier} 变量由${os.detected.name} 和 ${os.detected.arch} 组成--> <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.28.1:exe:${os.detected.classifier}</pluginArtifact> <!--protoSourceRoot 默认src/main/proto--> <protoSourceRoot>src/main/proto</protoSourceRoot> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
执行命令生产proto相关文件
将生成的文件拷贝到工程中,如下:
jrpc-server 为 springboot 项目。
jrpc-api
依赖<dependency>
<groupId>com.example</groupId>
<artifactId>jrpc-api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
@Service public class UserServiceImpl extends UserGrpc.UserImplBase { @Override public void sayHello(UserRequest request, StreamObserver<UserResponse> responseObserver) { Map<String, String> otherMap = new HashMap<>(); otherMap.put("test", "testmap"); UserResponse response = UserResponse.newBuilder() .setName("server:" + request.getName()) .setAddr("server:" + request.getAddr()) .setAge(request.getAge()) .setOtherMsg(UserResponse.OtherMsg.newBuilder() .setExt1("ext1") .setExt2("ext2") .build()) .putAllOtherMap(otherMap) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } }
@Configuration public class GrpcServerConfiguration { @Value("${grpc.server-port}") private int port; @Bean public Server server() throws Exception { System.out.println("Starting gRPC on port {}." + port); // 构建服务端 ServerBuilder<?> serverBuilder = ServerBuilder.forPort(port); // 添加需要暴露的接口 this.addService(serverBuilder); // start Server server = serverBuilder.build().start(); System.out.println("gRPC server started, listening on {}." + port); // 添加服务端关闭的逻辑 Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("Shutting down gRPC server."); if (server != null) { // 关闭服务端 server.shutdown(); } System.out.println("gRPC server shut down successfully."); })); if (server != null) { // 服务端启动后直到应用关闭都处于阻塞状态,方便接收请求 server.awaitTermination(); } return server; } @Autowired private UserServiceImpl userService; /** * 添加需要暴露的接口 * @param serverBuilder */ private void addService(ServerBuilder<?> serverBuilder) { serverBuilder.addService(userService); } }
server:
port: 8081
spring:
application:
name: spring-boot-jrpc-server
grpc:
server-port: 18081
jrpc-api
依赖<dependency>
<groupId>com.example</groupId>
<artifactId>jrpc-api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
@Configuration public class GrpcClientConfiguration { @Value("${server-host}") private String host; /** * gRPC Server的端口 */ @Value("${server-port}") private int port; @Bean public ManagedChannel managedChannel() { // 开启gRPC客户端 ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); System.out.println("gRPC client started, server address: " + host + " , " + port); // 添加客户端关闭的逻辑 Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { // 调用shutdown方法后等待1秒关闭channel managedChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS); System.out.println("gRPC client shut down successfully."); } catch (InterruptedException e) { e.printStackTrace(); } })); return managedChannel; } @Autowired private ManagedChannel managedChannel; @Bean public UserGrpc.UserBlockingStub userBlockingStub(ManagedChannel channel) { // 通过channel获取到服务端的stub return UserGrpc.newBlockingStub(managedChannel); } }
server:
port: 8080
spring:
application:
name: spring-boot-jrpc-client
# 本地测试
server-host: 127.0.0.1
server-port: 18081
@RestController("/user") public class UserController { @Autowired private UserGrpc.UserBlockingStub userBlockingStub; @GetMapping("/sayHello") public String sayHello(String name, String addr, int age) { UserRequest request = UserRequest.newBuilder() .setName(name) .setAddr(addr) .setAge(age) .build(); UserResponse response; try { response = userBlockingStub.sayHello(request); } catch (StatusRuntimeException e) { e.printStackTrace(); return e.getMessage(); } return response.toString(); } }
浏览器访问:http://localhost:8080/user/sayHello?name=test&addr=addr&age=99
返回:
name: "server:test" age: 99 addr: "server:addr" otherMsg { ext1: "ext1" ext2: "ext2" } otherMap { key: "test" value: "testmap" }
可以阅读 Alibaba Nacos 2.x 源码,通过 gRPC 进行client、server网络通信,并且会有连接保持、重试机制。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。