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 greetingrpc SayHello (HelloRequest) returns (HelloReply) {}}// The request message containing the user's name.message HelloRequest {string name = 1;}// The response message containing the greetingsmessage 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 {grpcvertx}}}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 排除掉。
