pandora boot热点应用探索60秒构建之路

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 本文介绍了阿里内部一个名为A的典型热点应用,该应用的构建产物是一个1GB的fatjar,包含2893个jar。最近,应用A采用了新的amaven版本,使得p95构建时间从20分钟降低到6分钟。为了进一步优化构建时间,作者探讨了两个主要优化点:1. 使用amaven的增量编译功能,可以减少大约45秒的构建时间;2. 优化autoconfig插件,通过并发执行和改变目录结构,使构建时间固定减少30秒。此外,文章还提到了docker build的优化,通过改变Dockerfile的结构和使用SYNC语法,减少了大约30秒的时间。综合这三个优化,构建时间可以缩短到136秒。

背景


在阿里内部有一个典型的pandora boot应用A,它参与研发的同学与每周的发布次数都比较多,是一个典型的热点应用.它的构建产物是一个fatjar,文件大小有1个G,其中包含了2893个jar。

image.png

在最近,应用A使用了最新的 amaven 版本后,p95的构建耗时从20分钟下降到了6分钟。

image.png

我们来看看有没可能将应用A的构建继续优化,下降到60秒.这样,我们就有更多的时间去外面呼吸更多的新鲜空气了。


现状


在命中所有amaven的依赖树缓存,及docker缓存后(即最佳情况),我们拿应用A的master分支来构建下,看能到几分钟。一次典型的构建主要分二个大步骤,一个是主包构建,即打fatjar,一个是镜像构建。我们先看主包构建,在amaven的依赖缓存全命中的情况下,最优是2:29min。

image.png

再看镜像构建,build耗时28秒,push耗时38秒,共66秒。

image.png

所以,在最佳情况下,应用A的"纯构建"耗时在3分半,即210秒左右.但实际上左边菜单中显示的时间169+73=242秒,约有242-210=32秒的时间,是构建系统在做构建前置及后置任务。

image.png

方案

3.1 mvn build优化

从amaven的build report中可以看到top 3慢的步骤,耗时依次是34390ms,20376ms,15761ms;主要是慢在maven-compiler-plugin及autoconfig-maven-plugin这二个插件,我们对这二个插件分别来优化。

image.png

3.1.1 增量编译 最佳减少45秒

maven-compiler-plugin主要是执行以下命令:


javac -classpath a.jar:b.jar.... --source-path ..


这个插件会将所有maven根据应用的pom进行依赖分析并且仲裁后的jar包列表作为classpath参数.maven-compiler-plugin的耗时基本上主是javac的耗时,而javac的耗时长并不是因为要编译的java文件太多,而更是因为classpath中的jar包太多;当它编译一个java类时,对这java类中import进来的类要去这些classpath指定的jar包中去遍历查找.从文章开头就讲到的应用A最后的fatjar中有2893个jar,可以近似知道classpath中的jar包也不少。


所以减少classpath中的jar包数,即通过应用的pom来治理依赖与减少依赖,是一个更治本的方案.但我们今天要说的是,不执行javac肯定是最快的。


不执行javac,而是直接复用之前编译的class文件,是最快的.即增量编译.即只编译变化的java,没变的就直接复用.所以增量编译,其实是少执行javac,而并不全是不执行javac。


现在amaven实现的增量编译,不是基于单个java文件,而是一个maven module.一般应用典型的module依赖关系如下图:

image.png

biz中的代码会高频更新,但它依赖的dal,common相对稳定.当一个分支在第二次编译时,只修改了biz层,则只要重新编译biz,controller二层,而dal,common二层的class可以直接复用之前的. 增量编译的一些描述问题请点这 并搜索"增量".在这我们先来看看增量编译的效果。

image.png

从上图可以看出 从原来02:20min能降到01:35min即减少45秒左右。


那如何启用增量编译?


只要在使用amaven进行编译时,加上参数-DenableIncrementTask=true 。

3.1.2 autoconfig 固定减少30秒


"增量编译"有效果,但其实是不稳定的,要看一个应用每次编译时是否只修改了java类,且这java类是否在上层模块.现在我们再来看看autoconfig插件.对这插件的优化的效果是稳定的。


autoconfig插件的作用是将同一份代码用不同的配置项来编译,从而部署在不同环境。


在之前我们已经对autoconfig针对war包应用的场景作过一次优化.现在我们再来对fatjar应用的场景作优化。


从优化前的日志中可以看到二点:1.是日志中有allocating large array,即在执行过程中消耗了大量的内存,因为当autoconfig插件执行时是会将一个应用A约1G大小的fatjar以zipInputStream的方式读进内存,再以nextEntry的方式遍历所有文件; 2.是耗时了34秒。

image.png

目前的一个fatjar应用主包的形态(目录或jar包文件)的构建与部署过程一般如下:

image.png

即mvn build时先构建出目录,然后再压缩成jar;到最后应用启动时又会将jar包解压成目录.压缩成jar主要目的是减少体积,但却带来了CPU开销.在网络带宽资源大于CPU资源时我们推荐不要压缩成jar包。


直接是目录形态.它还有2个好处:


  1. autoconfig可以并发执行;
  2. docker build可以使用SYNC语法;

请参照以下步骤来升级autoconfig:


1、在构建配置文件中配置以下二个参数:其中第一个是让aone不要压缩成tgz,而第二个是让aone知道要将哪个目录copy到镜像中。


build.output.copyonly=true 
build.output=appA-bootstrap-start/target/appA


2、将autoconfig-plugin 放到maven-antrun-plugin后,且使用2.0.10及以上版本,再加上以下第24-28行的配置.其中第25行,要指定到应用的fatjar结构的目录中的lib目录,即jar包们所在的目录。



<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
    <execution>
        <phase>package</phase>
        <configuration>
            <tasks>
                <unzip
                        src="${project.build.directory}/${project.build.finalName}.${project.packaging}"
                        dest="${project.build.directory}/appA"/>
            </tasks>
        </configuration>
        <goals>
            <goal>run</goal>
        </goals>
    </execution>
</executions>
</plugin>

<plugin>
<groupId>com.alibaba.citrus.tool</groupId>
<artifactId>autoconfig-plugin</artifactId>
<version>2.0.10</version>
<configuration>
    <dest>${project.build.directory}/appA/BOOT-INF/lib</dest>
    <type>jar</type>
    <isListDestFiles>true</isListDestFiles>
</configuration>
<executions>
    <execution>
        <phase>package</phase>
        <goals>
            <goal>autoconfig</goal>
        </goals>
    </execution>
</executions>
</plugin>

maven-antrun-plugin会将fatjar包文件解压成目录,所以要放在autoconfig插件前. 那能否让pandora-boot-maven-plugin不将fatjar压缩成文件呢?因为压缩的时间开销还好,经过对appA的测试,将2893个jar压缩成一个fat.jar只要2秒多(因为pandora-boot-maven-plugin没使用二次压缩,所以很快).同时因为要修改这插件不压缩成fat.jar,修改地方较多,所以pandora-boot-maven-plugin暂不提供 不压缩成fat.jar 的版本。


3、修改dockerfile:



COPY ${APP_NAME}.tgz /home/admin/${APP_NAME}/target/${APP_NAME}.tgz

修改成:


COPY build-output/ /home/admin/${APP_NAME}/target/${APP_NAME}/

因为现在不是tgz,而是build-output目录了.


4、修改应用启动脚本


因为现在不是fat.jar一个文件,而是一个目录了.所以对于旧版本的pandora boot应用,要相应修改应用的启动脚本,如原来有解压操作的,现在可以不要了.而最新版本的pandora boot版本的应用,则兼容,不用修改。


最后,这样配置后的效果如下:

image.png

image.png

从上图可以看出autoconfig的耗时从34秒下降到了3.8s,即减少了30秒。


autoconfig插件升级后,我们通过对比优化前后的二个构建日志来验证结果的正确性.搜索日志中的autoconfig产生的 "Generating META-INF"数量是一样的.比较具体的配置了的jar包列表,也是相同的,说明结果一样。


同时在 优化后日志 中我们发现:Runtime : ran out of parsers. 的日志:

image.png

这些日志是velocity报的,因为现在是"多线程作配置",所以同时要有的parser会较多。

image.png

对于新版本的autoconfig插件,我们主要作了二个优化:


1.用线程池来并发执行config:

image.png

2.能并发执行的前提是要autoconfig的目标是一个目录,而不是一个fat.jar文件.当目标是目录时,会先listFile,再将fileList传给destFiles。

image.png

在"增量编译"与autoconfig"并发执行"二个优化后,最佳情况下的mvn构建耗时能到55秒左右。

image.png


3.2 docker build优化

应用A的dockerfile 片段如下:

image.png

首先,是COPY主包没在最后一行,导致第14行每次编译都会执行,因为主包每次构建都会变。


从日志来看,13-14二行,执行了28-21=7秒左右. 即如能根据dockerfile的最佳实践"将不变的放下层,变化的放上层",将13,14二行放到10行前,可以节省7秒左右。


接着,因为前面我们将主包从tgz变成了build-output目录了,所以还可以使用 SYNC语法。


只要一步操作即可:修改dockerfile,将COPY修改成SYNC。


即将


COPY build-output/ /home/admin/${APP_NAME}/target/${APP_NAME}/

修改成(注意,SYNC不支持PATH中变量,请换成具体的应用名;最后也不能有/):


SYNC build-output/ /home/admin/appA/target/appA

最后,我们看看效果。


使用SYNC后,从原来 一个fatjar要1G的内容要docker build与push,变成只有变化的jar包(源码产生的及要autoconfig的,约100个jar包)才要增量build.耗时能从66秒减少30秒左右。


可行性小结

最后综合三个优化点后来看,一次完整的构建只能从242s降到136s,离60s还有一段路要突破。

image.png

但打个折,只从mvn 构建来看,只要进行autoconfig插件升级与启用增量构建,就可以到达60s(纯mvn build可以达到44秒)。

image.png

所以让我们行动起来:


  1. 启用amaven增量编译;
  2. 升级autoconfig插件;
  3. 选择buildkit编译机并使用SYNC;

对于60s的追求,我们也会一直探索下去,即使又到来年的丹桂飘香时......


amaven与buildkit相关内容请参考:java应用提速(速度与激情)


作者 | 魏洪波(微波)

来源 | 阿里云开发者公众号

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
分布式计算 并行计算 数据库
Schedulerx2.0分布式计算原理&最佳实践
1. 前言 Schedulerx2.0的客户端提供分布式执行、多种任务类型、统一日志等框架,用户只要依赖schedulerx-worker这个jar包,通过schedulerx2.0提供的编程模型,简单几行代码就能实现一套高可靠可运维的分布式执行引擎。
26823 2
|
存储 JavaScript 算法
Vue中如何实现动态路由
Vue中如何实现动态路由
248 1
|
机器学习/深度学习 人工智能 自然语言处理
撒花!PyTorch 官方教程中文版正式上线,激动人心的大好事!
撒花!PyTorch 官方教程中文版正式上线,激动人心的大好事!
1331 0
撒花!PyTorch 官方教程中文版正式上线,激动人心的大好事!
|
8月前
|
存储 Prometheus 监控
Prometheus 深度指南:设计理念 · PromQL · Exporter · Thanos
Prometheus 是一款开源的系统监控与报警工具,专为云原生环境设计。它采用拉取模型采集数据,内置高效的本地时序数据库(TSDB),支持丰富的指标类型和四个黄金指标(延迟、流量、错误、饱和度)。其查询语言 PromQL 功能强大,可灵活聚合和分析时间序列数据。此外,通过 Exporter 机制,Prometheus 能轻松扩展到各种系统和服务。针对大规模场景,Thanos 提供高可用解决方案,整合多 Prometheus 实例,实现全局视图和长期存储。整体架构简洁可靠,适用于动态分布式环境。
1046 10
Prometheus 深度指南:设计理念 · PromQL · Exporter · Thanos
|
缓存 Java Spring
Java本地高性能缓存实践问题之Caffeine中设置刷新机制的问题如何解决
Java本地高性能缓存实践问题之Caffeine中设置刷新机制的问题如何解决
560 1
|
XML Cloud Native Dubbo
【Dubbo3高级特性】「提升系统安全性」手把手教你如何通过令牌进行Dubbo3服务验证及服务鉴权控制实战指南(一)
【Dubbo3高级特性】「提升系统安全性」手把手教你如何通过令牌进行Dubbo3服务验证及服务鉴权控制实战指南
841 1
|
机器学习/深度学习 并行计算 Java
【java】 vector api 快速入门
【java】 vector api 快速入门
1387 0
|
Java 编译器 Maven
一文解读|Java编译期注解处理器AbstractProcessor
本文围绕编译器注解都是如何运行的呢? 又是怎么自动生成代码的呢?做出了详细介绍。
|
存储 运维 Dubbo
HSF:阿里RPC框架
HSF:阿里RPC框架
4057 0
|
运维 负载均衡 应用服务中间件
高速服务框架HSF的基本原理(上)
高速服务框架HSF的基本原理(上)
2274 1