gRPC 和 restful API 都提供了一套通信机制,用于 server/client 模型通信,而且它们都使用 http 作为底层的传输协议(严格地说, gRPC 使用的 http2.0,而 restful api 则不一定)。不过 gRPC 还是有些特有的优势,如下:
- gRPC 可以通过 protobuf 来定义接口,从而可以有更加严格的接口约束条件。
- 另外,通过 protobuf 可以将数据序列化为二进制编码,这会大幅减少需要传输的数据量,从而大幅提高性能。
- gRPC 可以方便地支持流式通信(理论上通过 http2.0 就可以使用 streaming 模式, 但是通常 web 服务的 restful api 似乎很少这么用,通常的流式数据应用如视频流,一般都会使用专门的协议如 HLS,RTMP 等,这些就不是我们通常 web 服务了,而是有专门的服务器应用。)
建议在使用前先仔细阅读 gRPC 的官方文档。
Vert.x 为 gRPC 开发了个插件,支持使用 Verticles 启动服务,以及支持非阻塞。所以在生成代码时,引入完 gRPC 官方的工具后还需要引入 Vert.x 的插件来保证让代码生成顺利。Vert.x 的文档见:https://vertx.io/docs/vertx-grpc/java/
Maven 使用 gRPC + 协程的完整代码示例见:https://github.com/cloudoptlab/cloudopt-next-grpc-example
在上面的示例中没有使用 Vert.x 的插件来生成 gRPC 的代码,而是使用了第三方的 Kotlin 协程插件来生成。要不然无法使用 Kotlin 的协程。
下面我们使用 gradle 来演示下生成 protobuf 的代码:
我们在 proto 文件夹中放入 hello_world.proto 这个文件,内容如下。
syntax = "proto3";
option java_multiple_files = true;
option java_package = "net.cloudopt.next.grpc.test.example";
option java_outer_classname = "HelloWorldProto";
package helloworld;
// The greeting service definition.
service Greeter {
// 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;
}
接着我们修改 build.gradle 文件。
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.15'
}
}
apply plugin: 'com.google.protobuf'
sourceSets {
main {
proto {
srcDir "src/main/proto"
}
java {
srcDirs "src/main/java", "generated/main/java", "generated/main/grpc", "generated/main/vertx"
}
kotlin {
srcDirs "src/main/kotlin", "generated/main/kotlin"
}
}
test {
proto {
srcDir "src/test/proto"
}
java {
srcDirs "src/test/java", "generated/test/java", "generated/test/grpc", "generated/test/vertx"
}
kotlin {
srcDirs "src/test/kotlin", "generated/test/kotlin"
}
}
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.2.0'
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:1.25.0"
}
vertx {
artifact = "io.vertx:vertx-grpc-protoc-plugin:${rootProject.property('vertx_version')}"
}
}
generateProtoTasks.generatedFilesBaseDir = "generated"
generateProtoTasks {
all()*.plugins {
grpc
vertx
}
}
}
dependencies {
implementation("com.google.protobuf:protobuf-java:3.6.1")
implementation("io.grpc:grpc-stub:${rootProject.property('grpc_version')}")
implementation("io.grpc:grpc-protobuf:${rootProject.property('grpc_version')}")
implementation("javax.annotation:javax.annotation-api:1.3.1")
implementation("io.vertx:vertx-grpc:${rootProject.property('vertx_version')}")
}
默认会把代码生成到 generated 文件夹下。
使用插件非常简单,你除了需要引入 Next 的插件外,可能还需要引入相关的依赖如 protobuf-java、grpc-stub、grpc-protobuf、vertx-grpc 外。
<dependency>
<groupId>net.cloudopt.next</groupId>
<artifactId>cloudopt-next-grpc</artifactId>
<version>${version}</version>
</dependency>
接着加载插件。
fun main() {
NextServer.addPlugin(GrpcPlugin())
NextServer.run()
}
接着只需要在任意继承了 gRPC 生成的代码或是 Vert.x 生成的代码的类上声明 @GrpcService 注解即可。
class MyInterceptor : ServerInterceptor {
override fun <ReqT : Any, RespT : Any> interceptCall(
call: ServerCall<ReqT, RespT>,
headers: Metadata,
next: ServerCallHandler<ReqT, RespT>
): ServerCall.Listener<ReqT> {
println("via MyInterceptor...")
return next.startCall(call, headers)
}
}
@GrpcService(interceptors = [MyInterceptor::class])
class HelloWordService : VertxGreeterGrpc.GreeterVertxImplBase() {
override fun sayHello(request: HelloRequest): Future<HelloReply> {
var helloReply: HelloReply = HelloReply.newBuilder()
.setMessage(request.name)
.build()
return Future.succeededFuture(helloReply)
}
}
注解中的 interceptors 表示 gRPC 自带的 ServerInterceptor 功能,会在自动扫描注册时自动把注解中的 ServerInterceptor 一并注册进入,形成拦截的效果。
默认 gRPC 启动是在 9090 端口,你也可以修改 application.json 来更改接口:
"grpc": {
"port": 9090,
"ssl": false
}
如果 ssl 设置为 true,那么你需要在启动插件前额外设置 SSL 相关的配置。
fun main() {
GrpcManager.optionsHandler= Handler<HttpServerOptions>{ options->
options
.setSsl(true)
.setUseAlpn(true).keyStoreOptions = JksOptions()
.setPath("server-keystore.jks")
.setPassword("secret")
}
NextServer.addPlugin(GrpcPlugin())
NextServer.run()
}
如果引入 gRPC 之类的插件发现 http 无法正常访问的话,有可能是出现了 netty 版本冲突的问题,需要将 gRPC 所依赖的 netty 版本或者是 cloudopt-next-web 依赖的 netty-all 排除掉。