天天看點

Dubbo 在跨語言和協定穿透性方向上的探索:支援 HTTP/2 gRPC 和 Protobuf

本文整理自劉軍在 Dubbo 成都 meetup 上分享的《Dubbo 在多語言和協定穿透性方向上的探索》。

本文總體上可分為基礎産品簡介、Dubbo 對 gRPC (HTTP/2) 和 Protobuf 的支援及示例示範三部分,在簡介部分介紹了 Dubbo、HTTP/2、gRPC、Protobuf 的基本概念和特點;第二部分介紹了 Dubbo 為何要支援 gRPC (HTTP/2) 和 Protobuf,以及這種支援為 gRPC 和 Dubbo 開發帶來的好處與不同;第三部分通過兩個執行個體分别示範了 Dubbo gRPC 和 Dubbo Protobuf 的使用方式。

基本介紹

Dubbo 協定

從協定層面展開,以下是目前 2.7 版本支援的 Dubbo 協定

Dubbo 在跨語言和協定穿透性方向上的探索:支援 HTTP/2 gRPC 和 Protobuf

衆所周知,Dubbo 協定是直接定義在 TCP 傳輸層協定之上,由于 TCP 高可靠全雙工的特點,為 Dubbo 協定的定義提供了最大的靈活性,但同時也正是因為這樣的靈活性,RPC 協定普遍都是定制化的私有協定,Dubbo 同樣也面臨這個問題。在這裡我們着重講一下 Dubbo 在協定通用性方面值得改進的地方,關于協定詳細解析請參見官網部落格

  • Dubbo 協定體 Body 中有一個可擴充的 attachments 部分,這給 RPC 方法之外額外傳遞附加屬性提供了可能,是一個很好的設計。但是類似的 Header 部分,卻缺少類似的可擴充 attachments,這點可參考 HTTP 定義的 Ascii Header 設計,将 Body Attachments 和 Header Attachments 做職責劃分。
  • Body 協定體中的一些 RPC 請求定位符如 Service Name、Method Name、Version 等,可以提到 Header 中,和具體的序列化協定解耦,以更好的被網絡基礎設施識别或用于流量管控。
  • 擴充性不夠好,欠缺協定更新方面的設計,如 Header 頭中沒有預留的狀态辨別位,或者像 HTTP 有專為協定更新或協商設計的特殊 packet。
  • 在 Java 版本的代碼實作上,不夠精簡和通用。如在鍊路傳輸中,存在一些語言綁定的内容;消息體中存在備援内容,如 Service Name 在 Body 和 Attachments 中都存在。

HTTP/1

相比于直接建構與 TPC 傳輸層的私有 RPC 協定,建構于 HTTP 之上的遠端調用解決方案會有更好的通用性,如WebServices 或 REST 架構,使用 HTTP + JSON 可以說是一個事實标準的解決方案。

之所有選擇建構在 HTTP 之上,我認為有兩個最大的優勢:

  1. HTTP 的語義和可擴充性能很好的滿足 RPC 調用需求。
  2. 通用性,HTTP 協定幾乎被網絡上的所有裝置所支援,具有很好的協定穿透性。
Dubbo 在跨語言和協定穿透性方向上的探索:支援 HTTP/2 gRPC 和 Protobuf

具體來說,HTTP/1 的優勢和限制是:

  • 典型的 Request – Response 模型,一個鍊路上一次隻能有一個等待的 Request 請求
  • HTTP/1 支援 Keep-Alive 連結,避免了連結重複建立開銷
  • Human Readable Headers,使用更通用、更易于人類閱讀的頭部傳輸格式
  • 無直接 Server Push 支援,需要使用 Polling Long-Polling 等變通模式

HTTP/2

HTTP/2 保留了 HTTP/1 的所有語義,在保持相容的同時,在通信模型和傳輸效率上做了很大的改進。

Dubbo 在跨語言和協定穿透性方向上的探索:支援 HTTP/2 gRPC 和 Protobuf
  • 支援單條鍊路上的 Multiplexing,相比于 Request - Response 獨占鍊路,基于 Frame 實作更高效利用鍊路
  • Request - Stream 語義,原生支援 Server Push 和 Stream 資料傳輸
  • Flow Control,單條 Stream 粒度的和整個鍊路粒度的流量控制
  • 頭部壓縮 HPACK
  • Binary Frame
  • 原生 TLS 支援

gRPC

上面提到了在 HTTP 及 TCP 協定之上建構 RPC 協定各自的優缺點,相比于 Dubbo 建構于 TPC 傳輸層之上,Google 選擇将 gRPC 直接定義在 HTTP/2 協定之上,關于 gRPC 的

設計願景

請參考以上兩篇文章,我這裡僅摘取 設計願景 中幾個能反映 gRPC 設計目的特性來做簡單說明。

  • Coverage & Simplicity,協定設計和架構實作要足夠通用和簡單,能運作在任何裝置之上,甚至一些資源首先的如 IoT、Mobile 等裝置。
  • Interoperability & Reach,要建構在更通用的協定之上,協定本身要能被網絡上幾乎所有的基礎設施所支援。
  • General Purpose & Performant,要在場景和性能間做好平衡,首先協定本身要是适用于各種場景的,同時也要盡量有高的性能。
  • Payload Agnostic,協定上傳輸的負載要保持語言和平台中立。
  • Streaming,要支援 Request - Response、Request - Stream、Bi-Steam 等通信模型。
  • Flow Control,協定自身具備流量感覺和限制的能力。
  • Metadata Exchange,在 RPC 服務定義之外,提供額外附加資料傳輸的能力。

總的來說,在這樣的設計理念指導下,gRPC 最終被設計為一個跨語言、跨平台的、通用的、高性能的、基于 HTTP/2 的 RPC 協定和架構。

Protobuf

Protocol buffers (Protobuf)

是 Google 推出的一個跨平台、語言中立的結構化資料描述和序列化的産品,它定義了一套結構化資料定義的協定,同時也提供了相應的

Compiler

工具,用來将語言中立的描述轉化為相應語言的具體描述。

它的一些特性包括:

  • 跨語言 跨平台,語言中立的資料描述格式,預設提供了生成多種語言的 Compiler 工具。
  • 安全性,由于反序列化的範圍和輸出内容格式都是 Compiler 在編譯時預生成的,是以繞過了類似 Java Deserialization Vulnarability 的問題。
  • 二進制 高性能
  • 強類型
  • 字段變更向後相容
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;
    }           

除了結構化資料描述之外,Protobuf 還支援定義 RPC 服務,它允許我們定義一個 .proto 的服務描述檔案,進而利用 Protobuf Compiler 工具生成特定語言和 RPC 架構的接口和 stub。後續将要具體講到的 gRPC + Protobuf、Dubbo-gRPC + Protobuf 以及 Dubbo + Protobuf 都是通過定制 Compiler 類實作的。

service SearchService {
    rpc Search (SearchRequest) returns (SearchResponse);
}           

Dubbo 所做的支援

跨語言的服務開發涉及到多個方面,從服務定義、RPC 協定到序列化協定都要做到語言中立,同時還針對每種語言有對應的 SDK 實作。雖然得益于社群的貢獻,現在 Dubbo 在多語言 SDK 實作上逐漸有了起色,已經提供了包括 Java, Go, PHP, C#, Python, NodeJs, C 等版本的用戶端或全量實作版本,但在以上提到的跨語言友好型方面,以上三點還是有很多可改進之處。

  • 協定,上面我們已經分析過 Dubbo 協定既有的缺點,如果能在 HTTP/2 之上建構應用層協定,則無疑能避免這些弊端,同時最大可能的提高協定的穿透性,避免網關等協定轉換元件的存在,更有利于鍊路上的流量管控。考慮到 gRPC 是建構在 HTTP/2 之上,并且已經是雲原生領域推薦的通信協定,Dubbo 在第一階段選擇了直接支援 gRPC 協定作為目前的 HTTP/2 解決方案。我們也知道 gRPC 架構自身的弊端在于易用性不足以及服務治理能力欠缺(這也是目前絕大多數廠商不會直接裸用 gRPC 架構的原因),通過将其內建進 Dubbo 架構,使用者可以友善的使用 Dubbo 程式設計模型 + Dubbo 服務治理 + gRPC 協定通信的組合。
  • 服務定義,目前 Dubbo 的服務定義和具體的程式設計語言綁定,沒有提供一種語言中立的服務描述格式,比如 Java 就是定義 Interface 接口,到了其他語言又得重新以另外的格式定義一遍。是以 Dubbo 通過支援 Protobuf 實作了語言中立的服務定義。
  • 序列化,Dubbo 目前支援的序列化包括 Json、Hessian2、Kryo、FST、Java 等,而這其中支援跨語言的隻有 Json、Hessian2,通用的 Json 有固有的性能問題,而 Hessian2 無論在效率還是多語言 SDK 方面都有所欠缺。為此,Dubbo 通過支援 Protobuf 序列化來提供更高效、易用的跨語言序列化方案。

示例

示例 1,使用 Dubbo 開發 gRPC 服務

gRPC 是 Google 開源的建構在 HTTP/2 之上的一個 PRC 通信協定。Dubbo 依賴其靈活的協定擴充機制,增加了對 gRPC (HTTP/2) 協定的支援。

目前的支援限定在 Dubbo Java 語言版本,後續 Go 語言或其他語言版本将會以類似方式提供支援。下面,通過一個簡單的

來示範如何在 Dubbo 中使用 gRPC 協定通信。

1. 定義服務 IDL

首先,通過标準的 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;
   }           

在此,我們定義了一個隻有一個方法 sayHello 的 Greeter 服務,同時定義了方法的入參和出參,

2. Protobuf Compiler 生成 Stub

  1. 定義 Maven Protobuf Compiler 插件工具。這裡我們擴充了 Protobuf 的 Compiler 工具,以用來生成 Dubbo 特有的 RPC stub,此目前以 Maven 插件的形式釋出。
<plugin>
  <groupId>org.xolstice.maven.plugins</groupId>
  <artifactId>protobuf-maven-plugin</artifactId>
  <version>0.5.1</version>
  <configuration>
    <protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}    
    </protocArtifact>
    <pluginId>dubbo-grpc-java</pluginId>
    <pluginArtifact>org.apache.dubbo:protoc-gen-dubbo-java:1.19.0-SNAPSHOT:exe:${os.detected.classifier}</pluginArtifact>
    <outputDirectory>build/generated/source/proto/main/java</outputDirectory>
    <clearOutputDirectory>false</clearOutputDirectory>
    <pluginParameter>grpc</pluginParameter>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>compile</goal>
        <goal>compile-custom</goal>
      </goals>
    </execution>
  </executions>
</plugin>           

其中,

pluginArtifact 指定了 Dubbo 定制版本的 Java Protobuf Compiler 插件,通過這個插件來在編譯過程中生成 Dubbo 定制版本的 gRPC stub。

<pluginArtifact>org.apache.dubbo:protoc-gen-dubbo-java:1.19.0-SNAPSHOT:exe:${os.detected.classifier}</pluginArtifact>           

由于 protoc-gen-dubbo-java 支援 gRPC 和 Dubbo 兩種協定,可生成的 stub 類型,預設值是 gRPC,關于 dubbo 協定的使用可參見 使用 Protobuf 開發 Dubbo 服務。

<pluginParameter>grpc</pluginParameter>           

2. 生成 Java Bean 和 Dubbo-gRPC stub

# 運作以下 maven 指令
$ mvn clean compile           

生成的 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<io.grpc.examples.helloworld.HelloReply> 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<io.grpc.examples.helloworld.HelloReply> responseObserver);

}           

3. 業務邏輯開發

繼承 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<HelloReply> 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();
  }
}           
  1. Provider 端暴露 Dubbo 服務

以 Spring XML 為例

<dubbo:application name="demo-provider"/>

<!-- 指定服務暴露協定為 gRPC -->
<dubbo:protocol id="grpc" name="grpc"/>

<dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>

<bean id="greeter" class="org.apache.dubbo.samples.basic.impl.GrpcGreeterImpl"/>

<!-- 指定 protoc-gen-dubbo-java 生成的接口 -->
<dubbo:service interface="io.grpc.examples.helloworld.GreeterGrpc$IGreeter" ref="greeter" protocol="grpc"/>           
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();
}           
  1. 引用 Dubbo 服務
<dubbo:application name="demo-consumer"/>

<dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>

<!-- 指定 protoc-gen-dubbo-java 生成的接口 -->
<dubbo:reference id="greeter" interface="io.grpc.examples.helloworld.GreeterGrpc$IGreeter" protocol="grpc"/>           
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();
}           

示例1附:進階用法

一、異步調用

再來看一遍 protoc-gen-dubbo-java 生成的接口:

/**
* Code generated for Dubbo
*/
public interface IGreeter {
  default public HelloReply sayHello(HelloRequest request) {
    // ......
  }
  default public ListenableFuture<HelloReply> sayHelloAsync(HelloRequest request) {
    // ......
  }
  public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver);
}           

這裡為 sayHello 方法生成了三種類型的重載方法,分别用于同步調用、異步調用和流式調用,如果消費端要進行異步調用,直接調用 sayHelloAsync() 即可:

public static void main(String[] args) throws IOException {
  // ...
  GreeterGrpc.IGreeter greeter = (GreeterGrpc.IGreeter) context.getBean("greeter");
  ListenableFuture<HelloReply> future =   
    greeter.sayHAsyncello(HelloRequest.newBuilder().setName("world!").build());
  // ...
}           

二、進階配置

由于目前實作方式是直接內建了 gRPC-java SDK,是以很多配置還沒有和 Dubbo 側對齊,或者還沒有以 Dubbo 的配置形式開放,是以,為了提供最大的靈活性,我們直接把 gRPC-java 的配置接口暴露了出來。

絕大多數場景下,你可能并不會用到以下擴充,因為它們更多的是對 gRPC 協定的攔截或者 HTTP/2 層面的配置。同時使用這些擴充點可能需要對 HTTP/2 或 gRPC 有基本的了解。

擴充點

目前支援的擴充點如下:

  • org.apache.dubbo.rpc.protocol.grpc.interceptors.ClientInterceptor
  • org.apache.dubbo.rpc.protocol.grpc.interceptors.GrpcConfigurator
  • org.apache.dubbo.rpc.protocol.grpc.interceptors.ServerInterceptor
  • org.apache.dubbo.rpc.protocol.grpc.interceptors.ServerTransportFilter

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;
  }
}           

以下是一個示例擴充實作:

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");
  }
}           

配置為 Dubbo SPI,`resources/META-INF/services 增加配置檔案

default=org.apache.dubbo.samples.basic.comtomize.MyGrpcConfigurator           
  1. 指定 Provider 端線程池

    預設用的是 Dubbo 的線程池,有 fixed (預設)、cached、direct 等類型。以下示範了切換為業務自定義線程池。

private final ExecutorService executor = Executors
             .newFixedThreadPool(200, new NamedThreadFactory("Customized-grpc", true));

public NettyServerBuilder configureServerBuilder(NettyServerBuilder builder, URL url) 
{
  return builder.executor(executor);
}           
  1. 指定 Consumer 端限流值

    設定 Consumer 限流值為 10

@Override
public NettyChannelBuilder configureChannelBuilder(NettyChannelBuilder builder, URL url)
{
  return builder.flowControlWindow(10);
}           
  1. 傳遞附加參數

    DemoService 服務調用傳遞 key

@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;
  }
}           

三、雙向流式通信

代碼中還提供了一個支援

雙向流式通信的示例

,同時提供了攔截流式調用的 Interceptor 擴充示例實作。

* MyClientStreamInterceptor,工作在 client 端,攔截發出的請求流和接收的響應流
* MyServerStreamInterceptor,工作在 server 端,攔截收到的請求流和發出的響應流
           

四、TLS 配置

配置方式和 Dubbo 提供的通用的 TLS 支援一緻,具體請參見 Dubbo 官方文檔或

示例 demo

五、使用 Reactive Stream 風格 RPC 程式設計

目前 Dubbo 支援 Reactor、RxJava 兩個 Reactive Stream library,具體使用請參見

Reactor 示例

RxJava 示例

示例 2, 使用 Protobuf 開發 Dubbo 服務

下面,我們以一個

具體的示例

來看一下基于 Protobuf 的 Dubbo 服務開發流程。

1. 定義服務

通過标準 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;
    }           

這裡定義了一個 DemoService 服務,服務隻包含一個 sayHello 方法,同時定義了方法的入參和出參。

2. Compiler 編譯服務

  1. 引入 Protobuf Compiler Maven 插件,同時指定 protoc-gen-dubbo-java RPC 擴充
<plugin>
  <groupId>org.xolstice.maven.plugins</groupId>
  <artifactId>protobuf-maven-plugin</artifactId>
  <version>0.5.1</version>
  <configuration>
    <protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}    
    </protocArtifact>
    <pluginId>dubbo-grpc-java</pluginId>
    <pluginArtifact>org.apache.dubbo:protoc-gen-dubbo-java:1.19.0-SNAPSHOT:exe:${os.detected.classifier}</pluginArtifact>
    <outputDirectory>build/generated/source/proto/main/java</outputDirectory>
    <clearOutputDirectory>false</clearOutputDirectory>
    <pluginParameter>dubbo</pluginParameter>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>compile</goal>
        <goal>compile-custom</goal>
      </goals>
    </execution>
  </executions>
</plugin>           

注意,這裡與 Dubbo 對 gRPC 支援部分的差別在于: dubbo

  1. 生成 RPC stub
# 運作以下 maven 指令
$mvn clean compile           

生成的 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<org.apache.dubbo.demo.HelloReply> sayHelloAsync(
   org.apache.dubbo.demo.HelloRequest request);

  }

}           

最值得注意的是 IDemoService 接口,它會作為 Dubbo 服務定義基礎接口。

3. 開發業務邏輯

從這一步開始,所有開發流程就和直接定義 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<HelloReply> sayHelloAsync(HelloRequest request) {
    return CompletableFuture.completedFuture(sayHello(request));
  }
}           

4. 配置 Provider

暴露 Dubbo 服務

<dubbo:application name="demo-provider"/>

<dubbo:registry address="zookeeper://127.0.0.1:2181"/>

<dubbo:protocol name="dubbo"/>

<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>

<dubbo:service interface="org.apache.dubbo.demo.DemoServiceDubbo$IDemoService" ref="demoService"/>           
public static void main(String[] args) throws Exception {
  ClassPathXmlApplicationContext context = 
    new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
  context.start();
  System.in.read();
}           

5. 配置 Consumer

<dubbo:application name="demo-consumer"/>

<dubbo:registry address="zookeeper://127.0.0.1:2181"/>

<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoServiceDubbo$IDemoService"/>           
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();
}           

總結與展望

HTTP/2 相比于 Dubbo/HSF 直接建構與 TCP 之上,無疑具有更好的穿透性和通用性,得益于網絡基礎設施的支援,HTTP 能友善的做到使用同一協定打通 Mobile、網關、Mesh、後端服務的整個鍊路。另外,HTTP/2 引入的 Stream、Flow-Control 等語義,也為 RPC 架構層實作 Reactive Stream、Back Pressure 提供了天然的傳輸層支援。目前 Dubbo 對 gRPC 的支援也是對未來 Dubbo/HSF 内置 HTTP/2 協定的一個探索。

RPC 協定是實作微服務體系互通的核心元件,通常采用不同的微服務通信架構則意味着綁定某一個特定的協定,如 Spring Cloud 基于 HTTP、gRPC 提供 gRPC over HTTP/2、Thrift Hessian 等都是自定義私有協定。

Dubbo 自身同樣提供了私有的 Dubbo 協定,這樣你也能基于 Dubbo 協定建構微服務。但除了單一協定之外,和以上所有架構不同的,Dubbo 最大的優勢在于它能同時支援多協定的暴露和消費,再配合 Dubbo 多注冊訂閱的模型,可以讓 Dubbo 成為橋接多種不同協定的微服務體系的開發架構,輕松的實作不同微服務體系的互調互通或技術棧遷移。

這篇文章詳細講解了 Dubbo 對 gRPC 協定的支援,再加上 Dubbo 之前已具備的對 REST、Hessian、Thrift 等的支援,使 Dubbo 在協定互調上具備了基礎。我們隻需要在服務發現模型上也能實作和這些不同體系的打通,就能解決不同技術棧互調和遷移的問題。關于這部分的具體應用場景以及工作模式,我們将在接下來的文章中來具體分析。