(4)可用性を高めるデザインパターン
不調なサービスは「ブレーカーを落とす」ことで即時にエラーとする:Circuit Breaker(サーキットブレーカー)とHealth Check(死活監視)
各サービスへの呼び出しは、時に失敗します。その原因が、ネットワーク接続の瞬断や、短期的なビジー状態などによるものであれば、自動的に修正されることを期待し、単純リトライなどの仕組みに任せればよいでしょう。しかし、予期しない不具合や修正に時間がかかるバグが原因であった場合は、繰り返しリトライしたりタイムアウトさせる仕組みをそのまま適用してしまうと、問題をさらに増やしてしまう要因になりかねません。
例えば、ユーザーにとってみれば、何度やり直しても長時間待たされた上にエラーが返されるというストレスフルな状況にさらされてしまいます。またシステムにとっては、再試行の繰り返しによる高負荷により、サービス単体の欠陥からシステム全体への障害に発展する危険性もはらんでいます。
それらを防ぐための機構がCircuit Breakerです。呼び出しを失敗した数や頻度などをもとに閾値を設定しておき、それを超えたら「ブレーカーを落とす」という仕組みです(以降、そのサービスへのアクセスをさせずに手前でエラーを返す)。これにより、ユーザーは速やかにエラーを受け取ることができるようになり、システムはリトライによる負荷を軽減できます。
なお、ブレーカーの先にあるサービスに対しては、定期的に死活監視(Health Check)を行い、復帰を確認できたら通常の処理に戻します。
リソースを分離し障害の連鎖を避ける:Bulkhead(障壁)
ある特定のサービスに不具合が発生しただけで、システム全体が使用できなくなることがあります。一例としては、あるシステムが持つ大半の機能から呼び出されるようなサービスがある場合に、そのサービスがダウンしたケースがそれにあたるでしょう(一般に「単一障害点」と呼ばれる箇所)。その他の例としては、あるサービスが不具合により暴走してCPUリソースを過剰消費してしまい、その結果システム全体の処理に悪影響を及ぼすというケースもあります。
どちらの例も、システムを区分することで回避することができます。その手法は、船の浸水を一部にとどめるための機能になぞらえて、Bulkhead(障壁)と表現されました。
先の例の1つ目でいうと、単一障害点となっているサービスを物理的に別々のサーバーにコピーすることで冗長化し、呼び元によって使用先を振り分ければ、サーバーの違いが「障壁」となり一方のサーバーが故障しても他方を使う処理には影響がありません(先に説明した仮想化技術やService Registry / Service Discoveryを使えば、さらに構成は柔軟になります)。2つ目の例でいえば、サービスによって使用するCPUを特定させることで、その他のCPUリソースへの影響をとどめることができます。
(5)再利用性を高めるデザインパターン
クライアントから各サービスへの接続をとりまとめてサーバーサイドを隠蔽する:API Gateway(APIゲートウェイ)
マイクロサービスが適用されたシステムは機能ごとにサービス分割されます。そのため、Webブラウザやスマホアプリなどのクライアントがシステムに接続する場合、クライアントが指定した各機能に対応したサービスが呼び出されます。その際、クライアントが直接個別のサービスを呼び出す構成をとってしまうと、いくつかの課題が発生します。
例えば、サービスの構成を変更するようなリファクタリングを試みる場合は、サービスだけでなく各クライアント側も修正が必要となるケースが出てきます。また、クライアントが求める1つの処理が複数のサービスで実現される場合、ネットワークを隔てた機能間で複数回のやりとりが必要となり、性能劣化の一因となります。その他には、1つ1つのサービスを直接公開することになるため、個別に認証やSSLといったセキュリティ対応が必要となるなど、サービスが複雑化する要因ともなります。
それらの問題は、サーバー側にAPI Gatewayと呼ばれる機能を配置し、全てのクライアントはその機能を通してサービスにアクセスする構造とすることで解決できます。
まず、サービスのリファクタリングはAPI Gatewayに隠蔽された裏側で行われるため、クライアントへの影響はありません。また、複数回のサービス呼び出しが必要な場合でも、サーバー側に配置されたAPI Gatewayがそれを行うことで、クライアント・サーバ間のネットワーク処理の増加も避けられます。そして公開されるのはAPI Gatewayのみとなるため、セキュリティ対応などもそちらで一元化でき、各サービスは業務処理に専念できます。
他には、API Gatewayを採用することによりクライアント側の処理がシンプルになるため、新たなクライアントが増えた場合でも既存のサービスを再利用しやすくなるというメリットもあります。
クライアントごとにAPI Gatewayを分けることでその肥大化を防ぐ:Backends for Frontends(クライアントごとのバックエンド)
多数のクライアントからの処理を1つのAPI Gatewayで処理する構造にしてしまうと、個別のクライアント向けの処理がそこに集約され、結果としてその機能がモノリスのように肥大化してしまう場合があります。それでは、これまでに説明してきたマイクロサービスの利点の多くが失われてしまいます。
その対策として、個別のAPI Gatewayを作成するという考え方がBackends for Frontends(クライアントごとのバックエンド)です。略してBFFと呼ばれます。BFFを採用することにより、API Gatewayの持つ特長をそのまま活かしながら、その肥大化を避けることができます。
まとめ:細部を知り、改めてマイクロサービスを振り返る
マイクロサービスの利点として挙げた5つの要素と照らし合わせながら、代表的なデザインパターンを見てきました。かなりの情報量であったと思いますが、まずは少しずつ理解を深めていただければと思います。
余裕がある方は、全体をつかんだ後にぜひ改めて入門編を振り返ってみてください。マイクロサービスについて漠然と理解していたイメージが、個々の詳細を知った今、より明確な構造として把握できたことに気づけるはずです。この記事がそんな喜ばしい体験につながれば、わたしも嬉しく思います。
