1. 序章

Dubbo は、AlibabaのオープンソースRPCおよびマイクロサービスフレームワークです。

特に、サービスガバナンスを強化し、従来のモノリスアプリケーションをスケーラブルな分散アーキテクチャにスムーズにリファクタリングできるようにします。

この記事では、Dubboとその最も重要な機能を紹介します。

2. 建築

ダボはいくつかの役割を区別しています。

  1. プロバイダー–サービスが公開されている場所。 プロバイダーはそのサービスをレジストリに登録します
  2. コンテナ–サービスが開始、ロード、実行される場所
  3. 消費者–リモートサービスを呼び出す人。 消費者はレジストリで必要なサービスに加入します
  4. レジストリ–サービスが登録および検出される場所
  5. 監視–サービスの統計を記録します。たとえば、特定の時間間隔でのサービス呼び出しの頻度などです。

(出典:http://dubbo.io/images/dubbo-architecture.png)

プロバイダー、コンシューマー、およびレジストリ間の接続は永続的であるため、サービスプロバイダーがダウンしているときはいつでも、レジストリは障害を検出してコンシューマーに通知できます。

レジストリとモニターはオプションです。 消費者はサービスプロバイダーに直接接続できますが、システム全体の安定性に影響があります。

3. Mavenの依存関係

飛び込む前に、pom.xmlに次の依存関係を追加しましょう。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.5.7</version>
</dependency>

最新バージョンはここにあります。

4. ブートストラップ

それでは、ダボの基本的な機能を試してみましょう。

これは低侵襲のフレームワークであり、その機能の多くは外部構成または注釈に依存しています。

Springコンテナ(現在はSpring 4.3.10)に依存しているため、XML構成ファイルを使用することが公式に推奨されています。

XML構成を使用して、その機能のほとんどを示します。

4.1. マルチキャストレジストリ–サービスプロバイダー

クイックスタートとして、必要なのはサービスプロバイダー、コンシューマー、および「非表示」レジストリのみです。 マルチキャストネットワークを使用しているため、レジストリは表示されません。

次の例では、プロバイダーはコンシューマーに「こんにちは」とだけ言います。

public interface GreetingsService {
    String sayHi(String name);
}

public class GreetingsServiceImpl implements GreetingsService {

    @Override
    public String sayHi(String name) {
        return "hi, " + name;
    }
}

リモートプロシージャコールを行うには、コンシューマーがサービスプロバイダーと共通のインターフェイスを共有する必要があるため、インターフェイスGreetingsServiceをコンシューマーと共有する必要があります。

4.2. マルチキャストレジストリ–サービス登録

GreetingsServiceをレジストリに登録しましょう。 非常に便利な方法は、プロバイダーとコンシューマーの両方が同じローカルネットワーク上にある場合にマルチキャストレジストリを使用することです。

<dubbo:application name="demo-provider" version="1.0"/>
<dubbo:registry address="multicast://224.1.1.1:9090"/>
<dubbo:protocol name="dubbo" port="20880"/>
<bean id="greetingsService" class="com.baeldung.dubbo.remote.GreetingsServiceImpl"/>
<dubbo:service interface="com.baeldung.dubbo.remote.GreetingsService"
  ref="greetingsService"/>

上記のBean構成で、 GreetingsService 下のURLにダボ://127.0.0.1:20880 で指定されたマルチキャストアドレスにサービスを登録しました

プロバイダーの構成では、アプリケーションメタデータ、公開するインターフェイス、およびその実装をそれぞれ宣言しました。

dubbo プロトコルは、フレームワークがサポートする多くのプロトコルの1つです。 Java NIOノンブロッキング機能の上に構築されており、使用されるデフォルトのプロトコルです。

これについては、この記事の後半で詳しく説明します。

4.3. マルチキャストレジストリ–サービスコンシューマ

一般に、コンシューマーは呼び出すインターフェイスとリモートサービスのアドレスを指定する必要があり、それがまさにコンシューマーに必要なものです。

<dubbo:application name="demo-consumer" version="1.0"/>
<dubbo:registry address="multicast://224.1.1.1:9090"/>
<dubbo:reference interface="com.baeldung.dubbo.remote.GreetingsService"
  id="greetingsService"/>

これですべての設定が完了しました。実際にどのように機能するかを見てみましょう。

public class MulticastRegistryTest {

    @Before
    public void initRemote() {
        ClassPathXmlApplicationContext remoteContext
          = new ClassPathXmlApplicationContext("multicast/provider-app.xml");
        remoteContext.start();
    }

    @Test
    public void givenProvider_whenConsumerSaysHi_thenGotResponse(){
        ClassPathXmlApplicationContext localContext 
          = new ClassPathXmlApplicationContext("multicast/consumer-app.xml");
        localContext.start();
        GreetingsService greetingsService
          = (GreetingsService) localContext.getBean("greetingsService");
        String hiMessage = greetingsService.sayHi("baeldung");

        assertNotNull(hiMessage);
        assertEquals("hi, baeldung", hiMessage);
    }
}

プロバイダーのremoteContextが起動すると、Dubboは自動的に GreetingsService をロードし、特定のレジストリに登録します。 この場合、それはマルチキャストレジストリです。

コンシューマーはマルチキャストレジストリにサブスクライブし、コンテキストでGreetingsServiceのプロキシを作成します。 ローカルクライアントがsayHiメソッドを呼び出すと、リモートサービスが透過的に呼び出されます。

レジストリはオプションであると述べました。つまり、コンシューマーは公開されたポートを介してプロバイダーに直接接続できます。

<dubbo:reference interface="com.baeldung.dubbo.remote.GreetingsService"
  id="greetingsService" url="dubbo://127.0.0.1:20880"/>

基本的に、手順は従来のWebサービスと似ていますが、Dubboを使用すると、わかりやすく、シンプルで軽量になります。

4.4. シンプルなレジストリ

「非表示」のマルチキャストレジストリを使用する場合、レジストリサービスはスタンドアロンではないことに注意してください。 ただし、制限されたローカルネットワークにのみ適用できます。

管理可能なレジストリを明示的に設定するには、SimpleRegistryServiceを使用できます。

次のBean構成をSpringコンテキストにロードした後、単純なレジストリサービスが開始されます。

<dubbo:application name="simple-registry" />
<dubbo:protocol port="9090" />
<dubbo:service interface="com.alibaba.dubbo.registry.RegistryService"
  ref="registryService" registry="N/A" ondisconnect="disconnect">
    <dubbo:method name="subscribe">
        <dubbo:argument index="1" callback="true" />
    </dubbo:method>
    <dubbo:method name="unsubscribe">
        <dubbo:argument index="1" callback="true" />
    </dubbo:method>
</dubbo:service>

<bean class="com.alibaba.dubbo.registry.simple.SimpleRegistryService"
  id="registryService" />

SimpleRegistryService クラスはアーティファクトに含まれていないため、ソースコードをGithubリポジトリから直接コピーしたことに注意してください。

次に、プロバイダーとコンシューマーのレジストリ構成を調整します。

<dubbo:registry address="127.0.0.1:9090"/>

SimpleRegistryService は、テスト時にスタンドアロンレジストリとして使用できますが、実稼働環境で使用することはお勧めしません。

4.5. Java構成

Java API、プロパティファイル、およびアノテーションを介した構成もサポートされています。 ただし、プロパティファイルとアノテーションは、アーキテクチャがそれほど複雑でない場合にのみ適用できます。

マルチキャストレジストリの以前のXML構成をAPI構成に変換する方法を見てみましょう。 まず、プロバイダーを次のように設定します。

ApplicationConfig application = new ApplicationConfig();
application.setName("demo-provider");
application.setVersion("1.0");

RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("multicast://224.1.1.1:9090");

ServiceConfig<GreetingsService> service = new ServiceConfig<>();
service.setApplication(application);
service.setRegistry(registryConfig);
service.setInterface(GreetingsService.class);
service.setRef(new GreetingsServiceImpl());

service.export();

サービスはマルチキャストレジストリを介してすでに公開されているので、ローカルクライアントで使用してみましょう。

ApplicationConfig application = new ApplicationConfig();
application.setName("demo-consumer");
application.setVersion("1.0");

RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("multicast://224.1.1.1:9090");

ReferenceConfig<GreetingsService> reference = new ReferenceConfig<>();
reference.setApplication(application);
reference.setRegistry(registryConfig);
reference.setInterface(GreetingsService.class);

GreetingsService greetingsService = reference.get();
String hiMessage = greetingsService.sayHi("baeldung");

上記のスニペットは、前のXML構成例のように魅力的に機能しますが、もう少し簡単です。 当面、Dubboを最大限に活用する場合は、XML構成を最初に選択する必要があります。

5. プロトコルサポート

フレームワークは、 dubbo RMI hessian HTTP Webサービスを含む複数のプロトコルをサポートします] thrift memcached redis dubbo を除いて、ほとんどのプロトコルは見覚えがあります。 このプロトコルの新機能を見てみましょう。

dubbo プロトコルは、プロバイダーとコンシューマー間の永続的な接続を維持します。 長い接続とNIOノンブロッキングネットワーク通信により、小規模なデータパケット(<100K)の送信中にかなり優れたパフォーマンスが得られます。

ポート、コンシューマーごとの接続数、最大許容接続数など、いくつかの構成可能なプロパティがあります。

<dubbo:protocol name="dubbo" port="20880"
  connections="2" accepts="1000" />

Dubboは、さまざまなプロトコルを介したサービスの公開も一度にサポートします。

<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />

<dubbo:service interface="com.baeldung.dubbo.remote.GreetingsService"
  version="1.0.0" ref="greetingsService" protocol="dubbo" />
<dubbo:service interface="com.bealdung.dubbo.remote.AnotherService"
  version="1.0.0" ref="anotherService" protocol="rmi" />

はい、上記のスニペットに示すように、さまざまなプロトコルを使用してさまざまなサービスを公開できます。 基盤となるトランスポーター、シリアル化の実装、およびネットワーキングに関連するその他の一般的なプロパティも構成可能です。

6. 結果のキャッシュ

ホットデータへのアクセスを高速化するために、ネイティブにリモートの結果キャッシュがサポートされています。 bean参照にキャッシュ属性を追加するのと同じくらい簡単です。

<dubbo:reference interface="com.baeldung.dubbo.remote.GreetingsService"
  id="greetingsService" cache="lru" />

ここでは、最も使用頻度の低いキャッシュを構成しました。 キャッシングの動作を確認するために、以前の標準実装を少し変更します(これを「特別な実装」と呼びましょう)。

public class GreetingsServiceSpecialImpl implements GreetingsService {
    @Override
    public String sayHi(String name) {
        try {
            SECONDS.sleep(5);
        } catch (Exception ignored) { }
        return "hi, " + name;
    }
}

プロバイダーを起動した後、コンシューマー側で、複数回呼び出したときに結果がキャッシュされることを確認できます。

@Test
public void givenProvider_whenConsumerSaysHi_thenGotResponse() {
    ClassPathXmlApplicationContext localContext
      = new ClassPathXmlApplicationContext("multicast/consumer-app.xml");
    localContext.start();
    GreetingsService greetingsService
      = (GreetingsService) localContext.getBean("greetingsService");

    long before = System.currentTimeMillis();
    String hiMessage = greetingsService.sayHi("baeldung");

    long timeElapsed = System.currentTimeMillis() - before;
    assertTrue(timeElapsed > 5000);
    assertNotNull(hiMessage);
    assertEquals("hi, baeldung", hiMessage);

    before = System.currentTimeMillis();
    hiMessage = greetingsService.sayHi("baeldung");
    timeElapsed = System.currentTimeMillis() - before;
 
    assertTrue(timeElapsed < 1000);
    assertNotNull(hiMessage);
    assertEquals("hi, baeldung", hiMessage);
}

ここでは、コンシューマーが特別なサービスの実装を呼び出しているため、呼び出しが最初に完了するまでに5秒以上かかりました。 再度呼び出すと、結果がキャッシュから返されるため、sayHiメソッドはほぼ即座に完了します。

スレッドローカルキャッシュとJCacheもサポートされていることに注意してください。

7. クラスターのサポート

Dubboは、負荷分散機能といくつかのフォールトトレランス戦略により、サービスを自由にスケールアップするのに役立ちます。 ここでは、クラスター内のサービスを管理するためのレジストリとしてZookeeperがあると仮定します。 プロバイダーは、次のようにZookeeperにサービスを登録できます。

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

POMには次の追加の依存関係が必要であることに注意してください。

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.11</version>
</dependency>
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

zookeeper依存関係およびzkclientの最新バージョンは、hereおよびhereにあります。

7.1. 負荷分散

現在、フレームワークはいくつかの負荷分散戦略をサポートしています。

  • ランダム
  • ラウンドロビン
  • 最もアクティブでない
  • コンシステントハッシュ。

次の例では、クラスター内のプロバイダーとして2つのサービス実装があります。 要求は、ラウンドロビンアプローチを使用してルーティングされます。

まず、サービスプロバイダーを設定しましょう。

@Before
public void initRemote() {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    executorService.submit(() -> {
        ClassPathXmlApplicationContext remoteContext 
          = new ClassPathXmlApplicationContext("cluster/provider-app-default.xml");
        remoteContext.start();
    });
    executorService.submit(() -> {
        ClassPathXmlApplicationContext backupRemoteContext
          = new ClassPathXmlApplicationContext("cluster/provider-app-special.xml");
        backupRemoteContext.start();
    });
}

これで、すぐに応答する標準の「高速プロバイダー」と、要求ごとに5秒間スリープする特別な「低速プロバイダー」ができました。

ラウンドロビン戦略で6回実行した後、平均応答時間は少なくとも2.5秒になると予想されます。

@Test
public void givenProviderCluster_whenConsumerSaysHi_thenResponseBalanced() {
    ClassPathXmlApplicationContext localContext
      = new ClassPathXmlApplicationContext("cluster/consumer-app-lb.xml");
    localContext.start();
    GreetingsService greetingsService
      = (GreetingsService) localContext.getBean("greetingsService");

    List<Long> elapseList = new ArrayList<>(6);
    for (int i = 0; i < 6; i++) {
        long current = System.currentTimeMillis();
        String hiMessage = greetingsService.sayHi("baeldung");
        assertNotNull(hiMessage);
        elapseList.add(System.currentTimeMillis() - current);
    }

    OptionalDouble avgElapse = elapseList
      .stream()
      .mapToLong(e -> e)
      .average();
    assertTrue(avgElapse.isPresent());
    assertTrue(avgElapse.getAsDouble() > 2500.0);
}

さらに、動的な負荷分散が採用されています。 次の例は、ラウンドロビン戦略では、新しいプロバイダーがオンラインになったときに、コンシューマーが新しいサービスプロバイダーを候補として自動的に選択することを示しています。

「低速プロバイダー」は、システムの起動から2秒後に登録されます。

@Before
public void initRemote() {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    executorService.submit(() -> {
        ClassPathXmlApplicationContext remoteContext
          = new ClassPathXmlApplicationContext("cluster/provider-app-default.xml");
        remoteContext.start();
    });
    executorService.submit(() -> {
        SECONDS.sleep(2);
        ClassPathXmlApplicationContext backupRemoteContext
          = new ClassPathXmlApplicationContext("cluster/provider-app-special.xml");
        backupRemoteContext.start();
        return null;
    });
}

コンシューマーは、1秒に1回リモートサービスを呼び出します。 6回実行した後、平均応答時間は1.6秒を超えると予想されます。

@Test
public void givenProviderCluster_whenConsumerSaysHi_thenResponseBalanced()
  throws InterruptedException {
    ClassPathXmlApplicationContext localContext
      = new ClassPathXmlApplicationContext("cluster/consumer-app-lb.xml");
    localContext.start();
    GreetingsService greetingsService
      = (GreetingsService) localContext.getBean("greetingsService");
    List<Long> elapseList = new ArrayList<>(6);
    for (int i = 0; i < 6; i++) {
        long current = System.currentTimeMillis();
        String hiMessage = greetingsService.sayHi("baeldung");
        assertNotNull(hiMessage);
        elapseList.add(System.currentTimeMillis() - current);
        SECONDS.sleep(1);
    }

    OptionalDouble avgElapse = elapseList
      .stream()
      .mapToLong(e -> e)
      .average();
 
    assertTrue(avgElapse.isPresent());
    assertTrue(avgElapse.getAsDouble() > 1666.0);
}

ロードバランサーは、コンシューマー側とプロバイダー側の両方で構成できることに注意してください。 コンシューマー側の構成の例を次に示します。

<dubbo:reference interface="com.baeldung.dubbo.remote.GreetingsService"
  id="greetingsService" loadbalance="roundrobin" />

7.2. フォールトトレランス

Dubboでは、次のようないくつかのフォールトトレランス戦略がサポートされています。

  • フェイルオーバー
  • フェイルセーフ
  • フェイルファスト
  • フェイルバック
  • フォーク。

フェールオーバーの場合、1つのプロバイダーに障害が発生すると、コンシューマーはクラスター内の他のサービスプロバイダーを試すことができます。

フォールトトレランス戦略は、サービスプロバイダーに対して次のように構成されます。

<dubbo:service interface="com.baeldung.dubbo.remote.GreetingsService"
  ref="greetingsService" cluster="failover"/>

サービスのフェイルオーバーの動作を示すために、GreetingsServiceのフェイルオーバー実装を作成しましょう。

public class GreetingsFailoverServiceImpl implements GreetingsService {

    @Override
    public String sayHi(String name) {
        return "hi, failover " + name;
    }
}

特別なサービスの実装GreetingsServiceSpecialImplはリクエストごとに5秒間スリープすることを思い出すことができます。

2秒を超える応答がコンシューマーの要求失敗と見なされる場合、フェイルオーバーシナリオがあります。

<dubbo:reference interface="com.baeldung.dubbo.remote.GreetingsService"
  id="greetingsService" retries="2" timeout="2000" />

2つのプロバイダーを開始した後、次のスニペットを使用してフェイルオーバーの動作を確認できます。

@Test
public void whenConsumerSaysHi_thenGotFailoverResponse() {
    ClassPathXmlApplicationContext localContext
      = new ClassPathXmlApplicationContext(
      "cluster/consumer-app-failtest.xml");
    localContext.start();
    GreetingsService greetingsService
      = (GreetingsService) localContext.getBean("greetingsService");
    String hiMessage = greetingsService.sayHi("baeldung");

    assertNotNull(hiMessage);
    assertEquals("hi, failover baeldung", hiMessage);
}

8. 概要

このチュートリアルでは、ダボを少し食べました。 ほとんどのユーザーは、そのシンプルさと豊富で強力な機能に魅了されています。

この記事で紹介したものとは別に、フレームワークには、パラメーターの検証、通知とコールバック、一般化された実装と参照、リモート結果のグループ化とマージ、サービスのアップグレードと下位互換性など、まだ検討されていない多くの機能があります。いくつか。

いつものように、完全な実装はGithubにあります。