ダボの紹介
1. 前書き
Dubboは、AlibabaのオープンソースRPCおよびマイクロサービスフレームワークです。
特に、サービスガバナンスを強化し、従来のモノリスアプリケーションをスケーラブルな分散アーキテクチャにスムーズにリファクタリングできるようにします。
この記事では、Dubboとその最も重要な機能を紹介します。
2. 建築
ダボはいくつかの役割を区別します:
-
プロバイダー–サービスが公開されている場所。プロバイダーはサービスをレジストリに登録します
-
コンテナ-サービスが開始、ロード、実行される場所
-
消費者–リモートサービスを呼び出すユーザー。消費者はレジストリで必要なサービスにサブスクライブします
-
レジストリ-サービスが登録および検出される場所
-
モニター–特定の時間間隔におけるサービス呼び出しの頻度など、サービスの統計を記録します
プロバイダー、コンシューマー、およびレジストリー間の接続は永続的であるため、サービスプロバイダーがダウンするたびに、レジストリーは障害を検出し、コンシューマーに通知できます。
レジストリとモニターはオプションです。 消費者はサービスプロバイダーに直接接続できますが、システム全体の安定性が影響を受けます。
3. メーベン依存
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をレジストリに登録しましょう。 非常に便利な方法は、プロバイダーとコンシューマーの両方が同じローカルネットワーク上にある場合、マルチキャストレジストリを使用することです。
上記のBean構成では、GreetingsServiceをdubbo://127.0.0.1:20880の下のURLに公開し、<dubbo:registry />で指定されたマルチキャストアドレスにサービスを登録しました。
プロバイダーの構成では、アプリケーションメタデータ、公開するインターフェース、およびその実装をそれぞれ<dubbo:application />、<dubbo:service />、および<beans />で宣言しました。
dubboプロトコルは、フレームワークがサポートする多くのプロトコルの1つです。 これは、Java NIOノンブロッキング機能の上に構築されており、使用されるデフォルトのプロトコルです。
これについては、この記事の後半で詳しく説明します。
4.3. マルチキャストレジストリ–サービスコンシューマー
一般に、コンシューマーは呼び出すインターフェイスとリモートサービスのアドレスを指定する必要があり、それがまさにコンシューマーに必要なものです。
これですべての設定が完了しました。実際にどのように機能するかを見てみましょう。
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("example");
assertNotNull(hiMessage);
assertEquals("hi, example", hiMessage);
}
}
プロバイダーのremoteContextが開始すると、Dubboは自動的にGreetingsServiceをロードし、それを特定のレジストリーに登録します。 この場合、それはマルチキャストレジストリです。
コンシューマーはマルチキャストレジストリにサブスクライブし、コンテキスト内にGreetingsServiceのプロキシを作成します。 ローカルクライアントがsayHiメソッドを呼び出すと、リモートサービスが透過的に呼び出されます。
レジストリはオプションであり、コンシューマが公開されたポートを介してプロバイダに直接接続できることを説明しました。
基本的に、手順は従来のWebサービスに似ていますが、Dubboは単純で単純で軽量なものにします。
4.4. シンプルなレジストリ
「見えない」マルチキャストレジストリを使用する場合、レジストリサービスはスタンドアロンではないことに注意してください。 ただし、制限されたローカルネットワークにのみ適用されます。
管理可能なレジストリを明示的に設定するには、SimpleRegistryServiceを使用できます。
次のBean構成をSpringコンテキストにロードすると、単純なレジストリサービスが開始されます。
SimpleRegistryServiceクラスはアーティファクトに含まれていないため、source codeをGithubリポジトリから直接コピーしたことに注意してください。
次に、プロバイダーとコンシューマーのレジストリ構成を調整します。
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 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 reference = new ReferenceConfig<>();
reference.setApplication(application);
reference.setRegistry(registryConfig);
reference.setInterface(GreetingsService.class);
GreetingsService greetingsService = reference.get();
String hiMessage = greetingsService.sayHi("example");
上記のスニペットは、前のXML構成例のように機能しますが、もう少し簡単です。 とりあえず、Dubboを最大限に活用する場合は、XML構成が最初の選択肢になるはずです。
5. プロトコルサポート
フレームワークは、dubbo、RMI、hessian、HTTP、web service、thrift、memcached、%を含む複数のプロトコルをサポートします(t7)s。 dubboを除いて、ほとんどのプロトコルは見覚えがあります。 このプロトコルの新機能を見てみましょう。
dubboプロトコルは、プロバイダーとコンシューマーの間の永続的な接続を維持します。 長い接続とNIOノンブロッキングネットワーク通信により、小規模データパケット(<100K)を送信しながら、かなり優れたパフォーマンスが得られます。
ポート、コンシューマごとの接続数、最大許容接続数など、いくつかの構成可能なプロパティがあります。
Dubboは、さまざまなプロトコルを介したサービスの一括公開もサポートしています。
はい、上記のスニペットに示すように、異なるプロトコルを使用して異なるサービスを公開できます。 基礎となるトランスポーター、シリアル化の実装、およびネットワークに関連するその他の一般的なプロパティも構成可能です。
6. 結果のキャッシュ
ホットデータへのアクセスを高速化するために、ネイティブのリモート結果キャッシュがサポートされています。 Bean参照にキャッシュ属性を追加するのと同じくらい簡単です。
ここでは、使用頻度が最も低いキャッシュを構成しました。 キャッシュの動作を確認するために、以前の標準実装を少し変更します(これを「特別な実装」と呼びましょう)。
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("example");
long timeElapsed = System.currentTimeMillis() - before;
assertTrue(timeElapsed > 5000);
assertNotNull(hiMessage);
assertEquals("hi, example", hiMessage);
before = System.currentTimeMillis();
hiMessage = greetingsService.sayHi("example");
timeElapsed = System.currentTimeMillis() - before;
assertTrue(timeElapsed < 1000);
assertNotNull(hiMessage);
assertEquals("hi, example", hiMessage);
}
ここで、コンシューマは特別なサービス実装を呼び出しているため、呼び出しが最初に完了するまでに5秒以上かかりました。 再度呼び出すと、結果がキャッシュから返されるため、sayHiメソッドはほぼ即座に完了します。
スレッドローカルキャッシュとJCacheもサポートされていることに注意してください。
7. クラスターのサポート
Dubboは、負荷分散機能といくつかのフォールトトレランス戦略により、サービスを自由に拡大するのに役立ちます。 ここでは、クラスター内のサービスを管理するためのレジストリとしてZookeeperがあると仮定します。 プロバイダーは、次のようにZookeeperにサービスを登録できます。
POMに次の追加の依存関係が必要であることに注意してください。
org.apache.zookeeper
zookeeper
3.4.11
com.101tec
zkclient
0.10
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 elapseList = new ArrayList<>(6);
for (int i = 0; i < 6; i++) {
long current = System.currentTimeMillis();
String hiMessage = greetingsService.sayHi("example");
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 elapseList = new ArrayList<>(6);
for (int i = 0; i < 6; i++) {
long current = System.currentTimeMillis();
String hiMessage = greetingsService.sayHi("example");
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);
}
ロードバランサーは、コンシューマー側とプロバイダー側の両方で構成できることに注意してください。 コンシューマー側の構成の例を次に示します。
7.2. フォールトトレランス
Dubboでは、次のようないくつかのフォールトトレランス戦略がサポートされています。
-
フェイルオーバー
-
フェイルセーフ
-
フェイルファースト
-
フェイルバック
-
フォーク。
フェイルオーバーの場合、1つのプロバイダーに障害が発生すると、コンシューマーはクラスター内の他のサービスプロバイダーを試すことができます。
フォールトトレランス戦略は、サービスプロバイダーに対して次のように構成されます。
サービスのフェイルオーバーの実際を示すために、GreetingsServiceのフェイルオーバー実装を作成しましょう。
public class GreetingsFailoverServiceImpl implements GreetingsService {
@Override
public String sayHi(String name) {
return "hi, failover " + name;
}
}
特別なサービス実装GreetingsServiceSpecialImplは、リクエストごとに5秒間スリープすることを思い出してください。
2秒以上かかる応答がコンシューマーの要求失敗と見なされる場合、フェールオーバーシナリオがあります。
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("example");
assertNotNull(hiMessage);
assertEquals("hi, failover example", hiMessage);
}
8. 概要
このチュートリアルでは、Dubboを少し取りました。 ほとんどのユーザーは、そのシンプルさと豊富で強力な機能に惹かれます。
この記事で紹介したものとは別に、フレームワークには、パラメーターの検証、通知とコールバック、一般化された実装と参照、リモート結果のグループ化とマージ、サービスのアップグレードと下位互換性など、まだ調査されていない多くの機能がありますいくつか。
いつものように、完全な実装はover on Githubで見つけることができます。