編集者– この 7 部構成の記事シリーズはこれで完了です。
また、記事の完全なセットと、NGINX Plus を使用したマイクロサービスの実装に関する情報を電子書籍「マイクロサービス」としてダウンロードすることもできます。 設計から展開まで。 また、新しいマイクロサービス ソリューション ページもご覧ください。
これは、マイクロサービス アーキテクチャを使用したアプリケーションの構築に関するシリーズの 3 番目の記事です。 最初の記事では、マイクロサービス アーキテクチャ パターンを紹介し、それをモノリシック アーキテクチャ パターンと比較し、マイクロサービスを使用する利点と欠点について説明します。 2 番目の記事では、アプリケーションのクライアントがAPI ゲートウェイと呼ばれる仲介者を介してマイクロサービスと通信する方法について説明します。 この記事では、システム内のサービスが相互に通信する方法について説明します。 4 番目の記事では、サービス検出という密接に関連する問題について説明します。
モノリシック アプリケーションでは、コンポーネントは言語レベルのメソッドまたは関数呼び出しを介して相互に呼び出します。 対照的に、マイクロサービス ベースのアプリケーションは、複数のマシン上で実行される分散システムです。 各サービス インスタンスは通常、プロセスです。 したがって、次の図に示すように、サービスはプロセス間通信 (IPC) メカニズムを使用して対話する必要があります。
後ほど、特定の IPC テクノロジについて説明しますが、まずはさまざまな設計上の問題について検討してみましょう。
サービスに IPC メカニズムを選択するときは、まずサービスがどのように相互作用するかについて考えると役立ちます。 クライアントとサービスのやりとりのスタイルは多種多様です。 それらは 2 つの次元に沿って分類できます。 最初の次元は、相互作用が 1 対 1 か 1 対多かです。
2 番目の次元は、インタラクションが同期か非同期かです。
次の表は、さまざまなインタラクション スタイルを示しています。
1対1 | 1対多 | |
---|---|---|
同期 | リクエスト/レスポンス | — |
非同期 | 通知 | 公開/購読 |
リクエスト/非同期レスポンス | 公開/非同期応答 |
1 対 1 のインタラクションには次の種類があります。
1 対多の相互作用には次の種類があります。
各サービスでは通常、これらのインタラクション スタイルの組み合わせが使用されます。 一部のサービスでは、単一の IPC メカニズムで十分です。 他のサービスでは、IPC メカニズムの組み合わせを使用する必要がある場合があります。 次の図は、ユーザーが乗車をリクエストしたときに、タクシー配車アプリケーションのサービスがどのように相互作用するかを示しています。
サービスでは、通知、要求/応答、および公開/サブスクライブの組み合わせを使用します。 たとえば、乗客のスマートフォンが乗車管理サービスに通知を送信して、ピックアップをリクエストします。 旅行管理サービスは、リクエスト/レスポンスを使用して乗客サービスを呼び出すことにより、乗客のアカウントがアクティブであることを確認します。 次に、Trip Management サービスは旅行を作成し、パブリッシュ/サブスクライブを使用して、利用可能なドライバーを検索する Dispatcher などの他のサービスに通知します。
インタラクション スタイルについて説明したので、次は API を定義する方法を見てみましょう。
サービスの API は、サービスとそのクライアント間の契約です。 IPC メカニズムの選択に関係なく、何らかのインターフェース定義言語 (IDL) を使用してサービスの API を正確に定義することが重要です。 サービスを定義する際にAPI ファーストのアプローチを使用するという説得力のある議論もあります。 サービスの開発は、インターフェース定義を記述し、クライアント開発者とそれを確認することから始めます。 API 定義を反復処理した後にのみ、サービスを実装します。 事前にこの設計を行うことで、顧客のニーズを満たすサービスを構築できる可能性が高まります。
この記事の後半で説明するように、API 定義の性質は、使用している IPC メカニズムによって異なります。 メッセージングを使用している場合、API はメッセージ チャネルとメッセージ タイプで構成されます。 HTTP を使用している場合、API は URL とリクエストおよびレスポンスの形式で構成されます。 後ほど、いくつかの IDL について詳しく説明します。
サービスの API は時間の経過とともに必ず変化します。 モノリシック アプリケーションでは、通常、API を変更してすべての呼び出し元を更新するのは簡単です。 マイクロサービス ベースのアプリケーションでは、API のすべてのコンシューマーが同じアプリケーション内の他のサービスであっても、それははるかに困難になります。 通常、すべてのクライアントをサービスに合わせて強制的にアップグレードすることはできません。 また、サービスの古いバージョンと新しいバージョンの両方が同時に実行されるように、サービスの新しいバージョンを段階的に展開する可能性もあります。 これらの問題に対処するための戦略を持つことが重要です。
API の変更をどのように処理するかは、変更の規模によって異なります。 一部の変更は軽微であり、以前のバージョンと下位互換性があります。 たとえば、リクエストやレスポンスに属性を追加することができます。 堅牢性の原則を遵守するようにクライアントとサービスを設計することは理にかなっています。 古い API を使用するクライアントは、引き続き新しいバージョンのサービスを使用する必要があります。 サービスは不足しているリクエスト属性に対してデフォルト値を提供し、クライアントは余分なレスポンス属性を無視します。 API を簡単に進化させることができる IPC メカニズムとメッセージング形式を使用することが重要です。
ただし、場合によっては、API に互換性のない大きな変更を加えなければならないことがあります。クライアントに直ちにアップグレードを強制することはできないため、サービスは一定期間、古いバージョンの API をサポートする必要があります。 REST などの HTTP ベースのメカニズムを使用している場合、1 つの方法は、URL にバージョン番号を埋め込むことです。 各サービス インスタンスは複数のバージョンを同時に処理する場合があります。 あるいは、それぞれ特定のバージョンを処理する異なるインスタンスをデプロイすることもできます。
たとえば、その記事の製品詳細シナリオを考えてみましょう。 推奨サービスが応答していないと想像してみましょう。 クライアントの単純な実装では、応答を待機して無期限にブロックされる可能性があります。 これにより、ユーザー エクスペリエンスが低下するだけでなく、多くのアプリケーションではスレッドなどの貴重なリソースが消費されることになります。 最終的には、次の図に示すように、ランタイムのスレッドが不足し、応答しなくなります。
この問題を防ぐには、部分的な障害を処理できるようにサービスを設計することが重要です。
従うべき良いアプローチは、 Netflix が説明しているものです。 部分的な障害に対処するための戦略は次のとおりです。
Netflix Hystrixは、これらおよびその他のパターンを実装するオープンソース ライブラリです。 JVM を使用している場合は、必ず Hystrix の使用を検討してください。 また、JVM 以外の環境で実行している場合は、同等のライブラリを使用する必要があります。
選択できる IPC テクノロジーは多数あります。 サービスは、HTTP ベースの REST や Thrift などの同期要求/応答ベースの通信メカニズムを使用できます。 あるいは、AMQP や STOMP などの非同期のメッセージベースの通信メカニズムを使用することもできます。 メッセージの形式も多種多様です。 サービスでは、JSON や XML などの人間が読めるテキストベースの形式を使用できます。 あるいは、Avro や Protocol Buffers などのバイナリ形式 (より効率的) を使用することもできます。 同期 IPC メカニズムについては後ほど説明しますが、まずは非同期 IPC メカニズムについて説明しましょう。
メッセージングを使用する場合、プロセスは非同期的にメッセージを交換して通信します。 クライアントはメッセージを送信してサービスにリクエストを行います。 サービスが応答することが予想される場合は、別のメッセージをクライアントに送り返して応答します。 通信は非同期であるため、クライアントは応答を待ってブロックしません。 代わりに、クライアントは応答がすぐに受信されないことを前提として記述されています。
メッセージは、ヘッダー (送信者などのメタデータ) とメッセージ本文で構成されます。 メッセージはチャネルを介して交換されます。 任意の数のプロデューサーがチャネルにメッセージを送信できます。 同様に、任意の数のコンシューマーがチャネルからメッセージを受信できます。 チャネルには、ポイントツーポイントとパブリッシュサブスクライブの 2 種類があります。 ポイントツーポイント チャネルは、チャネルから読み取っているコンシューマーの 1 つにのみメッセージを配信します。 サービスは、前述の 1 対 1 の対話スタイルにポイントツーポイント チャネルを使用します。 パブリッシュ サブスクライブ チャネルは、接続されているすべてのコンシューマーに各メッセージを配信します。 サービスは、上記の 1 対多の対話スタイルにパブリッシュ サブスクライブ チャネルを使用します。
次の図は、タクシー配車アプリケーションがパブリッシュ サブスクライブ チャネルをどのように使用するかを示しています。
旅行管理サービスは、パブリッシュ サブスクライブ チャネルに旅行作成メッセージを書き込むことで、ディスパッチャなどの関係するサービスに新しい旅行について通知します。 ディスパッチャは利用可能なドライバーを見つけ、Driver Proposed メッセージをパブリッシュ サブスクライブ チャネルに書き込むことで他のサービスに通知します。
選択できるメッセージング システムは多数あります。 さまざまなプログラミング言語をサポートしているものを選択する必要があります。 一部のメッセージング システムは、AMQP や STOMP などの標準プロトコルをサポートしています。 他のメッセージング システムには、独自の文書化されたプロトコルがあります。 RabbitMQ 、 Apache Kafka 、 Apache ActiveMQ 、 NSQなど、選択できるオープンソース メッセージング システムは多数あります。 大まかに言えば、それらはすべて何らかの形式のメッセージとチャネルをサポートしています。 いずれも信頼性、パフォーマンス、拡張性の向上を目指しています。 ただし、各ブローカーのメッセージング モデルの詳細には大きな違いがあります。
メッセージングを使用すると多くの利点があります。
ただし、メッセージングの使用にはいくつかの欠点があります。
ここまでメッセージングベースの IPC の使用について説明してきましたが、次はリクエスト/レスポンスベースの IPC について見ていきましょう。
同期の要求/応答ベースの IPC メカニズムを使用する場合、クライアントはサービスに要求を送信します。 サービスはリクエストを処理し、応答を返します。 多くのクライアントでは、リクエストを行うスレッドは応答を待機している間ブロックされます。 他のクライアントは、おそらく Futures または Rx Observables によってカプセル化された非同期のイベント駆動型クライアント コードを使用する場合があります。 ただし、メッセージングを使用する場合とは異なり、クライアントは応答がタイムリーに到着することを想定します。 選択できるプロトコルは多数あります。 2 つの一般的なプロトコルは REST と Thrift です。 まずRESTについて見てみましょう。
今日では、 RESTfulスタイルで API を開発することが流行しています。 REST は、(ほとんどの場合) HTTP を使用する IPC メカニズムです。 REST の重要な概念はリソースであり、通常は顧客や製品などのビジネス オブジェクト、またはビジネス オブジェクトのコレクションを表します。 REST は、URL を使用して参照されるリソースを操作するために HTTP 動詞を使用します。 たとえば、 GET
リクエストはリソースの表現を返します。これは XML ドキュメントまたは JSON オブジェクトの形式である場合があります。 POST
リクエストは新しいリソースを作成し、 PUT
リクエストはリソースを更新します。 REST の創始者である Roy Fielding 氏の言葉を引用します。
次の図は、タクシー配車アプリケーションが REST を使用する方法の 1 つを示しています。
乗客のスマートフォンは、旅行管理サービスの/trips
リソースにPOST
リクエストを送信して旅行をリクエストします。 このサービスは、乗客に関する情報のGET
リクエストを乗客管理サービスに送信してリクエストを処理します。 乗客が旅行を作成する権限を持っていることを確認した後、旅行管理サービスは旅行を作成し、201
スマートフォンへの応答。
多くの開発者は、HTTP ベースの API が RESTful であると主張しています。 しかし、Fielding 氏がこのブログ投稿で説明しているように、実際にはすべてがそうであるわけではありません。 Leonard Richardson (関係はありません) は、次のレベルで構成されるREST の非常に便利な成熟モデルを定義しています。
POST
リクエストを送信してサービスを呼び出します。 各リクエストでは、実行するアクション、アクションのターゲット (ビジネス オブジェクトなど)、およびパラメーターを指定します。POST
リクエストを作成します。得る
取得する、 役職
作成し、 置く
更新します。 リクエスト クエリ パラメータと本文 (存在する場合) は、アクションのパラメータを指定します。 これにより、サービスはGET
リクエストのキャッシュなどの Web インフラストラクチャを活用できるようになります。GET
リクエストによって返されるリソースの表現に、そのリソースに対して許可されるアクションを実行するためのリンクが含まれているというものです。 たとえば、クライアントは、注文を取得するために送信されたGET
リクエストへの応答として返された Order 表現内のリンクを使用して注文をキャンセルできます。 HATEOAS の利点としては、URL をクライアント コードにハードコードする必要がなくなることが挙げられます。 もう 1 つの利点は、リソースの表現に許可されるアクションへのリンクが含まれているため、クライアントが現在の状態のリソースに対して実行できるアクションを推測する必要がないことです。HTTP ベースのプロトコルを使用すると、次のような多くの利点があります。
curl を
使用してコマンドラインから (JSON またはその他のテキスト形式が使用されていると想定) HTTP API をテストできます。HTTP の使用にはいくつかの欠点があります。
開発者コミュニティは最近、RESTful API のインターフェース定義言語の価値を再発見しました。 RAMLやSwaggerなど、いくつかのオプションがあります。 Swagger などの一部の IDL では、要求メッセージと応答メッセージの形式を定義できます。 RAML などの他の言語では、 JSON Schemaなどの別の仕様を使用する必要があります。 IDL には通常、API を記述するだけでなく、インターフェース定義からクライアント スタブとサーバー スケルトンを生成するツールがあります。
Apache Thrift は、 REST の興味深い代替手段です。 これは、言語間RPCクライアントとサーバーを作成するためのフレームワークです。 Thrift は、API を定義するための C スタイルの IDL を提供します。 Thrift コンパイラを使用して、クライアント側のスタブとサーバー側のスケルトンを生成します。 コンパイラは、C++、Java、Python、PHP、Ruby、Erlang、Node.js など、さまざまな言語のコードを生成します。
Thrift インターフェースは 1 つ以上のサービスで構成されます。 サービス定義は Java インターフェースに類似しています。 厳密に型指定されたメソッドのコレクションです。 Thrift メソッドは、(おそらく void の) 値を返すことも、一方向として定義することもできます。 値を返すメソッドは、リクエスト/レスポンス形式の対話を実装します。 クライアントは応答を待機し、例外をスローする可能性があります。 一方向の方法は、通知スタイルの対話に対応します。 サーバーは応答を送信しません。
Thrift はさまざまなメッセージ形式をサポートしています。 JSON、バイナリ、コンパクトバイナリ。 バイナリは JSON よりもデコードが速いため効率的です。 名前が示すように、コンパクト バイナリはスペース効率に優れた形式です。 もちろん、JSON は人間にもブラウザにも優しいものです。 Thrift では、生の TCP や HTTP などのトランスポート プロトコルも選択できます。 生の TCP は HTTP よりも効率的である可能性があります。 ただし、HTTP はファイアウォール、ブラウザ、そして人間にとって使いやすいものです。
HTTP と Thrift について見てきたので、次はメッセージ形式の問題について検討してみましょう。 メッセージング システムまたは REST を使用している場合は、メッセージ形式を選択できます。 Thrift などの他の IPC メカニズムでは、少数のメッセージ形式、おそらく 1 つのメッセージ形式のみがサポートされる可能性があります。 どちらの場合でも、言語間メッセージ形式を使用することが重要です。 現在マイクロサービスを単一の言語で作成している場合でも、将来的には他の言語を使用することになるでしょう。
メッセージ形式には、テキストとバイナリの 2 種類があります。 テキストベースの形式の例としては、JSON や XML などがあります。 これらの形式の利点は、人間が読めるだけでなく、自己記述的であることです。 JSON では、オブジェクトの属性は名前と値のペアのコレクションによって表されます。 同様に、XML では属性は名前付き要素と値によって表されます。 これにより、メッセージの消費者は関心のある値を選択し、残りを無視することができます。 したがって、メッセージ形式に対する小さな変更は簡単に下位互換性を保つことができます。
XML ドキュメントの構造は、 XML スキーマによって指定されます。 時間が経つにつれて、開発者コミュニティは JSON にも同様のメカニズムが必要であることに気付きました。 1 つのオプションは、 JSON スキーマをスタンドアロンまたは Swagger などの IDL の一部として使用することです。
テキストベースのメッセージ形式を使用する場合の欠点は、特に XML の場合、メッセージが冗長になる傾向があることです。 メッセージは自己記述型であるため、すべてのメッセージには属性の値に加えて属性の名前が含まれます。 もう 1 つの欠点は、テキストを解析する際のオーバーヘッドです。 したがって、バイナリ形式の使用を検討することをお勧めします。
選択できるバイナリ形式はいくつかあります。 Thrift RPC を使用している場合は、バイナリ Thrift を使用できます。 メッセージ形式を選択する場合、一般的なオプションとしてはProtocol BuffersやApache Avroなどがあります。 これら両方の形式では、メッセージの構造を定義するための型指定された IDL が提供されます。 ただし、1 つの違いは、プロトコル バッファーはタグ付けされたフィールドを使用するのに対し、Avro コンシューマーはメッセージを解釈するためにスキーマを知る必要があることです。 その結果、API の進化は Avro よりも Protocol Buffers の方が簡単になります。 このブログ投稿は、 Thrift、Protocol Buffers、Avro の優れた比較です。
マイクロサービスは、プロセス間通信メカニズムを使用して通信する必要があります。 サービスの通信方法を設計する際には、サービス間のやり取りの方法、各サービスの API の指定方法、API の進化方法、部分的な障害の処理方法など、さまざまな問題を考慮する必要があります。 マイクロサービスが使用できる IPC メカニズムには、非同期メッセージングと同期要求/応答の 2 種類があります。 シリーズの次の記事では、マイクロサービス アーキテクチャにおけるサービス検出の問題について見ていきます。
編集者– この 7 部構成の記事シリーズはこれで完了です。
また、記事の完全なセットと、NGINX Plus を使用したマイクロサービスの実装に関する情報を電子書籍「マイクロサービス」としてダウンロードすることもできます。 設計から展開まで。
ゲストブロガーの Chris Richardson 氏は、Amazon EC2 向けの初期の Java PaaS (Platform as a Service) であるオリジナルのCloudFoundry.comの創設者です。 彼は現在、アプリケーションの開発および展開方法の改善について組織にコンサルティングを行っています。 彼はまた、 http://microservices.ioでマイクロサービスに関するブログを定期的に書いています。
「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"