本文整理自刘军在 Dubbo 成都 meetup 上分享的《Dubbo 在多语言和协议穿透性方向上的探索》。
本文总体上可分为基础产品简介、Dubbo 对 gRPC (HTTP/2) 和 Protobuf 的支持及示例演示三部分,在简介部分介绍了 Dubbo、HTTP/2、gRPC、Protobuf 的基本概念和特点;第二部分介绍了 Dubbo 为何要支持 gRPC (HTTP/2) 和 Protobuf,以及这种支持为 gRPC 和 Dubbo 开发带来的好处与不同;第三部分通过两个实例分别演示了 Dubbo gRPC 和 Dubbo Protobuf 的使用方式。
从协议层面展开,以下是当前 2.7 版本支持的 Dubbo 协议
众所周知,Dubbo 协议是直接定义在 TCP 传输层协议之上,由于 TCP 高可靠全双工的特点,为 Dubbo 协议的定义提供了最大的灵活性,但同时也正是因为这样的灵活性,RPC 协议普遍都是定制化的私有协议,Dubbo 同样也面临这个问题。在这里我们着重讲一下 Dubbo 在协议通用性方面值得改进的地方,关于协议详细解析请参见官网博客
相比于直接构建与 TPC 传输层的私有 RPC 协议,构建于 HTTP 之上的远程调用解决方案会有更好的通用性,如WebServices 或 REST 架构,使用 HTTP + JSON 可以说是一个事实标准的解决方案。
之所有选择构建在 HTTP 之上,我认为有两个最大的优势:
具体来说,HTTP/1 的优势和限制是:
HTTP/2 保留了 HTTP/1 的所有语义,在保持兼容的同时,在通信模型和传输效率上做了很大的改进。
上面提到了在 HTTP 及 TCP 协议之上构建 RPC 协议各自的优缺点,相比于 Dubbo 构建于 TPC 传输层之上,Google 选择将 gRPC 直接定义在 HTTP/2 协议之上,关于 gRPC 的 基本介绍和 设计愿景请参考以上两篇文章,我这里仅摘取 设计愿景 中几个能反映 gRPC 设计目的特性来做简单说明。
总的来说,在这样的设计理念指导下,gRPC 最终被设计为一个跨语言、跨平台的、通用的、高性能的、基于 HTTP/2 的 RPC 协议和框架。
Protocol buffers (Protobuf) 是 Google 推出的一个跨平台、语言中立的结构化数据描述和序列化的产品,它定义了一套结构化数据定义的协议,同时也提供了相应的 Compiler 工具,用来将语言中立的描述转化为相应语言的具体描述。
它的一些特性包括:
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nepFAQq3-1575271682939)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TyIET2Cc-1575271682939)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
除了结构化数据描述之外,Protobuf 还支持定义 RPC 服务,它允许我们定义一个 .proto 的服务描述文件,进而利用 Protobuf Compiler 工具生成特定语言和 RPC 框架的接口和 stub。后续将要具体讲到的 gRPC + Protobuf、Dubbo-gRPC + Protobuf 以及 Dubbo + Protobuf 都是通过定制 Compiler 类实现的。
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hf3JRF4N-1575271682940)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y9fFwcS7-1575271682941)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
跨语言的服务开发涉及到多个方面,从服务定义、RPC 协议到序列化协议都要做到语言中立,同时还针对每种语言有对应的 SDK 实现。虽然得益于社区的贡献,现在 Dubbo 在多语言 SDK 实现上逐步有了起色,已经提供了包括 Java, Go, PHP, C#, Python, NodeJs, C 等版本的客户端或全量实现版本,但在以上提到的跨语言友好型方面,以上三点还是有很多可改进之处。
gRPC 是 Google 开源的构建在 HTTP/2 之上的一个 PRC 通信协议。Dubbo 依赖其灵活的协议扩展机制,增加了对 gRPC (HTTP/2) 协议的支持。
目前的支持限定在 Dubbo Java 语言版本,后续 Go 语言或其他语言版本将会以类似方式提供支持。下面,通过一个简单的示例来演示如何在 Dubbo 中使用 gRPC 协议通信。
首先,通过标准的 Protobuf 协议定义服务如下:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
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;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kc79fIBm-1575271682941)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z1be5Xjr-1575271682941)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
在此,我们定义了一个只有一个方法 sayHello 的 Greeter 服务,同时定义了方法的入参和出参,
org.xolstice.maven.plugins
protobuf-maven-plugin
0.5.1
com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}
dubbo-grpc-java
org.apache.dubbo:protoc-gen-dubbo-java:1.19.0-SNAPSHOT:exe:${os.detected.classifier}
build/generated/source/proto/main/java
false
grpc
compile
compile-custom
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WbCyXDh8-1575271682941)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DoIIGF20-1575271682942)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
其中,
pluginArtifact 指定了 Dubbo 定制版本的 Java Protobuf Compiler 插件,通过这个插件来在编译过程中生成 Dubbo 定制版本的 gRPC stub。
org.apache.dubbo:protoc-gen-dubbo-java:1.19.0-SNAPSHOT:exe:${os.detected.classifier}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t6EHTS4F-1575271682943)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BLP8EP2e-1575271682944)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
由于 protoc-gen-dubbo-java 支持 gRPC 和 Dubbo 两种协议,可生成的 stub 类型,默认值是 gRPC,关于 dubbo 协议的使用可参见 使用 Protobuf 开发 Dubbo 服务。
grpc
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pqm0pQs9-1575271682944)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ykkBPDCR-1575271682945)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
# 运行以下 maven 命令
$ mvn clean compile
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KkEDn7y-1575271682945)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7dJhEAkX-1575271682945)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
生成的 Stub 和消息类 如下:
重点关注 GreeterGrpc ,包含了所有 gRPC 标准的 stub 类/方法,同时增加了 Dubbo 特定的接口,之后 Provider 端的服务暴露和 Consumer 端的服务调用都将依赖这个接口。
/**
* Code generated for Dubbo
*/
public interface IGreeter {
default public io.grpc.examples.helloworld.HelloReply sayHello(io.grpc.examples.helloworld.HelloRequest request) {
throw new UnsupportedOperationException("No need to override this method, extend XxxImplBase and override all methods it allows.");
}
default public com.google.common.util.concurrent.ListenableFuture sayHelloAsync(
io.grpc.examples.helloworld.HelloRequest request) {
throw new UnsupportedOperationException("No need to override this method, extend XxxImplBase and override all methods it allows.");
}
public void sayHello(io.grpc.examples.helloworld.HelloRequest request,
io.grpc.stub.StreamObserver responseObserver);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5eDThE32-1575271682945)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omFzrsWO-1575271682945)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
继承 GreeterGrpc.GreeterImplBase (来自第 2 步),编写业务逻辑,这点和原生 gRPC 是一致的。
package org.apache.dubbo.samples.basic.impl;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.stub.StreamObserver;
public class GrpcGreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver responseObserver) {
System.out.println("Received request from client.");
System.out.println("Executing thread is " + Thread.currentThread().getName());
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello " + request.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nsVjrC0i-1575271682945)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0snLwWDj-1575271682946)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
以 Spring XML 为例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zKWrdEst-1575271682946)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qw4QFGFs-1575271682947)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring/dubbo-demo-provider.xml");
context.start();
System.out.println("dubbo service started");
new CountDownLatch(1).await();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n84utrZS-1575271682947)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0QWITr50-1575271682948)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ft1L7sBP-1575271682948)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EjjhoUbf-1575271682949)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring/dubbo-demo-consumer.xml");
context.start();
GreeterGrpc.IGreeter greeter = (GreeterGrpc.IGreeter) context.getBean("greeter");
HelloReply reply = greeter.sayHello(HelloRequest.newBuilder().setName("world!").build());
System.out.println("Result: " + reply.getMessage());
System.in.read();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1P0YZXg0-1575271682949)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEFixSxD-1575271682949)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
一、异步调用
再来看一遍 protoc-gen-dubbo-java 生成的接口:
/**
* Code generated for Dubbo
*/
public interface IGreeter {
default public HelloReply sayHello(HelloRequest request) {
// ......
}
default public ListenableFuture sayHelloAsync(HelloRequest request) {
// ......
}
public void sayHello(HelloRequest request, StreamObserver responseObserver);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eANNZVhX-1575271682950)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6LjhNs6R-1575271682950)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
这里为 sayHello 方法生成了三种类型的重载方法,分别用于同步调用、异步调用和流式调用,如果消费端要进行异步调用,直接调用 sayHelloAsync() 即可:
public static void main(String[] args) throws IOException {
// ...
GreeterGrpc.IGreeter greeter = (GreeterGrpc.IGreeter) context.getBean("greeter");
ListenableFuture future =
greeter.sayHAsyncello(HelloRequest.newBuilder().setName("world!").build());
// ...
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IhYQxzDS-1575271682951)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s3gT1kgj-1575271682951)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
二、高级配置
由于当前实现方式是直接集成了 gRPC-java SDK,因此很多配置还没有和 Dubbo 侧对齐,或者还没有以 Dubbo 的配置形式开放,因此,为了提供最大的灵活性,我们直接把 gRPC-java 的配置接口暴露了出来。
绝大多数场景下,你可能并不会用到以下扩展,因为它们更多的是对 gRPC 协议的拦截或者 HTTP/2 层面的配置。同时使用这些扩展点可能需要对 HTTP/2 或 gRPC 有基本的了解。
扩展点
目前支持的扩展点如下:
GrpcConfigurator 是最通用的扩展点,我们以此为例来说明一下,其基本定义如下:
public interface GrpcConfigurator {
// 用来定制 gRPC NettyServerBuilder
default NettyServerBuilder configureServerBuilder(NettyServerBuilder builder, URL url) {
return builder;
}
// 用来定制 gRPC NettyChannelBuilder
default NettyChannelBuilder configureChannelBuilder(NettyChannelBuilder builder, URL url) {
return builder;
}
// 用来定制 gRPC CallOptions, 定义某个服务在每次请求间传递数据
default CallOptions configureCallOptions(CallOptions options, URL url) {
return options;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NC2ETiiF-1575271682951)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKOayKut-1575271682952)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
以下是一个示例扩展实现:
public class MyGrpcConfigurator implements GrpcConfigurator {
private final ExecutorService executor = Executors
.newFixedThreadPool(200, new NamedThreadFactory("Customized-grpc", true));
@Override
public NettyServerBuilder configureServerBuilder(NettyServerBuilder builder, URL url) {
return builder.executor(executor);
}
@Override
public NettyChannelBuilder configureChannelBuilder(NettyChannelBuilder builder, URL url)
{
return builder.flowControlWindow(10);
}
@Override
public CallOptions configureCallOptions(CallOptions options, URL url) {
return options.withOption(CallOptions.Key.create("key"), "value");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7MKFnZ1Z-1575271682952)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5kv8kONu-1575271682952)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
配置为 Dubbo SPI,`resources/META-INF/services 增加配置文件
default=org.apache.dubbo.samples.basic.comtomize.MyGrpcConfigurator
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDOBXKQv-1575271682953)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hOqUs3mu-1575271682953)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
private final ExecutorService executor = Executors
.newFixedThreadPool(200, new NamedThreadFactory("Customized-grpc", true));
public NettyServerBuilder configureServerBuilder(NettyServerBuilder builder, URL url)
{
return builder.executor(executor);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pp695tlD-1575271682953)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wzo3iReg-1575271682955)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
@Override
public NettyChannelBuilder configureChannelBuilder(NettyChannelBuilder builder, URL url)
{
return builder.flowControlWindow(10);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GYz4e2Ij-1575271682955)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pC2dcWXA-1575271682957)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
@Override
public CallOptions configureCallOptions(CallOptions options, URL url) {
if (url.getServiceInterface().equals("xxx.DemoService")) {
return options.withOption(CallOptions.Key.create("key"), "value");
} else {
return options;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yDcXwgSm-1575271682958)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-onypDtui-1575271682958)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
三、双向流式通信
代码中还提供了一个支持双向流式通信的示例,同时提供了拦截流式调用的 Interceptor 扩展示例实现。
* MyClientStreamInterceptor,工作在 client 端,拦截发出的请求流和接收的响应流
* MyServerStreamInterceptor,工作在 server 端,拦截收到的请求流和发出的响应流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olaCT74R-1575271682958)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S6Nhimfy-1575271682958)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
四、TLS 配置
配置方式和 Dubbo 提供的通用的 TLS 支持一致,具体请参见 Dubbo 官方文档
下面,我们以一个具体的示例来看一下基于 Protobuf 的 Dubbo 服务开发流程。
通过标准 Protobuf 定义服务
syntax = "proto3";
option java_multiple_files = true;
option java_package = "org.apache.dubbo.demo";
option java_outer_classname = "DemoServiceProto";
option objc_class_prefix = "DEMOSRV";
package demoservice;
// The demo service definition.
service DemoService {
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;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TKJ6lygX-1575271682958)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MOE4pM5P-1575271682959)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
这里定义了一个 DemoService 服务,服务只包含一个 sayHello 方法,同时定义了方法的入参和出参。
org.xolstice.maven.plugins
protobuf-maven-plugin
0.5.1
com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}
dubbo-grpc-java
org.apache.dubbo:protoc-gen-dubbo-java:1.19.0-SNAPSHOT:exe:${os.detected.classifier}
build/generated/source/proto/main/java
false
dubbo
compile
compile-custom
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-29yGJCBw-1575271682959)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UXUJy1J7-1575271682959)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
注意,这里与 Dubbo 对 gRPC 支持部分的区别在于: dubbo
# 运行以下 maven 命令
$mvn clean compile
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cPhs32kd-1575271682959)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2iwfiDnN-1575271682960)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
生成的 Java 类如下:
DemoServiceDubbo 为 Dubbo 定制的 stub
public final class DemoServiceDubbo {
private static final AtomicBoolean registered = new AtomicBoolean();
private static Class> init() {
Class> clazz = null;
try {
clazz = Class.forName(DemoServiceDubbo.class.getName());
if (registered.compareAndSet(false, true)) {
org.apache.dubbo.common.serialize.protobuf.support.ProtobufUtils.marshaller(
org.apache.dubbo.demo.HelloRequest.getDefaultInstance());
org.apache.dubbo.common.serialize.protobuf.support.ProtobufUtils.marshaller(
org.apache.dubbo.demo.HelloReply.getDefaultInstance());
}
} catch (ClassNotFoundException e) {
// ignore
}
return clazz;
}
private DemoServiceDubbo() {}
public static final String SERVICE_NAME = "demoservice.DemoService";
/**
* Code generated for Dubbo
*/
public interface IDemoService {
static Class> clazz = init();
org.apache.dubbo.demo.HelloReply sayHello(org.apache.dubbo.demo.HelloRequest request);
java.util.concurrent.CompletableFuture sayHelloAsync(
org.apache.dubbo.demo.HelloRequest request);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EDONL9SF-1575271682960)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cJ5NLroI-1575271682960)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
最值得注意的是 IDemoService 接口,它会作为 Dubbo 服务定义基础接口。
从这一步开始,所有开发流程就和直接定义 Java 接口一样了。实现接口定义业务逻辑。
public class DemoServiceImpl implements DemoServiceDubbo.IDemoService {
private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public HelloReply sayHello(HelloRequest request) {
logger.info("Hello " + request.getName() + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return HelloReply.newBuilder()
.setMessage("Hello " + request.getName() + ", response from provider: "
+ RpcContext.getContext().getLocalAddress())
.build();
}
@Override
public CompletableFuture sayHelloAsync(HelloRequest request) {
return CompletableFuture.completedFuture(sayHello(request));
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MvweX25h-1575271682960)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2fMYLSNc-1575271682960)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
暴露 Dubbo 服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iDQxZD9Y-1575271682960)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h2EiMCz2-1575271682961)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
context.start();
System.in.read();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nzEgVIFQ-1575271682961)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EUQRFnUN-1575271682961)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
引用 Dubbo 服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YNuWju5M-1575271682962)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-opujuCZB-1575271682962)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
context.start();
IDemoService demoService = context.getBean("demoService", IDemoService.class);
HelloRequest request = HelloRequest.newBuilder().setName("Hello").build();
HelloReply reply = demoService.sayHello(request);
System.out.println("result: " + reply.getMessage());
System.in.read();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ei3Ce9om-1575271682962)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-55BCucKT-1575271682962)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
RPC 协议是实现微服务体系互通的核心组件,通常采用不同的微服务通信框架则意味着绑定某一个特定的协议,如 Spring Cloud 基于 HTTP、gRPC 提供 gRPC over HTTP/2、Thrift Hessian 等都是自定义私有协议。
Dubbo 自身同样提供了私有的 Dubbo 协议,这样你也能基于 Dubbo 协议构建微服务。但除了单一协议之外,和以上所有框架不同的,Dubbo 最大的优势在于它能同时支持多协议的暴露和消费,再配合 Dubbo 多注册订阅的模型,可以让 Dubbo 成为桥接多种不同协议的微服务体系的开发框架,轻松的实现不同微服务体系的互调互通或技术栈迁移。
这篇文章详细讲解了 Dubbo 对 gRPC 协议的支持,再加上 Dubbo 之前已具备的对 REST、Hessian、Thrift 等的支持,使 Dubbo 在协议互调上具备了基础。我们只需要在服务发现模型上也能实现和这些不同体系的打通,就能解决不同技术栈互调和迁移的问题。关于这部分的具体应用场景以及工作模式,我们将在接下来的文章中来具体分析。
**作者信息:**刘军,Github账号Chickenlj,Apache Dubbo PMC,项目核心维护者,见证了Dubbo从重启开源到Apache毕业的整个流程。现任职阿里巴巴中间件团队,参与服务框架、微服务相关工作,目前主要在推动Dubbo开源的云原生化。
原文链接
本文整理自刘军在 Dubbo 成都 meetup 上分享的《Dubbo 在多语言和协议穿透性方向上的探索》。
本文总体上可分为基础产品简介、Dubbo 对 gRPC (HTTP/2) 和 Protobuf 的支持及示例演示三部分,在简介部分介绍了 Dubbo、HTTP/2、gRPC、Protobuf 的基本概念和特点;第二部分介绍了 Dubbo 为何要支持 gRPC (HTTP/2) 和 Protobuf,以及这种支持为 gRPC 和 Dubbo 开发带来的好处与不同;第三部分通过两个实例分别演示了 Dubbo gRPC 和 Dubbo Protobuf 的使用方式。
从协议层面展开,以下是当前 2.7 版本支持的 Dubbo 协议
众所周知,Dubbo 协议是直接定义在 TCP 传输层协议之上,由于 TCP 高可靠全双工的特点,为 Dubbo 协议的定义提供了最大的灵活性,但同时也正是因为这样的灵活性,RPC 协议普遍都是定制化的私有协议,Dubbo 同样也面临这个问题。在这里我们着重讲一下 Dubbo 在协议通用性方面值得改进的地方,关于协议详细解析请参见官网博客
相比于直接构建与 TPC 传输层的私有 RPC 协议,构建于 HTTP 之上的远程调用解决方案会有更好的通用性,如WebServices 或 REST 架构,使用 HTTP + JSON 可以说是一个事实标准的解决方案。
之所有选择构建在 HTTP 之上,我认为有两个最大的优势:
具体来说,HTTP/1 的优势和限制是:
HTTP/2 保留了 HTTP/1 的所有语义,在保持兼容的同时,在通信模型和传输效率上做了很大的改进。
上面提到了在 HTTP 及 TCP 协议之上构建 RPC 协议各自的优缺点,相比于 Dubbo 构建于 TPC 传输层之上,Google 选择将 gRPC 直接定义在 HTTP/2 协议之上,关于 gRPC 的 基本介绍和 设计愿景请参考以上两篇文章,我这里仅摘取 设计愿景 中几个能反映 gRPC 设计目的特性来做简单说明。
总的来说,在这样的设计理念指导下,gRPC 最终被设计为一个跨语言、跨平台的、通用的、高性能的、基于 HTTP/2 的 RPC 协议和框架。
Protocol buffers (Protobuf) 是 Google 推出的一个跨平台、语言中立的结构化数据描述和序列化的产品,它定义了一套结构化数据定义的协议,同时也提供了相应的 Compiler 工具,用来将语言中立的描述转化为相应语言的具体描述。
它的一些特性包括:
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzMcaYIr-1575271682963)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C4H0aD9L-1575271682963)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
除了结构化数据描述之外,Protobuf 还支持定义 RPC 服务,它允许我们定义一个 .proto 的服务描述文件,进而利用 Protobuf Compiler 工具生成特定语言和 RPC 框架的接口和 stub。后续将要具体讲到的 gRPC + Protobuf、Dubbo-gRPC + Protobuf 以及 Dubbo + Protobuf 都是通过定制 Compiler 类实现的。
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Tje9OXF-1575271682963)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OSe1XDxE-1575271682964)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
跨语言的服务开发涉及到多个方面,从服务定义、RPC 协议到序列化协议都要做到语言中立,同时还针对每种语言有对应的 SDK 实现。虽然得益于社区的贡献,现在 Dubbo 在多语言 SDK 实现上逐步有了起色,已经提供了包括 Java, Go, PHP, C#, Python, NodeJs, C 等版本的客户端或全量实现版本,但在以上提到的跨语言友好型方面,以上三点还是有很多可改进之处。
gRPC 是 Google 开源的构建在 HTTP/2 之上的一个 PRC 通信协议。Dubbo 依赖其灵活的协议扩展机制,增加了对 gRPC (HTTP/2) 协议的支持。
目前的支持限定在 Dubbo Java 语言版本,后续 Go 语言或其他语言版本将会以类似方式提供支持。下面,通过一个简单的示例来演示如何在 Dubbo 中使用 gRPC 协议通信。
首先,通过标准的 Protobuf 协议定义服务如下:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
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;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Sze7Tey-1575271682964)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZXjDMF6f-1575271682964)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
在此,我们定义了一个只有一个方法 sayHello 的 Greeter 服务,同时定义了方法的入参和出参,
org.xolstice.maven.plugins
protobuf-maven-plugin
0.5.1
com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}
dubbo-grpc-java
org.apache.dubbo:protoc-gen-dubbo-java:1.19.0-SNAPSHOT:exe:${os.detected.classifier}
build/generated/source/proto/main/java
false
grpc
compile
compile-custom
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HL0PN7d5-1575271682964)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u0DqVcCM-1575271682964)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
其中,
pluginArtifact 指定了 Dubbo 定制版本的 Java Protobuf Compiler 插件,通过这个插件来在编译过程中生成 Dubbo 定制版本的 gRPC stub。
org.apache.dubbo:protoc-gen-dubbo-java:1.19.0-SNAPSHOT:exe:${os.detected.classifier}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ZaqViot-1575271682964)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KqnLnfMa-1575271682966)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
由于 protoc-gen-dubbo-java 支持 gRPC 和 Dubbo 两种协议,可生成的 stub 类型,默认值是 gRPC,关于 dubbo 协议的使用可参见 使用 Protobuf 开发 Dubbo 服务。
grpc
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KTSQOEOG-1575271682966)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8koUSAl3-1575271682966)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
# 运行以下 maven 命令
$ mvn clean compile
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zFDgZQck-1575271682966)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iaHnZtUm-1575271682967)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
生成的 Stub 和消息类 如下:
重点关注 GreeterGrpc ,包含了所有 gRPC 标准的 stub 类/方法,同时增加了 Dubbo 特定的接口,之后 Provider 端的服务暴露和 Consumer 端的服务调用都将依赖这个接口。
/**
* Code generated for Dubbo
*/
public interface IGreeter {
default public io.grpc.examples.helloworld.HelloReply sayHello(io.grpc.examples.helloworld.HelloRequest request) {
throw new UnsupportedOperationException("No need to override this method, extend XxxImplBase and override all methods it allows.");
}
default public com.google.common.util.concurrent.ListenableFuture sayHelloAsync(
io.grpc.examples.helloworld.HelloRequest request) {
throw new UnsupportedOperationException("No need to override this method, extend XxxImplBase and override all methods it allows.");
}
public void sayHello(io.grpc.examples.helloworld.HelloRequest request,
io.grpc.stub.StreamObserver responseObserver);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QMxPST3p-1575271682967)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vdqda5ZC-1575271682967)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
继承 GreeterGrpc.GreeterImplBase (来自第 2 步),编写业务逻辑,这点和原生 gRPC 是一致的。
package org.apache.dubbo.samples.basic.impl;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.stub.StreamObserver;
public class GrpcGreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver responseObserver) {
System.out.println("Received request from client.");
System.out.println("Executing thread is " + Thread.currentThread().getName());
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello " + request.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-92JaUNw5-1575271682967)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Fgbn5iM-1575271682967)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
以 Spring XML 为例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jNP8srZM-1575271682967)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jn7fOfy7-1575271682967)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring/dubbo-demo-provider.xml");
context.start();
System.out.println("dubbo service started");
new CountDownLatch(1).await();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1YtyC4b3-1575271682968)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TIYGGYqy-1575271682968)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EBAOkA3g-1575271682968)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SWl9G4hY-1575271682968)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring/dubbo-demo-consumer.xml");
context.start();
GreeterGrpc.IGreeter greeter = (GreeterGrpc.IGreeter) context.getBean("greeter");
HelloReply reply = greeter.sayHello(HelloRequest.newBuilder().setName("world!").build());
System.out.println("Result: " + reply.getMessage());
System.in.read();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YyhLNtcW-1575271682969)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l47iW7tO-1575271682969)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
一、异步调用
再来看一遍 protoc-gen-dubbo-java 生成的接口:
/**
* Code generated for Dubbo
*/
public interface IGreeter {
default public HelloReply sayHello(HelloRequest request) {
// ......
}
default public ListenableFuture sayHelloAsync(HelloRequest request) {
// ......
}
public void sayHello(HelloRequest request, StreamObserver responseObserver);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmrxokAk-1575271682969)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ykyMACYU-1575271682970)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
这里为 sayHello 方法生成了三种类型的重载方法,分别用于同步调用、异步调用和流式调用,如果消费端要进行异步调用,直接调用 sayHelloAsync() 即可:
public static void main(String[] args) throws IOException {
// ...
GreeterGrpc.IGreeter greeter = (GreeterGrpc.IGreeter) context.getBean("greeter");
ListenableFuture future =
greeter.sayHAsyncello(HelloRequest.newBuilder().setName("world!").build());
// ...
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nXg6eRk5-1575271682970)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AyGoLcDQ-1575271682970)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
二、高级配置
由于当前实现方式是直接集成了 gRPC-java SDK,因此很多配置还没有和 Dubbo 侧对齐,或者还没有以 Dubbo 的配置形式开放,因此,为了提供最大的灵活性,我们直接把 gRPC-java 的配置接口暴露了出来。
绝大多数场景下,你可能并不会用到以下扩展,因为它们更多的是对 gRPC 协议的拦截或者 HTTP/2 层面的配置。同时使用这些扩展点可能需要对 HTTP/2 或 gRPC 有基本的了解。
扩展点
目前支持的扩展点如下:
GrpcConfigurator 是最通用的扩展点,我们以此为例来说明一下,其基本定义如下:
public interface GrpcConfigurator {
// 用来定制 gRPC NettyServerBuilder
default NettyServerBuilder configureServerBuilder(NettyServerBuilder builder, URL url) {
return builder;
}
// 用来定制 gRPC NettyChannelBuilder
default NettyChannelBuilder configureChannelBuilder(NettyChannelBuilder builder, URL url) {
return builder;
}
// 用来定制 gRPC CallOptions, 定义某个服务在每次请求间传递数据
default CallOptions configureCallOptions(CallOptions options, URL url) {
return options;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rogtr8tU-1575271682971)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZjEGv6bQ-1575271682971)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
以下是一个示例扩展实现:
public class MyGrpcConfigurator implements GrpcConfigurator {
private final ExecutorService executor = Executors
.newFixedThreadPool(200, new NamedThreadFactory("Customized-grpc", true));
@Override
public NettyServerBuilder configureServerBuilder(NettyServerBuilder builder, URL url) {
return builder.executor(executor);
}
@Override
public NettyChannelBuilder configureChannelBuilder(NettyChannelBuilder builder, URL url)
{
return builder.flowControlWindow(10);
}
@Override
public CallOptions configureCallOptions(CallOptions options, URL url) {
return options.withOption(CallOptions.Key.create("key"), "value");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OD5F1ZUf-1575271682971)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YHrYfKD0-1575271682971)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
配置为 Dubbo SPI,`resources/META-INF/services 增加配置文件
default=org.apache.dubbo.samples.basic.comtomize.MyGrpcConfigurator
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pkRm3h4F-1575271682971)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6L0Y3jcU-1575271682971)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
private final ExecutorService executor = Executors
.newFixedThreadPool(200, new NamedThreadFactory("Customized-grpc", true));
public NettyServerBuilder configureServerBuilder(NettyServerBuilder builder, URL url)
{
return builder.executor(executor);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6VC6kCO0-1575271682972)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-arvJ3Bio-1575271682972)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
@Override
public NettyChannelBuilder configureChannelBuilder(NettyChannelBuilder builder, URL url)
{
return builder.flowControlWindow(10);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZAV4Yn4c-1575271682972)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6TIrYVcP-1575271682973)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
@Override
public CallOptions configureCallOptions(CallOptions options, URL url) {
if (url.getServiceInterface().equals("xxx.DemoService")) {
return options.withOption(CallOptions.Key.create("key"), "value");
} else {
return options;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SVi1jlkA-1575271682973)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-myMeENei-1575271682973)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
三、双向流式通信
代码中还提供了一个支持双向流式通信的示例,同时提供了拦截流式调用的 Interceptor 扩展示例实现。
* MyClientStreamInterceptor,工作在 client 端,拦截发出的请求流和接收的响应流
* MyServerStreamInterceptor,工作在 server 端,拦截收到的请求流和发出的响应流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7JZStTBF-1575271682974)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AgmndANM-1575271682974)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
四、TLS 配置
配置方式和 Dubbo 提供的通用的 TLS 支持一致,具体请参见 Dubbo 官方文档
下面,我们以一个具体的示例来看一下基于 Protobuf 的 Dubbo 服务开发流程。
通过标准 Protobuf 定义服务
syntax = "proto3";
option java_multiple_files = true;
option java_package = "org.apache.dubbo.demo";
option java_outer_classname = "DemoServiceProto";
option objc_class_prefix = "DEMOSRV";
package demoservice;
// The demo service definition.
service DemoService {
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;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GGsSsUkU-1575271682974)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uefPPaWd-1575271682974)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
这里定义了一个 DemoService 服务,服务只包含一个 sayHello 方法,同时定义了方法的入参和出参。
org.xolstice.maven.plugins
protobuf-maven-plugin
0.5.1
com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}
dubbo-grpc-java
org.apache.dubbo:protoc-gen-dubbo-java:1.19.0-SNAPSHOT:exe:${os.detected.classifier}
build/generated/source/proto/main/java
false
dubbo
compile
compile-custom
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QQ470f5l-1575271682975)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j3IjKJAT-1575271682975)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
注意,这里与 Dubbo 对 gRPC 支持部分的区别在于: dubbo
# 运行以下 maven 命令
$mvn clean compile
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PSoIyKc3-1575271682975)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4W5Y6L6P-1575271682975)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
生成的 Java 类如下:
DemoServiceDubbo 为 Dubbo 定制的 stub
public final class DemoServiceDubbo {
private static final AtomicBoolean registered = new AtomicBoolean();
private static Class> init() {
Class> clazz = null;
try {
clazz = Class.forName(DemoServiceDubbo.class.getName());
if (registered.compareAndSet(false, true)) {
org.apache.dubbo.common.serialize.protobuf.support.ProtobufUtils.marshaller(
org.apache.dubbo.demo.HelloRequest.getDefaultInstance());
org.apache.dubbo.common.serialize.protobuf.support.ProtobufUtils.marshaller(
org.apache.dubbo.demo.HelloReply.getDefaultInstance());
}
} catch (ClassNotFoundException e) {
// ignore
}
return clazz;
}
private DemoServiceDubbo() {}
public static final String SERVICE_NAME = "demoservice.DemoService";
/**
* Code generated for Dubbo
*/
public interface IDemoService {
static Class> clazz = init();
org.apache.dubbo.demo.HelloReply sayHello(org.apache.dubbo.demo.HelloRequest request);
java.util.concurrent.CompletableFuture sayHelloAsync(
org.apache.dubbo.demo.HelloRequest request);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8HdXxHBD-1575271682975)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gzss6mml-1575271682975)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
最值得注意的是 IDemoService 接口,它会作为 Dubbo 服务定义基础接口。
从这一步开始,所有开发流程就和直接定义 Java 接口一样了。实现接口定义业务逻辑。
public class DemoServiceImpl implements DemoServiceDubbo.IDemoService {
private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public HelloReply sayHello(HelloRequest request) {
logger.info("Hello " + request.getName() + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return HelloReply.newBuilder()
.setMessage("Hello " + request.getName() + ", response from provider: "
+ RpcContext.getContext().getLocalAddress())
.build();
}
@Override
public CompletableFuture sayHelloAsync(HelloRequest request) {
return CompletableFuture.completedFuture(sayHello(request));
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eITcygWZ-1575271682976)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pvL68JND-1575271682976)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
暴露 Dubbo 服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JC5cOsnQ-1575271682976)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-flC8bL7w-1575271682976)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
context.start();
System.in.read();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DuNZTmQ5-1575271682976)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9jLxBJkI-1575271682976)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
引用 Dubbo 服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4GcSHlw-1575271682977)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AtFmgSCP-1575271682977)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
context.start();
IDemoService demoService = context.getBean("demoService", IDemoService.class);
HelloRequest request = HelloRequest.newBuilder().setName("Hello").build();
HelloReply reply = demoService.sayHello(request);
System.out.println("result: " + reply.getMessage());
System.in.read();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TfQfWdJe-1575271682977)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-duKHykpL-1575271682977)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]
RPC 协议是实现微服务体系互通的核心组件,通常采用不同的微服务通信框架则意味着绑定某一个特定的协议,如 Spring Cloud 基于 HTTP、gRPC 提供 gRPC over HTTP/2、Thrift Hessian 等都是自定义私有协议。
Dubbo 自身同样提供了私有的 Dubbo 协议,这样你也能基于 Dubbo 协议构建微服务。但除了单一协议之外,和以上所有框架不同的,Dubbo 最大的优势在于它能同时支持多协议的暴露和消费,再配合 Dubbo 多注册订阅的模型,可以让 Dubbo 成为桥接多种不同协议的微服务体系的开发框架,轻松的实现不同微服务体系的互调互通或技术栈迁移。
这篇文章详细讲解了 Dubbo 对 gRPC 协议的支持,再加上 Dubbo 之前已具备的对 REST、Hessian、Thrift 等的支持,使 Dubbo 在协议互调上具备了基础。我们只需要在服务发现模型上也能实现和这些不同体系的打通,就能解决不同技术栈互调和迁移的问题。关于这部分的具体应用场景以及工作模式,我们将在接下来的文章中来具体分析。
**作者信息:**刘军,Github账号Chickenlj,Apache Dubbo PMC,项目核心维护者,见证了Dubbo从重启开源到Apache毕业的整个流程。现任职阿里巴巴中间件团队,参与服务框架、微服务相关工作,目前主要在推动Dubbo开源的云原生化。
原文链接
本文为云栖社区原创内容,未经允许不得转载。