Towards Native: Spring&Dubbo AOT technology example and principle explanation

Author: Liu Jun

In the era of cloud computing, Java applications face problems such as slow "cold start", high memory usage, and long warm-up time, and cannot well adapt to cloud deployment modes such as Serverless. GraalVM solves these problems to a large extent through technologies such as static compilation and packaging. To solve these problems, and for some usage restrictions of GraalVM, mainstream frameworks such as Spring and Dubbo also provide corresponding AOT solutions.

In this article, we will analyze in detail the challenges that Java applications face in the cloud era, how GraalVM Native Image solves these problems, the basic concepts and working principles of GraalVM, and finally we demonstrate how to integrate a common microservice with a Spring6 + Dubbo3 microservice application example. Service applications are packaged statically.

This article is mainly divided into the following four parts:

  1. First of all, we will first look at the characteristics that cloud applications should have in the current rapid development of cloud computing, and what challenges Java applications face on the cloud.
  2. Secondly, I will introduce GraalVM, what is Native Image, and how to statically print Java applications through GraalVM to generate executable binary programs from Native Image.
  3. In the third part, we know that the use of GraalVM has certain restrictions. For example, dynamic features such as Java reflection are not supported, so we need to provide special Metadata configuration to bypass these restrictions. In this part, we will explain how to add AOT Processing is introduced to realize automatic Metadata configuration, including AOT processing in Spring6 framework, AOT processing in Dubbo3 framework, etc.
  4. Finally, we will use a Spring6+Dubbo3 application example to demonstrate how to statically package such a Java application.

Challenges Faced by Java Applications in the Cloud Era

First of all, let's take a look at the application characteristics of the cloud computing era and the challenges Java faces in the cloud era. Judging from the data given by various statistical agencies, the Java language is still one of the most popular programming languages ​​for developers today, second only to some scripting development languages. Java language can be used to develop business applications very efficiently. The rich ecology makes Java have very high development and operation efficiency, and countless applications are developed based on Java language.

image

But in the era of cloud computing, the deployment and operation of Java applications began to face many problems. Let's take Serverless as an example. Serverless is an increasingly mainstream deployment model on the cloud. It allows developers to focus more on business logic and help solve resource problems through fast elasticity. According to the latest data, Java is in all cloud computing The proportion of the vendor's Serverless runtime is not high, far from matching its proportion in traditional application development.

image

The main reason for this is that Java applications cannot meet several key requirements of the Serverless scenario.

  • The first is the problem of startup speed. Java cold startup takes a long time. This is a very big challenge for serverless scenarios that require fast pop-up, because the pull-up time of Java applications may be on the order of seconds or tens of seconds;
  • The second point is that Java applications often need a certain amount of warm-up time to achieve the best performance status. It is not appropriate to allocate relatively large traffic to applications that have just been launched, and problems such as request timeouts and high resource usage often occur , which further prolongs the effective pull-up time of the Java application;
  • The third point is the requirements of Java applications on the operating environment. It often requires large memory and computing resources, but these are not allocated to the business itself, and are consumed on some JVM runtimes. This is not the same as using the cloud to reduce costs. Efficiency-improving goals are combined and matched;
  • Finally, the package or image created by the Java application is also very large, which also affects the efficiency of storage and retrieval as a whole.

Next, let's take a look at how GraalVM, a packaging and runtime technology, solves these problems faced by Java applications.

Introduction to GraaIVM

GraalVM compiles your Java applications ahead of time into standalone binaries that start instantly, provide peak performance with no warmup, and use fewer resources.

According to the official introduction, GraalVM provides AOT compilation and binary packaging capabilities for Java applications. Binary packages based on GraalVM can achieve fast startup, ultra-high performance, no warm-up time, and very little resource consumption. The AOT mentioned here is a technical abbreviation that occurs during compilation, that is, Ahead-of-time, which we will talk about later. In general, GraalVM can be divided into two parts.

  • First of all, GraalVM is a complete JDK release version, from this point it is equivalent to OpenJDK, and can run any application developed in a jvm-oriented language;
  • Secondly, GraalVM provides Native Image packaging technology, which can package the application into a binary package that can run independently. This package is a self-contained application that can run without the JVM.

image.png

As shown in the figure above, the GraalVM compiler provides two modes, JIT and AOT.

  • For JIT, we all know that Java classes will be compiled into files in .class format. After compiling here, it is the bytecode recognized by jvm. During the running of Java applications, the JIT compiler will compile some Byte code is compiled into machine code, which has achieved faster execution speed;
  • For AOT mode, it directly converts bytecode into machine code during compilation, which directly saves the dependence on jvm at runtime. Since the time for jvm loading and bytecode runtime warm-up is saved, AOT Compiled and packaged programs have very high runtime efficiency.

image

In general, JIT enables applications to have higher extreme processing capabilities, which can reduce the key indicator of the maximum delay of requests; while AOT can further improve the cold start speed of applications, with smaller binary package mentions, In the running state, less resources such as memory are required.

What is Native Image?

We mentioned the concept of Native Image in GraalVM many times above. Native Image is a technology that compiles and packages Java code into an executable binary program. The package contains only the code required by the runtime, including the application's own code and standard dependencies. Static code associated with packages, language runtimes, and JDK libraries. The operation of this package no longer requires the jvm environment. Of course, it is bound to the specific machine environment and needs to be packaged separately for different machine environments. Native Image has a set of features listed here:

  • Contains only a part of the resources required by the JVM to run, and the running cost is lower

  • Startup time in milliseconds

  • It enters the best state after starting, no need to warm up

  • Can be packaged as a lighter binary package, making deployment faster and more efficient

  • higher level of security

image

To sum up, here are the key items: faster startup speed, less resource usage, less risk of security vulnerabilities, and more compact binary package size. Solve the outstanding problems faced by Java applications in cloud computing application scenarios such as Sererless.

The basic principle and use of GraaIVM Native Image

Next, let's take a look at the basic usage of GraalVM. First, you need to install the relevant basic dependencies required by native-image, which vary according to different operating system environments. Next, you can use the GraalVM JDK downloader to download native-image. After everything is installed, in the second step, you can use the native-image command to compile and package Java applications. The input can be class files, jar files, Java modules, etc., and finally packaged into an executable file that can run independently, such as here Hello World. In addition, GraalVM also provides corresponding Maven and Gradle build tool plugins to make the packaging process easier.

image.png

GraalVM is based on a concept called "closed world assumption", which requires that all runtime resources and behavior of a program can be fully determined during compilation. The figure shows the specific AOT compilation and packaging process. The left application code, warehouse, jdk, etc. are all used as input. GraalVM uses main as the entry point to scan all accessible codes and execution paths. During the processing, some The pre-initialization action, the final AOT compiled machine code and some state data such as initialization resources are packaged into an executable Native package.

Compared with the traditional JVM deployment mode, the GraalVM Native Image mode is very different.

  • GraalVM will use the main function as the entry point during compilation and construction to complete the static analysis of the application code
  • Code that cannot be reached during static analysis will be removed and will not be included in the final binary package
  • GraalVM cannot recognize some dynamic calling behaviors in the code, such as reflection, resource resource loading, serialization, dynamic proxy, etc., all dynamic behaviors will be limited
  • Classpath is solidified during the construction phase and cannot be modified
  • Delayed class loading is no longer supported, all available classes and code are determined at program startup
  • There are also some other Java application capabilities that are restricted to use (such as class initialization in advance, etc.)

GraalVM does not support dynamic features such as reflection, but many of our applications and frameworks use a lot of features such as reflection and dynamic proxy. How can we package these applications as Native Image to achieve static? GraalVM provides a metadata configuration entry. By providing configuration files for all dynamic features, the "closed world assumption" mode is still established, allowing GraalVM to know all expected behaviors at compile time. Two examples are given here:

  1. In terms of encoding methods, such as the encoding method reflected here, GraalVM can calculate Metadata through code analysis.

image

image

  1. Another example is to provide additional json configuration files and place them under the specified directory META-INF/native-image//.

image

AOT Processing

The use of dynamic features such as reflection in Java applications or frameworks is an obstacle that affects the use of GraalVM, and a large number of frameworks have this limitation. If applications or developers are required to provide Metadata configuration, it will be a very challenging task. Therefore, frameworks such as Spring and Dubbo have introduced AOT Processing, or AOT preprocessing, before AOT Compilation, or AOT compilation. AOT Processing is used to complete automated Metadata collection and provide Metadata to the AOT compiler.

image

The AOT compilation mechanism is common to all Java applications, but compared with AOT compilation, the process of collecting Metadata by AOT Processing is different for each framework, because each framework has its own usage for reflection, dynamic proxy, etc.

Let's take a typical Spring + Dubbo microservice application as an example. To realize the static packaging of this application, it involves the Metadata processing process of Spring, Dubbo and a number of third-party dependencies.

  • Spring - Spring AOT processing
  • Dubbo - Dubbo AOT processing
  • Third-party libraries - Reachability Metadata

For Spring, the Spring AOT mechanism was released in Spring6 to support the static preprocessing of Spring applications; Dubbo also released the Dubbo AOT mechanism in version 3.2 recently, allowing Dubbo related components to automatically implement Native preprocessing; in addition to this Two frameworks are closely related to business development. There are often a large number of third-party dependencies in an application. The metadata of these dependencies is also the key to affecting static. If there are reflections, class loading and other behaviors in them, then Metadata needs to be provided for them Configuration, there are currently two channels for these third-party applications, one is the shared space officially provided by GraalVM, where a considerable part of the dependent Metadata configuration is available, and the other way is to require the official release of the component to include the Metadata configuration , GraalVM can automatically read Metadata in both cases.

Metadata configuration: https://github.com/oracle/graalvm-reachability-metadata

Spring AOT

Next, let's take a look at the preprocessing work done by Spring AOT before compilation. There are many dynamic features in the Spring framework, such as automatic configuration and conditional beans. Spring AOT is aimed at these dynamic features, preprocessing during the construction phase, and generating a series of Metadata inputs that can be used by GraalVM. The generated content here includes:

  • Spring Bean defines related code pre-generation, as shown in the following figure
  • Generate dynamic proxy related code during the build phase
  • About some JSON metadata files used by reflection etc.

image.png

Dubbo AOT

What Dubbo AOT does is very similar to Spring AOT, except that Dubbo AOT is specially preprocessed for the unique usage of Dubbo framework, including:

  • SPI extension related source code generation
  • Some reflections use JSON configuration files to generate
  • RPC proxy class code generation

image

image

Spring6+Dubbo3 demo

Next, we demonstrate how to use Spring AOT, Dubbo AOT, etc. to realize the Native Image packaging of the application through a sample microservice application of Spring6 + Dubbo3.

The complete code sample can be downloaded here: https://github.com/apache/dubbo-samples/tree/master/1-basic/dubbo-samples-native-image

Step 1: Install GraalVM

  1. Select the corresponding Graalvm version according to your own system on the Graalvm official website: https://www.graalvm.org/downloads/

  2. Install native-image according to the official documentation: https://www.graalvm.org/latest/reference-manual/native-image/#install-native-image

Step Two: Create a Project

This sample application is an ordinary and common microservice application. We use SpringBoot3 for application configuration development, and Dubbo3 to define and publish RPC services; the application construction tool uses Maven.

image.png

image.png

Step 3: Configure the Maven plugin

The focus is to add three plug-in configurations of spring-boot-maven-plugin, native-maven-plugin, and dubbo-maven-plugin, enable AOT processing, and modify the mainClass in dubbo-maven-plugin to the full path of the required startup class. (The API usage method does not need to add spring-boot-maven-plugin dependencies.)

<profiles>
        <profile>
            <id>native</id>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <configuration>
                            <release>17</release>
                            <fork>true</fork>
                            <verbose>true</verbose>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>process-aot</id>
                                <goals>
                                    <goal>process-aot</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.graalvm.buildtools</groupId>
                        <artifactId>native-maven-plugin</artifactId>
                        <version>0.9.20</version>
                        <configuration>
                            <classesDirectory>${project.build.outputDirectory}</classesDirectory>
                            <metadataRepository>
                                <enabled>true</enabled>
                            </metadataRepository>
                            <requiredVersion>22.3</requiredVersion>
                        </configuration>
                        <executions>
                            <execution>
                                <id>add-reachability-metadata</id>
                                <goals>
                                    <goal>add-reachability-metadata</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-maven-plugin</artifactId>
                        <version>${dubbo.version}</version>
                        <configuration>
                            <mainClass>com.example.nativedemo.NativeDemoApplication</mainClass>
                        </configuration>
                        <executions>
                            <execution>
                                <phase>process-sources</phase>
                                <goals>
                                    <goal>dubbo-process-aot</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

Step 4: Add native-related dependencies to Pom dependencies

In addition, for Dubbo, because some current native mechanisms rely on JDK17 and other versions, Dubbo does not package some packages into the release version by default, so two additional dependencies need to be added: dubbo-spring6 adaptation and dubbo-native components.

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-config-spring6</artifactId>
    <version>${dubbo.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-native</artifactId>
    <version>${dubbo.version}</version>
</dependency>

Step 5: Adjust compiler, proxy, serialization and logger

At the same time, this example's support for third-party components is currently limited, mainly the Reachability Metadata of third-party components. For example, the currently supported network communication or encoding components include Netty and Fastjson2; the supported log components are Logback; the microservice components include Nacos, Zookeeper, etc.

  • The serialization method currently supported is Fastjson2
  • Compiler and proxy can only choose jdk at present
  • logger currently needs to configure slf4j, currently only supports logback

An example configuration is as follows:

dubbo:
  application:
    name: ${spring.application.name}
    logger: slf4j
    compiler: jdk
  protocol:
    name: dubbo
    port: -1
    serialization: fastjson2
  registry:
    id: zk-registry
    address: zookeeper://127.0.0.1:2181
  config-center:
    address: zookeeper://127.0.0.1:2181
  metadata-report:
    address: zookeeper://127.0.0.1:2181
  provider:
    proxy: jdk
    serialization: fastjson2
  consumer:
    proxy: jdk
    serialization: fastjson2

Step 6: Compile

Execute the following compilation command under the project root path:

  • Execute directly by API
mvn clean install -P native -Dmaven.test.skip=true
  • Annotation and xml method (Springboot3 integrated method)
mvn clean install -P native native:compile -Dmaven.test.skip=true

Step 7: Execute the binary file

Binary files are in the target/ directory, and the name of the binary package is usually the project name, such as target/native-demo.

Summarize

GraalVM technology has brought new changes to the application of Java in the era of cloud computing, helping to solve the slow startup and resource occupation of Java applications, but at the same time we have also seen some limitations in the use of GraalVM, so Spring6, SpringBoot3, and Dubbo3 all provide The corresponding Native solution is provided. In addition to Dubbo, Spring Cloud Alibaba is also promoting a static packaging solution. Next, we will promote the overall Native static verification around the surrounding ecological components of the two frameworks, such as nacos, sentinel, and seata.

Related Links:

[1] Apache Dubbo blog

https://dubbo.apache.org/zh-cn/blog/

Graduates of the National People’s University stole the information of all students in the school to build a beauty scoring website, and have been criminally detained. The new Windows version of QQ based on the NT architecture is officially released. The United States will restrict China’s use of Amazon, Microsoft and other cloud services that provide training AI models . Open source projects announced to stop function development LeaferJS , the highest-paid technical position in 2023, released: Visual Studio Code 1.80, an open source and powerful 2D graphics library , supports terminal image functions . The number of Threads registrations has exceeded 30 million. "Change" deepin adopts Asahi Linux to adapt to Apple M1 database ranking in July: Oracle surges, opening up the score again
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/3874284/blog/10087336