【问题标题】:Generically forwarding a GRPC call一般转发 GRPC 调用
【发布时间】:2020-02-21 15:28:47
【问题描述】:

我有一个 GRPC API,经过重构,一些包被重命名。这包括我们定义 API 的原型文件之一中的 package 声明。像这样的:

package foo;

service BazApi {
    rpc FooEventStream(stream Ack) returns (stream FooEvent);
}

改成

package bar;

service BazApi {
    rpc FooEventStream(stream Ack) returns (stream FooEvent);
}

服务器端使用grpc-java 实现,顶部带有scala 和monix。

这对于使用新 proto 文件的客户端来说一切正常,但对于在旧 proto 文件之上构建的旧客户端,这会导致问题:UNIMPLEMENTED: Method not found: foo.BazApi/FooEventStream

通过 GRPC API 传递的消息的实际数据格式没有改变,只有包。

由于我们需要保持向后兼容性,我一直在寻找一种方法来让旧客户端在保持名称更改的同时正常工作。

我希望使用通用的 ServerInterceptor 来完成这项工作,它能够检查来电,查看它来自旧客户端(我们在标头中有客户端版本)并将其重定向/转发到改名服务。 (因为只是包名发生了变化,这很容易弄清楚,例如foo.BazApi/FooEventStream -> bar.BazApi/FooEventStream

但是,似乎没有一种优雅的方法可以做到这一点。我认为可以通过将新的ClientCall 启动到正确的端点,然后通过委托给ClientCall 在拦截器中处理ServerCall,但这将需要一堆管道代码来正确处理一元/clientStreaming/ serverStreaming/bidiStreaming 调用。

有没有更好的方法来做到这一点?

【问题讨论】:

    标签: grpc grpc-java


    【解决方案1】:

    使用较低级别的“通道”API,您可以制作代理而无需太多 工作。您主要只是代理从ServerCall.ListenerClientCallClientCall.ListenerServerCall 的事件。您将了解较低级别的MethodDescriptor 和很少使用的HandlerRegistry。处理流控制也有一些复杂性(isReady()request())。

    不久前我做了一个例子,但从来没有花时间将它合并到 grpc-java 本身。它目前可用on my random branch。您应该能够通过更改localhost:8980 并重写传递给channel.newCall(...)MethodDescriptor 来使其工作。类似于:

    MethodDescriptor desc = serverCall.getMethodDescriptor();
    if (desc.getFullMethodName().startsWith("foo.BazApi/")) {
      String newName = desc.getFullMethodName().replace("foo.BazApi/", "bar.BazApi/");
      desc = desc.toBuilder().setFullMethodName(newName).build();
    }
    ClientCall<ReqT, RespT> clientCall
        = channel.newCall(desc, CallOptions.DEFAULT);
    

    【讨论】:

    • 哦,我现在意识到这比您可能需要的要多一点。我提供了另一个不做完全代理的答案。
    【解决方案2】:

    如果您可以轻松更改服务器,则可以让它同时支持这两个名称。您可以考虑使用两个不同的描述符注册服务两次的解决方案。

    每个服务都有一个bindService() 方法,该方法返回一个ServerServiceDefinition。您可以通过普通的serverBuilder.addService() 将定义传递给服务器。

    这样你就可以得到正常的ServerServiceDefinition,然后改写成新的名字,然后注册新的名字。

    BazApiImpl service = new BazApiImpl();
    serverBuilder.addService(service); // register "bar"
    
    ServerServiceDefinition barDef = service.bindService();
    ServerServiceDefinition fooDefBuilder = ServerServiceDefinition.builder("foo.BazApi");
    for (ServerMethodDefinition<?,?> barMethodDef : barDef.getMethods()) {
      MethodDescriptor desc = barMethodDef.getMethodDescriptor();
      String newName = desc.getFullMethodName().replace("foo.BazApi/", "bar.BazApi/");
      desc = desc.toBuilder().setFullMethodName(newName).build();
      foDefBuilder.addMethod(desc, barMethodDef.getServerCallHandler());
    }
    serverBuilder.addService(fooDefBuilder.build()); // register "foo"
    

    【讨论】:

    • 不错的解决方案!今年早些时候,当我无意中发现即使 protobufs 从不通过网络发送包名称时,我也可以使用它,但 grpc 服务确实!我仍然不明白为什么,因为对于我想象的用例,元组“监听端口,服务”应该足够了;我不明白为什么您需要通过三重“侦听端口、包、服务”来区分服务。
    • 这就像一个魅力。感谢您花时间解释这一点!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 2011-02-08
    相关资源
    最近更新 更多