An Efficient Solution to Java Dependency Conflicts

その 1: 概要

Alimama Alliance チームの業務特性により、システムは膨大な外部依存を抱えており、60 から 70 のチームのサービスと N 個のツールコンポーネントに依存しています。本記事では、複雑な依存関係の効果的なガバナンスについて蓄積した経験を共有します。単純な技術スキルのまとめに加え、この分野のアーキテクチャに関する考察も議論します。本記事が Java の依存関係の競合による問題を体系的かつ徹底的に解決する一助となれば幸いです。

その 2: 依存関係の競合の本質的な原因

依存関係の競合を解決するには、まず Java の依存関係の競合の本質的な原因を理解する必要があります。

上の図はその例です。現在、Ali のほとんどの Java プロジェクトは Maven プロジェクトです。開発からオンラインまで、このようなプロジェクトは次の 2 つの重要なステップを経る必要があります。

1 コンパイルとパッケージング

通常、アプリケーションコードを記述する際、Maven を使用してアプリケーションコードをコンパイルする場合、Maven はアプリケーションコードのコンパイルを完了するために第一レベルの jar パッケージのみに依存します。調停結果は対応する jar をダウンロードして指定されたディレクトリに配置します(たとえば Y です。これは同じではないため、Maven 調停のカテゴリには属しません)。

注意すべき点として、異なる Maven バージョン間には差異がある可能性があり、これによりローカル環境と日次およびプレリリースのパッケージング間でアプリケーションロジックのパフォーマンスが一貫しない場合があります(この状況には他の理由もある可能性があり、必ずしも Maven バージョンの不整合が調停結果の不整合を引き起こすとは限らないことを説明しておきます)。

2 オンラインリリース

まず概念を明確にしましょう。JVM では、型インスタンスはその完全修飾クラス名とそれをロードするクラスローダー(ClassLoader)インスタンスによって一意に決定されます。したがって、「クラス分離」とは、実際には分離が必要なクラスを異なるクラスローダーインスタンスを通じてロードすることで実現されます。これにより、完全修飾クラス名が同じでも内容が異なる 2 つのクラスでも、クラスローダーインスタンスが異なれば、コンテナプロセス内で共存でき、互いに干渉せずに独立して実行できます。

コンテナを公開して起動する際、Tomcat、taobao-tomcat、PandoraBoot、またはその他のコンテナのいずれであっても、まずコンテナ自体が依存する jar パッケージが特定のクラスローダーインスタンスでロードされ、コンテナには通常複数のクラスローダーインスタンスがあります。コンテナが依存する jar パッケージは通常、アプリケーションパッケージとの絶対的な分離を実現するために特別なクラスローダーインスタンスによってロードされます。たとえば、Pandora にも Amoy システムのミドルウェアをロードするための特別なクラスローダーインスタンスがあり、ミドルウェアとアプリケーションクラスの間の競合を回避しています。次の図を参照してください。

コンテナの内部依存 jar がロードされた後、避けられないステップが続きます。アプリケーション ClassLoader インスタンス(通常はコンテナクラスローダーインスタンスと同じではない)がアプリケーション jar パッケージと、コンパイルおよびパッケージングフェーズでタイプされたアプリケーション .class プログラムをロードします。したがって、コンテナはアプリケーションがコンテナの実行を干渉しないように保証しながらビジネスを実行する必要があります。

たとえば、図 1 では、最終的なアプリケーションパッケージの Y.jar-2.0 と Z.jar の両方に com.taobao.Cc.class クラスがありますが、1 つのアプリケーション ClassLoader インスタンスは V3 または V2 のいずれか 1 つのバージョンの com.taobao.Cc.class クラスのみをロードできます。

どのバージョンの com.taobao.Cc.class がロードされるでしょうか?答えは必ずしも決まっていません。コンテナアプリケーションのクラスローディングの実装戦略に依存します。過去には、Tomcat、taobao-tomcat、Pandora はすべて、アプリケーション lib パッケージ下のすべての .jar パッケージファイルのリストを直接ロードしていました(上記の例は A.jar、B.jar、*.jar、Y.jar、Z.jar です。Tomcat 以外はソースコードを確認していないため、間違っている場合はご指摘ください)。しかし、Java がディレクトリ内のすべての jar パッケージをロードする際、ロードする順序は完全にオペレーティングシステムに依存します。Linux の順序は完全に inode の順序に依存し、inode の順序は完全には一貫していないため、筆者は以前同様の問題に遭遇しました。オンライン上に 20 台のマシンがあり、同じイメージを使用しているにもかかわらず、起動できない 2 台のマシンがありました。このような状況に遭遇した場合は、次の章の方法に従って解決するしかありません。理論的には、最も正しいアプローチは、コンテナがアプリケーション jar パッケージをロードする際に指定された順序でロードすることです。

上記の分析に基づき、ほぼすべてのクラス競合の本質的な理由は、Maven の依存関係の調停がランタイム要件を満たさない jar パッケージを提供するか、コンテナクラスローディングプロセスでロードされるクラスがランタイム要件を満たさないかのいずれかであるという結論に達します。

コンテナクラスローディング分離戦略については、インターネット上の ATA に多くの資料があります。本記事では、競合のさまざまな解決策を説明することに焦点を当てます。競合を解決するには、上記の重要な原則を知るだけです。

依存関係の競合の本質的な原因を理解した後、競合を引き起こす特定の jar パッケージを効率的に特定する方法については、次の章に進んでください。

その 3: 依存関係の競合問題の効率的な特定スキル

依存関係の競合は主にシステムの起動時または実行時の異常として現れ、その 99% は NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError の 3 つのタイプとして現れます。以下、それぞれのトラブルシューティング技術を説明します。

1. NoClassDefFoundError、ClassNotFoundException のトラブルシューティングステップ

STEP1. NoClassDefFoundError が発生した場合は、まず完全な例外スタックを確認して、静的コードブロックで例外が発生しているかどうかを確認する必要があります。静的コードブロックの例外スタックと jar パッケージの競合には明確な違いがあります。「Could not initialize」と「Caused by: ...」というキーワードは通常、クラスをロードできない原因となる静的コードブロックの例外です。

静的コードブロックの例外が原因で NoClassDefFoundError が発生している場合は、静的コードブロックを修正して例外をスローしないようにするだけです。問題が静的コードブロックの例外によって引き起こされていない場合は、次のステップに進みます。

STEP2. 静的コードブロックの例外がロード失敗の原因ではない場合、例外メッセージのキーワードに欠落しているクラス名が明確に表示されます。例:

STEP3. IDEA(ショートカットキー Ctrl+N)で、例外スタック内の欠落しているクラスを含む jar パッケージのバージョンを特定します。

STEP4. アプリケーションデプロイマシン上のアプリケーション lib パッケージディレクトリを確認します(通常、前のステップで見つかった jar パッケージの対応するバージョンがあるかどうか)。上記の状況は通常、アプリケーションが現在 jar パッケージの低バージョンに依存しており、その jar パッケージに競合クラスが含まれていないために発生します。ほとんどの場合、NoClassDefFoundError と ClassNotFoundException の特定確認は、Maven 依存関係の調停によって最終的に採用された jar パッケージのバージョンとランタイム要件の間の不整合によって引き起こされます。

2. NoSuchMethodError のトラブルシューティングステップ

STEP1. NoSuchMethodError が発生した場合、例外スタックログのコアフラグメント(例外スタックの最下部のフラグメント。多くの学生が例外をかき集めているのを見てきましたが、それは無意味です。目的を持って重要な場所をかき集める必要があります)には、どのクラスのどのメソッドが欠落しているかが明確に表示されます。例外スタックのコアフラグメントの例を以下に示します。

まず、JVM で現在ロードされている欠落メソッドクラスを確認し、上記のクラスがどの jar パッケージから来ているかを特定する必要があります。現在最も効率的な方法は次のとおりです。

外部環境コンテナの下、または一部のコンテナバージョンが低すぎて Arthas オンライン診断をサポートしていない場合は、JVM 起動パラメータに「-XX:+TraceClassLoading」を追加してからシステムを再起動すると、システムエンジニアリングログにロードされたクラスに関する情報が表示されます。そこから、JVM がどの jar パッケージからロードされているかを特定できます。

STEP2. IDEA(ショートカットキー Ctrl+N)で、例外スタック内の欠落クラスを含む jar パッケージのバージョンを特定します。次の図を参照してください。

次に、jar パッケージの各バージョンで競合クラスのソースコードを順番に確認します。プロジェクトの一部の jar にはソースコードパッケージがパッケージ化されており、ソースコードを直接確認できます。ソースコードがない場合は、IDEA プラグイン(jad を推奨)を使用してデコンパイルする必要があります。次に、各 jar パッケージ内の競合クラスを順番に検索します。検索の最初のステップは、上記の図でバージョンクラスをクリックし、IDEA でクラスレベルの関係(ショートカットキー Ctrl+H)を見つけることです。下の図を参照してください。

次に、競合クラスとすべての競合クラスの親クラスソースコードで、NoSuchMethodError 例外情報に記載されている欠落メソッドを検索します。

上記のプロセスステップに従うと、基本的に 99% の依存関係の競合を根本原因まで特定できます。原因を特定した後、競合を解決する方法は?実際、イントラネットの多くの投稿で説明されているように、競合を解決することは「mvn dependency:tree」や jar の配置よりもはるかに簡単ではない場合があります。詳細については、次の章に進んでください。

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.