天天看點

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

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 gRPCDubbo 在跨語言和協定穿透性方向上的探索:支援 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 gRPCDubbo 在跨語言和協定穿透性方向上的探索:支援 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 gRPCDubbo 在跨語言和協定穿透性方向上的探索:支援 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 官方文檔

示例 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();
}           

展望

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開源的雲原生化。