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 这个文件,内容如下。

    1. syntax = "proto3";
    2. option java_multiple_files = true;
    3. option java_package = "net.cloudopt.next.grpc.test.example";
    4. option java_outer_classname = "HelloWorldProto";
    5. package helloworld;
    6. // The greeting service definition.
    7. service Greeter {
    8. // Sends a greeting
    9. rpc SayHello (HelloRequest) returns (HelloReply) {}
    10. }
    11. // The request message containing the user's name.
    12. message HelloRequest {
    13. string name = 1;
    14. }
    15. // The response message containing the greetings
    16. message HelloReply {
    17. string message = 1;
    18. }

    接着我们修改 build.gradle 文件。

    1. buildscript {
    2. repositories {
    3. mavenCentral()
    4. }
    5. dependencies {
    6. classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.15'
    7. }
    8. }
    9. apply plugin: 'com.google.protobuf'
    10. sourceSets {
    11. main {
    12. proto {
    13. srcDir "src/main/proto"
    14. }
    15. java {
    16. srcDirs "src/main/java", "generated/main/java", "generated/main/grpc", "generated/main/vertx"
    17. }
    18. kotlin {
    19. srcDirs "src/main/kotlin", "generated/main/kotlin"
    20. }
    21. }
    22. test {
    23. proto {
    24. srcDir "src/test/proto"
    25. }
    26. java {
    27. srcDirs "src/test/java", "generated/test/java", "generated/test/grpc", "generated/test/vertx"
    28. }
    29. kotlin {
    30. srcDirs "src/test/kotlin", "generated/test/kotlin"
    31. }
    32. }
    33. }
    34. protobuf {
    35. protoc {
    36. artifact = 'com.google.protobuf:protoc:3.2.0'
    37. }
    38. plugins {
    39. grpc {
    40. artifact = "io.grpc:protoc-gen-grpc-java:1.25.0"
    41. }
    42. vertx {
    43. artifact = "io.vertx:vertx-grpc-protoc-plugin:${rootProject.property('vertx_version')}"
    44. }
    45. }
    46. generateProtoTasks.generatedFilesBaseDir = "generated"
    47. generateProtoTasks {
    48. all()*.plugins {
    49. grpc
    50. vertx
    51. }
    52. }
    53. }
    54. dependencies {
    55. implementation("com.google.protobuf:protobuf-java:3.6.1")
    56. implementation("io.grpc:grpc-stub:${rootProject.property('grpc_version')}")
    57. implementation("io.grpc:grpc-protobuf:${rootProject.property('grpc_version')}")
    58. implementation("javax.annotation:javax.annotation-api:1.3.1")
    59. implementation("io.vertx:vertx-grpc:${rootProject.property('vertx_version')}")
    60. }

    默认会把代码生成到 generated 文件夹下。

    使用插件非常简单,你除了需要引入 Next 的插件外,可能还需要引入相关的依赖如 protobuf-java、grpc-stub、grpc-protobuf、vertx-grpc 外。

    1. <dependency>
    2. <groupId>net.cloudopt.next</groupId>
    3. <artifactId>cloudopt-next-grpc</artifactId>
    4. <version>${version}</version>
    5. </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 排除掉。