An Efficient Solution to Java Dependency Conflicts

One: Overview

Due to the special nature of the business of the Alimama Alliance team, the system has a huge external dependence, relying on the services of 60 or 70 teams and N multi-tool components. Through this article, I would like to share with you some of our accumulated experience in effective governance of complex dependencies. In addition to the summary of simple technical skills, we will also discuss some thoughts about the architecture in this area. I hope this article can systematically and thoroughly solve the problems caused by java dependency conflicts.

Two: the essential cause of dependency conflict

To resolve dependency conflicts, we must first understand the essential causes of java dependency conflicts.

The above figure is an example. At present, most of the Java projects in Ali are Maven projects. From development to online, such projects need to go through the following two important steps:

1 Compile and package

Usually we write application code, when using maven to compile the application code, maven only relies on the first-level jar package to complete the compilation of the application code. Arbitration result downloads the corresponding jar and puts it in the specified directory (for example, Y. It is not the same, so it does not belong to the category of maven arbitration).

One thing to note is that there may be differences between different maven versions, which will sometimes cause inconsistent application logic performance due to inconsistencies between the local environment and daily and pre-release packaging (explain that there are other reasons for this situation, not necessarily Inconsistent maven versions are caused by inconsistent arbitration results).

2 release online

First clarify a concept. In the JVM, a type instance is uniquely determined by its full class name and the class loader (ClassLoader) instance that loads it. So the so-called "class isolation" is actually achieved by loading the classes that need to be isolated through different class loader instances, so that even if two classes with the same full class name but different content, as long as their class loader instances are different , can coexist in a container process, and run independently without interfering with each other.

When publishing and starting a container, whether it is tomcat, taobao-tomcat, PandoraBoot, or other containers, the jar package that the container itself depends on is first loaded with a specific class loader instance, and the container generally has multiple class loader instances. The jar package that it depends on is generally loaded by a special class loader instance to achieve absolute isolation from the application package. For example, Pandroa also has a special class loader instance to load the middleware of the Amoy system to avoid conflicts between middleware and application classes, as shown in the following figure :

After the container's internal dependency jar is loaded, it is the inevitable step: an application ClassLoader instance (generally not the same as the container class loader instance) loads the application jar package and application .class program typed out during the compilation and packaging phase, so Containers are required to run business while ensuring that applications do not interfere with the running of containers.

For example, in Figure 1, Y.jar-2.0 and Z.jar in the final application package both have com.taobao.Cc.class class, but one application ClassLoader instance can only load one version of com.taobao in V3 or V2. Cc.class class.

Which version of com.taobao.Cc.class will be loaded? The answer is not necessarily, it depends on the implementation strategy of the container application class loading. From the past, tomcat, taobao-tomcat, and Pandora all directly load the list of all .jar package files under the application lib package (the above example is A.jar, B.jar, *.jar, Y.jar, Z.jar. I haven’t checked the source code except tomcat, please correct me if I’m wrong). But when Java loads all jar packages in a directory, the order in which it loads depends entirely on the operating system! The order of Linux depends entirely on the order of INodes, and the order of INodes is not completely consistent, so the author encountered a similar problem before. When there are 20 machines on the line, using the same image, there are 2 machines that cannot be started. When encountering this situation, you can only obediently follow the methods in the following chapters to solve it. Theoretically, the most correct approach should be to load the application jar packages in the specified order when the container loads them.

Based on the above analysis, we can conclude that the essential reason for almost all class conflicts: either because maven depends on the arbitration jar package that does not meet the runtime requirements, or the classes loaded during the container class loading process do not meet the runtime requirements. of.

Regarding the container class loading isolation strategy, there are a lot of information on ATA on the Internet. This article focuses on explaining various solutions to conflicts. To resolve conflicts, you only need to know the above key principles.

After understanding the essential causes of dependency conflicts, how to efficiently locate the specific jar packages that cause the conflicts? Please continue to the next chapter.

Three: Efficient positioning skills for dependency conflict problems

Dependency conflicts are mainly manifested in abnormalities during system startup or operation, and 99% of them manifest as three types of NoClassDefFoundError, ClassNotFoundException, and NoSuchMethodError. The positioning techniques are explained one by one below.

1. NoClassDefFoundError, ClassNotFoundException troubleshooting steps
STEP1. When NoClassDefFoundError occurs, you must first look at the complete exception stack to confirm whether an exception occurs in a static code block. There is a clear difference between an exception stack in a static code block and a jar package conflict. "Could not initialize" and "Caused by: ... "The keyword is generally an exception in the static code block that causes the class to fail to load:

Because the exception of the static code block causes NoClassDefFoundError, just modify the static code block to avoid throwing an exception. If the problem is not caused by an exception in the static code block, continue to the next step.

STEP2. If it is not an exception in the static code block that causes the loading to fail, the missing class name will be clearly displayed in the exception message keyword, for example:

STEP3. In IDEA (shortcut key Ctrl+N), find out which version of the jar package contains the missing class in the exception stack.

STEP4. Check the application lib package directory on the application deployment machine (generally, whether there is a corresponding version of the jar package found in the previous step. The above situation is generally because the application depends on the lower version of the jar package at this time, and the jar package contains There are no conflicting classes. In most cases, NoClassDefFoundError and ClassNotFoundException location confirmation are caused by the inconsistency between the version of the jar package finally adopted by the maven dependency arbitration and the runtime requirements.

2. NoSuchMethodError troubleshooting steps in place

STEP1, when a NoSuchMethodError occurs, the core fragment of the exception stack log (the fragment at the bottom of the exception stack, I have seen many students rummaging through the exception, it is meaningless, you must rummage the key places with a purpose, do not rummage) will be clearly displayed Specifically which class, which method is missing, and an example of the core fragment of the exception stack are as follows.

First of all, you need to confirm the missing method class currently loaded in the JVM, which jar package the above class comes from, and the most efficient way at present:

Under the external environment container, or some container versions are too low to support Arthas online diagnosis, you can add "-XX:+TraceClassLoading" to the JVM startup parameters, then restart the system, and you can see it in the system engineering log Information about loaded classes to the JVM. From it, you can find out which jar package the JVM is loaded from.

STEP2. In IDEA (shortcut key Ctrl+N), find out which versions of jar packages contain the missing classes in the exception stack, as shown in the following figure:

Then check the source code of the conflicting classes in each version of the jar package in turn. Some jars in the project are packaged with source code packages, and you can directly see the source code. If you do not have source code, you need to use the IDEA plug-in (jad is recommended) to decompile it. Then search for the conflicting classes in each jar package in turn. The first step of searching is to click a version class in the above figure, and find the class-level relationship in IDEA (shortcut key Ctrl+H), as shown in the figure below. .

Then find the missing method described in the NoSuchMethodError exception information in the conflict class and the parent class source code of all conflict classes.

According to the above process steps, basically 99% of dependency conflicts can be located to the root cause. How to resolve the conflict after locating the cause? In fact, sometimes resolving conflicts is far less simple than "mvn dependency:tree" and arranging jars as described in many posts on the intranet. For details, please continue to the next chapter.

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 Contact Us