Exploration and practice of large-scale Spring Cloud microservices online and offline without loss

「一般的なリリースについて言えば、システムアプリケーションをクラウド上でリリースする際、再起動フェーズにより大量の OpenAPI 呼び出しが発生し、上流のビジネスリクエストの応答時間が大幅に増加し、タイムアウトによる障害に至ることもあります。ビジネスの発展に伴い、ユーザー数と呼び出し数が増加しており、システムは週 2 回という高い頻度でイテレーションを繰り返しています。リリース時のビジネスへの影響はますます無視できないものとなり、マイクロサービスのグレースフルシャットダウンのガバナンスはますます緊急性を増しています。」

クラウドネイティブアーキテクチャの発展により、マイクロサービスシステムには自動スケーリング、ローリングアップグレード、バッチリリースなどのネイティブ機能がもたらされ、リソース、コスト、安定性の最適なバランスを実現できています。しかし、アプリケーションのスケールダウンやリリースなどのプロセスにおいて、インスタンスの停止処理が十分に優雅ではないため、短期的なサービス停止を引き起こし、ビジネスモニタリングでは短時間に大量の IO 例外が報告されます。トランザクション処理が適切に行われていない場合、データの不整合が発生し、誤ったデータを手動で緊急修正する必要が生じることもあります。さらに、リリースのたびに停止通知を出し、ユーザーに利用不可期間を知らせる必要があり、開発チームにとって各リリースは懸念と負担の種となっています。

マイクロサービス停止時の損失問題の分析

API エラーを不必要に発生させないことが、最高のユーザー体験であり、最高のマイクロサービス開発体験です。マイクロサービス分野におけるこの難題をどのように解決すべきでしょうか。その前に、なぜマイクロサービスが停止時にトラフィックを失う可能性があるのかを理解しておきましょう。

上図に示すのは、マイクロサービスノードが停止する際の一連の正常なプロセスです。

1. 停止前、サービスコンシューマーは負荷分散ルールに従ってサービスプロバイダーを呼び出しており、ビジネスは正常に動作しています。

2. サービスプロバイダーのノード A が停止の準備をします。まず、ノードのうち 1 つに対して操作を行い、最初に Java プロセスの停止シグナルをトリガーします。

3. ノードが停止すると、サービスプロバイダーのノードはサービスレジストリに対してサービスノードの登録解除アクションを送信します。

4. サービスレジストリは、サービスプロバイダーのノードリストに変更があったというシグナルを受信した後、サービスコンシューマーに対してプロバイダーリスト内のノードが停止したことを通知します。

5. 新しいサービスプロバイダーのノードリストを受信した後、サービスコンシューマーはクライアントのアドレスリストキャッシュをリフレッシュし、新しいアドレスリストに基づいてルーティングと負荷分散を再計算します。

6. 最後に、サービスコンシューマーは停止中のノードを呼び出さなくなります。

マイクロサービスの停止プロセスは比較的複雑ですが、全体のプロセスは非常に論理的です。マイクロサービスアーキテクチャは、サービス登録とサービスディスカバリを通じてノード認識を実現しており、当然ながらこれがノードの停止変更を認識する方法でもあります。このプロセス自体に問題はありません。

ここで提供されるいくつかの簡単な実データを参照すると、見方が変わるかもしれません。ステップ 2 からステップ 6 まで、Eureka では最悪の場合 2 分かかり、Nacos でさえ最悪の場合 50 秒かかります。ステップ 3 では、Dubbo 3.0 以前のすべてのバージョンでサービスレベルの登録・ディスカバリモデルが使用されており、ビジネス量が大きすぎる場合、レジストリに大きな負荷がかかります。登録/解除アクションごとに 20〜30 ミリ秒かかると仮定すると、500〜600 のサービスが約 15 秒で登録/解除を行う必要があります。ステップ 5 では、Spring Cloud が使用する Ribbon ロードバランサーのデフォルトアドレスキャッシュリフレッシュ時間は 30 秒です。つまり、クライアントはレジストリからノード停止のシグナルをリアルタイムで受け取っても、クライアント側ではしばらくの間、古いノードにリクエストを負荷分散し続けてしまいます。

上図に示すように、クライアントがサーバーの停止を検知し、最新のアドレスリストを使用してルーティングと負荷分散を行う場合にのみ、リクエストが停止中のノードに負荷分散されなくなります。停止ノードの開始から、停止中のノードにリクエストが送信されなくなるまでの期間、ビジネスリクエストに問題が発生する可能性があります。この期間をサービスコールエラー発生期間と呼ぶことができます。

マイクロサービスアーキテクチャの下で、毎秒数万リクエストのトラフィックのピークに直面すると、サービスコールエラー発生期間がわずか数秒であっても、企業にとっては非常に辛いものです。さらに極端な場合、サービスコールエラー発生期間が数分にまで悪化し、多くの企業がリリースを躊躇するようになり、最終的には各リリースを午前 2〜3 時にスケジュールせざるを得なくなります。開発チームにとって、各リリースは恐怖と苦痛を伴うものです。

ロスレスオフライン技術

マイクロサービスの停止プロセスの分析を通じて、マイクロサービスの停止問題を解決する鍵は、各マイクロサービスの停止プロセスにおいてサービスコールエラー発生期間をできるだけ短縮し、かつそのノードに送信されたすべてのリクエストを処理し終えるまで停止中のノードが停止しないようにすることであると理解しました。

サービスコールエラー発生期間を短縮するにはどうすればよいでしょうか。いくつかの戦略を考案しました。

1. ステップ 3、すなわちレジストリへのノード登録解除プロセスを、ステップ 2 より前に前倒しします。つまり、サービス登録解除の通知をアプリケーションの停止前に実行させます。K8s が提供する Prestop インターフェイスを考慮し、このプロセスを抽象化して K8s の Prestop に配置してトリガーできます。

2. レジストリの容量が十分でない場合、レジストリをバイパスして、停止前に現在のサーバーノードの停止信号をクライアントに直接通知することが可能かどうか。このアクションも K8s の Prestop インターフェイスでトリガーできます。

3. クライアントがサーバーからの通知を受信した後、クライアントのアドレスリストキャッシュをアクティブにリフレッシュできるかどうか。

サーバーノードがそのノードに送信されたすべてのリクエストを処理し終えてから停止するにはどうすればよいでしょうか。サーバーの観点から、待機メカニズムを提供して、送信中のすべてのリクエストおよびサーバーで処理中のリクエストが、クライアントへの停止信号通知後に停止プロセスを開始する前に処理されるようにできるかどうか。

上図に示すように、上記の戦略を通じて、サービスコンシューマーができるだけ早い段階でサービスプロバイダーノードの停止動作をリアルタイムで検知できるようにします。同時に、サービスプロバイダーは停止前に送信中のすべてのリクエストと処理中のリクエストを確実に処理します。これらの考え方は適切そうです。次に、Spring Cloud と Dubbo のサービスフレームワークでどのように実装するかを見ていきましょう。

まず、サービスプロバイダーのプロセス内に HttpServer を構築して外部に停止インターフェイスを公開し、アクティブな登録解除の通知を受け取る必要があります。K8s の Prestop で curl http://localhost:20001/offline を実行し、アクティブ登録解除インターフェイスをトリガーするように設定できます。このインターフェイスはオフラインコマンドを受け取ると、レジストリ内の停止インスタンスインターフェイスの呼び出しをトリガーするか、マイクロサービスプログラム内の ServiceRegistration.stop インターフェイスを呼び出してサービス登録解除アクションを実行し、マイクロサービスを停止する前にレジストリへのノードアドレスの停止アクションを完了させます。

さらに、Prestop インターフェイスにアクティブ通知機能を実装する必要があります。Dubbo フレームワークでは、Dubbo 自体が長い接続モデルを採用しているため、実装は容易です。サービスプロバイダー内で、すべてのサービスコンシューマーに接続されたチャネルのコレクションが管理されていることがわかります。オフラインコマンドを受け取ると、管理中のすべてのチャネルに ReadOnly 信号を送信し、チャネルを読み取り専用にマークします。Dubbo コンシューマーは ReadOnly 信号を受け取ると、そのサービスプロバイダーにリクエストを送信しなくなり、アクティブ通知の効果が得られます。Spring Cloud フレームワークでも実装の考え方は同様です。Spring Cloud のリクエスト呼び出しにはチャネルモデルがないため、オフラインコマンドを受け取った後、リクエストのレスポンスヘッダーに ReadOnly タグを配置します。サービスコンシューマーは ReadOnly タグを受け取ると、負荷分散の Ribbon キャッシュをアクティブにリフレッシュし、停止プロセス中にこのサービスプロバイダーに新しいリクエストがアクセスしないようにします。

サービスプロバイダーは、送信中のすべてのリクエストが処理されるまでアプリケーション停止プロセスの続行を待つ必要があります。ビジネスの不確実性によりリクエストの処理時間は不確実です。サービスプロバイダーは、送信中のすべてのリクエストが処理されるまでどのくらい待つ必要があるでしょうか。この問題を解決するため、アダプティブウェイティング戦略を設計しました。アプリケーションに停止前のアダプティブな待機期間を設け、サービスプロバイダーに流入し呼び出しが完了したすべてのトラフィックを統計・計算します。このプロセス中、アプリケーションは現在のアプリケーションに流入したすべてのトラフィックが処理されるまで待機し、その後停止プロセスを開始します。

サービスの早期登録解除、アクティブ通知、アダプティブウェイティングの 3 つの戦略を通じて、マイクロサービスのロスレスオフライン機能を実現しました。これにより、マイクロサービスノードの停止プロセスにおける長いサービスエラー発生期間を回避し、デプロイプロセスにおけるビジネストラフィック損失の問題を解決しました。

大規模ロスレスオフラインの実践

ここまでのところ、上記の一連のソリューションと戦略は完璧に見えます。しかし、クラウドの顧客、特に大規模マイクロサービスシナリオに直面すると、ロスレスオフラインソリューションは実装プロセスでまだ多くの問題に遭遇します。ある顧客の本番環境の Spring Cloud アプリケーションを当社のソリューションに接続した後、デプロイプロセスで大量のエラーコード ServiceUnavailable が依然として発生しました。顧客と共に分析と調査を行った結果、問題の根本原因は、一部のサービスコンシューマーがサービスプロバイダーからの停止通知を時間内に受け取れなかったことでした。サーバーノードが既に停止しているにもかかわらず、停止中のサーバーノードにアクセスするトラフィックが依然として存在していました。大規模な環境では、レジストリによる通知の適時性は保証できません。また、「オフラインコマンドを受け取った後、リクエストの戻り値に ReadOnly タグを配置する」方法の適時性も保証できないことに気づきました。特に QPS が小さく、レスポンスタイムが長く、アプリケーションノード数が非常に多い場合、多くのコンシューマーがプロバイダーからの停止通知を受け取れません。各コンシューマーノードが ReadOnly タグを受け取ったログを確認したところ、多くのコンシューマーにログ記録がなく、これが私たちの推測を裏付けました。

プロアクティブ通知

大規模実践における問題を解決するため、よりリアルタイムで信頼性の高いアクティブ通知スキームが必要です。Spring Cloud のリクエスト呼び出しはチャネルレスモデルであることを考慮し、Spring Cloud サービスプロバイダー側で、最近このインスタンスを呼び出したサービスコンシューマーのアドレスリストを管理する必要があります。オフラインコマンドを受け取った後、サービスプロバイダーはメモリにキャッシュされたサービスコンシューマーのアドレスリストを走査し、各コンシューマーに対して GoAway HTTP 呼び出しを開始します。もちろん、サービスコンシューマー側で公開する HttpServer に GoAway 通知を受信するインターフェイスを追加する必要があります。サービスコンシューマーはこの呼び出しを受け取った後、現在のノードの負荷分散 Ribbon キャッシュをアクティブにリフレッシュし、フローのルーティングプロセス中で GoAway リクエストを送信したプロバイダーノードを分離し、現在のサービスコンシューマーが該当するプロバイダーノードにリクエストを送信しないようにします。プロバイダーが各コンシューマーノードに GoAway 呼び出しを行った後、サービスプロバイダーはすべてのアクティブなコンシューマーに「停止」信号を通知したことになります。これにより、大規模環境における比較的信頼性の高いアクティブ通知機能を実現しました。

可観測性の構築

ロスレス停止のプロセスは非常に複雑で、複数のノード間の通知メカニズムも関係しています。特に大規模な状況では、停止プロセスの完全性と信頼性の確認が非常に複雑で煩雑になります。停止プロセスに問題がないかを観察し、問題発生時に素早く問題と根本原因を特定するのに役立つ、完全な可観測性が必要です。

各リリースでデプロイされるアプリケーションのロスレスオフラインが有効かどうかをどのように判断するのでしょうか。

最も直感的な方法はサービスフローを確認することです。プロバイダーの視点に立って、プロバイダーが停止する前にサービスフローが停止し、このプロセス中にサービスフローが失われていないかどうかを確認する必要があります。これに基づき、プロバイダーノードのトラフィックを提供し、ロスレスオフラインイベントと関連付けるべきです。これにより、ロスレスオフラインプロセスがまずトリガーされ、その後ビジネストラフィックがなくなってからアプリケーションが停止されることを直感的に確認できます。

・メトリクストラフィックビュー

メトリクスの可観測性機能により、各 Pod のビジネストラフィックを統計・表示し、トラフィック実行中のロスレスオフラインイベントを関連付けることで、マイクロサービスノードの停止プロセスに問題がないかを一目で直感的に確認できます。

ロスレスオフラインのプロセス実装が期待通りかどうかをどのように判断するのでしょうか。

アクティブ通知のロジックによると、マイクロサービスノードの停止プロセス中に、各コンシューマーノードに対して GoAway 呼び出しを行う必要があります。大規模シナリオで、現在のアプリケーションに 5 つのコンシューマーアプリケーションがあり、各アプリケーションに 50 のノードがある場合、この 250 のコンシューマーすべてに GoAway が通知されることをどのように保証できるでしょうか。ロスレスオフラインプロセス自体が非常に複雑であり、特に大規模シナリオではロスレスオフラインの可観測性問題の複雑さが急激に上昇します。Tracing の力を借りてロスレスオフラインの可観測性を向上できると考えました。

・Tracing に基づくロスレスオフライン可観測性の新しいアイデア

上図に示すように、108 ノードのスケールダウン時に、アクティブ通知、サービス登録解除、アプリケーション停止などのステップを含む Tracing リンクを取得でき、各ステップで必要な情報を確認できます。

アクティブ通知フェーズでは、現在のプロバイダーノードがどのコンシューマーに対して GoAway リクエストを行ったかを確認できます。下図に示すように、10.0.0.90 と 10.0.0.176 の 2 つのコンシューマーノードにアクティブに通知しています。

コンシューマーは GoAway 呼び出しを受け取った後、負荷分散リストをリフレッシュしてルートを分離します。負荷分散アドレスリストに、現在のサービスに対して現在のサービスコンシューマーがキャッシュしている最新キャプチャのアドレスリストを表示します。下図では、アドレスリストにサービスプロバイダーノード 10.0.0.204 の呼び出しアドレスのみが残っていることを確認できます。

また、Spring Cloud が Nacos (レジストリ) に対して実行したオフラインサービスの呼び出し結果も確認でき、登録解除は成功しています。

ロスレスオフラインのワークフローを Tracing 構造に抽象化する戦略により、大規模シナリオや複雑なリンクにおけるロスレスオフライン問題のトラブルシューティングコストを削減し、大規模マイクロサービス停止時のロストレスラフィック問題をよりよく解決できることがわかりました。

まとめ

ソフトウェアイテレーションプロセスにおけるリスク管理に加え、マイクロサービス分野にはアプリケーションのオンライン・オフラインプロセスにおけるトラフィックガバナンスという共通の問題もあります。その目的も明確で、リリース、スケールアップ、再起動などのシナリオでアプリケーションがビジネストラフィックを一切失わないようにすることです。この背景の下、ロスレスオフライン技術が生まれました。変更プロセス中のビジネストラフィック損失の問題を解決し、トラフィックガバナンスシステムにおいて非常に重要な要素です。ロスレスオフライン機能はビジネストラフィックの安定性を効果的に確保し、マイクロサービス開発者の体験を向上させます。

MSE のロスレスオフライン機能も、顧客シナリオの充実とともに進化・改善を続けています。特筆すべきは、マイクロサービスガバナンスの実践プロセスにおいて、OpenSergo プロジェクトをオープンソース化し、マイクロサービスガバナンスを生産実践から標準へと推進することを目指している点です。興味のある方はぜひ議論と共同構築にご参加いただき、マイクロサービスガバナンスの未来を定義してください。

Related Articles

Explore More Special Offers

  1. Short Message Service(SMS) & Mail Service

    50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00

phone お問い合わせ
Hi, I'm Alibaba Cloud AI Assistant!
I can help with questions and solutions.