gRPC 学习笔记
Contents
概念
定义服务
默认情况下, gRPC 使用 protocol buffers 作为接口定义语言. 例如
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
单个请求, 单个响应
rpc SayHello(HelloRequest) returns (HelloResponse){ }
单个请求, 响应为一个message顺序流
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){ }
请求顺序流, 单个响应
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) { }
请求顺序流, 响应顺序流
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){ }
使用 api
服务端: 服务器实现了声明的服务并运行一个 gRPC server 来处理请求
客户端: 有一个本地的对象称为 stub
(其他语言可能更喜欢使用 client
)
同步 VS 异步
同步 RPC 调用会一直阻塞直到服务器端返回数据.
异步 RPC 则不会一直等待服务器返回.
RPC 的生命周期
单个请求, 单个响应
- 一旦 client 在 stub/client 对象调用方法时, server 端会通知 RPC 为该次请求调用相应的 client 的元数据(metadata)
- server 端也可以返回它自己的 initial metadata (这必须在返回任何的 response 之前发送) 或者等待 client 的请求 message.
- 一旦 server 已经有了 client 的请求 message, 它就可以处理了. 返回返回 response 及状态详情
- 如果状态是 OK, 则 client 就会获取 response, 在 client 端就完成了整个调用.
server 为 streaming RPC
这跟上面的差不多, 除了 server 会返回一个 response stream .
即 response , status 状态, 结尾的 metadata
client 为 stream RPC
这跟上面的差不多, 除了 client 发送的是一个 request stream .
双向 streaming RPC
deadlines/timeouts
在 client 中, 允许设置等待多长时间, 超出则会触发 DEADLINE_EXCEDED
错误.
在 server 中, 它可以查询一个特定的 RPC 请求是否 timeouts 或者还有多久才完成 RPC
RPC 终止
client 和 server 都是独立的, 本地调用是否成功的结论, 它们可能不一致.
取消 RPC
client 和 server 在任意时间都可以取消一个 RPC.
注意, 它不是 undo, 即不会回滚的.
metadata 元数据
metadata 是一些关于特定 RPC 请求(例如 认证), 由一堆 k-v 键值对组成的. key 为 strings 类型, value 一般也是 strings 类型(也可以是二进制类型).
metadata 对于 gRPC 自身不是透明的.
访问 metadata 是取决于特定语言的.
channels
gRPC channel 提供了一个 connection 连接到 server 的特定的 host 和 port , 当创建一个 client stub 时会被使用.
client 可以指定 channel 的参数来修改 gRPC 的默认行为. 例如 message 的压缩开关.
一个 channel 有状态, 包含 connected
和 idel
认证
你可以使用 gRPC 支持的机制 - SSL/TLS 带有或不带 Google token-based 认证, 或者你可以添加你自定义的认证插件来扩展.
类型
- Channel credentials
- Call credentials
也可以使用组合: CompositeChannelCredentials
示例代码
没加密或没认证:
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext(true)
.build();
GreeterGrpc.GreeterStub stub = GreeterGrpc.newStub(channel);
server SSL/TLS
Server server = ServerBuilder.forPort(8443)
// Enable TLS
.useTransportSecurity(certChainFile, privateKeyFile)
.addService(TestServiceGrpc.bindService(serviceImplementation))
.build();
server.start();
client SSL/TLS
// With server authentication SSL/TLS
ManagedChannel channel = ManagedChannelBuilder.forAddress("myservice.example.com", 443)
.build();
GreeterGrpc.GreeterStub stub = GreeterGrpc.newStub(channel);
// With server authentication SSL/TLS; custom CA root certificates; not on Android
ManagedChannel channel = NettyChannelBuilder.forAddress("myservice.example.com", 443)
.sslContext(GrpcSslContexts.forClient().trustManager(new File("roots.pem")).build())
.build();
GreeterGrpc.GreeterStub stub = GreeterGrpc.newStub(channel);
错误码
例子
生成Java代码
https://github.com/grpc/grpc-java/blob/master/README.md
典型的 pom.xml
<project>
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.5.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.grpc/grpc-all -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.8.0</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.5.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.8.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
将你的 proto 文件, 放在 src/main/proto
目录下即可.
然后打包的时候, 插件会自动生成相应的 grpc 代码到 target/generated-sources/protobuf
目录下面了.
打包时输出到指定目录
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.5.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.8.0:exe:${os.detected.classifier}</pluginArtifact>
<outputDirectory>src/main/java</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
client 示例代码
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
private ManagedChannel channel;
private GreeterGrpc.GreeterBlockingStub blockingStub;
@PostConstruct
public void init() {
channel = ManagedChannelBuilder.forAddress("127.0.0.1", 8999).usePlaintext(true).build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
GreeterMessage.GreeterRequest request = GreeterMessage.GreeterRequest.newBuilder().setName(" world").build();
GreeterMessage.GreeterResponse response = blockingStub.sayHello(request);
System.out.println(response.getName());
}
}
server 示例代码
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(DemoApplication.class, args);
server.awaitTermination();
}
private static Server server;
@PostConstruct
public void init() throws IOException, InterruptedException {
server = ServerBuilder.forPort(8999).addService(new GreeterImpl()).build().start();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
server.shutdown();
System.err.println("*** server shut down");
}
});
server.awaitTermination();
}
@PreDestroy
public void detroy() {
if (server != null) {
server.shutdown();
}
}
}
常见问题收集
传输文件(或二进制)
在 proto 文件中将该字段定义为 bytes
类型即可(在Java中时, 它表示是 ByteString
类型)