如何让Java应用在Aone上打包速度提高100%以上

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

大家在平时开发的过程中,经常会在Aone上部署自己的应用进行功能验证,可能都会遇到应用在Aone上部署非常慢性,其中花在打包上的时间就差不多有7-8分钟,非常影响开发效率,下图就是一个非常典型应用在aone上部署构建一次所花的时间:
image.png
长期这么下去,肯定会是有问题,主要有如下两个方面的问题:

1. 应用打出的War包会越来越大,部署一次所花的时间会越来越长;

2. 新的需求,会不断升级已经的jar包和引入新的jar包,每个jar包会随着时间的推移,各个版本依赖深浅不断的变化,导致同一个应用中同一个jar包真正依赖的jar的版本不同,有时会出现莫期妙的问题,例如

image.png
图中honolulu-common,现在看是依赖的最终版本是1.0.2,也许明天又有一个新人依赖进来,间接依赖了honolulu-common,如果版本高,最终依赖的版本就高,这是只maven依赖深度不同而已,平时我们接触到的其它应用提供的二方包,比这种情况要复杂得多,这恐怕才是导致我们的业务系统依赖的jar包越来越多,间接依赖的jar包版本不断变化,应用打出的war包越来越慢的根本原因,下面就来分享一下自己在这段时间对应用的jar包进行排除来提升应用打包速度所做的工作:

1.安装idea插件Maven Helper

如果发现自己的idea没有安装,就安装Maven Helper
image.png
安装Maven Helper插件以后,重启Idea, 随便点击项目中任意一个模块的pom.xml,如果左下角出现Dependency Analyzer,说明插件安装成功,如下图所示:
image.png
点击Dependency Analyzer, 如下图所示:
image.png
输入你想要排除的jar包,点击右键,出现Exclude,再点击Exclude即可排除该包的的依赖

2. 二方包&客户端jar包排除:

二方包&客户端jar排除相对比较容易,基本的原则是: 不影响二方包打包发布成功的jar都可以排除掉,拿lafite这个应用的二方包来说,除了latour-client, commons-lang3和slf4j-api 这三个jar以外,其它的二方包都用不上,都可以排除,排除以后,发现lafite-client打包的速度超超快, 如下图:
排包后:
image.png
打包前:
image.png
对比打包前和打包后,我们发现,排包后,打包的速度差不多提高了近3倍左右,提速还是非常明显

经验:

1. 二方包依赖的jar 包越小越好,去掉一切无用的Jar包,判断的标就是打出的二方包不出错,即可,当然这么做会有风险,但一般的二方包都不是特别复杂,在打包的时候,基本上都能判断出来

2. 最好用maven3.0+以上的版本打二方包,不到万不得已,不要用maven2.2.1版本打二方,这会坑了其它依赖你二方包的开发同学,导致其它用依赖用maven2.2.1版本打出的二方包无法做Maven升级

3. 应用本身二方包排除:

应用本身的二方包排除要稍微复杂些,首先要确认那些jar包对应用来确实无用,这个确认的过程比较长,可以用一个定时任务来确认,方法如下:

1. 在应用的各个环境的脚本文件setenv.sh中,找到下面这一行:

CATALINA_OPTS="{CATALINA_OPTS} -Dhsf.publish.delayed=false", 然后在这一行加上下面这一行: CATALINA_OPTS="{CATALINA_OPTS} -XX:+TraceClassLoading"
如下图所示:
image.png
这样每个类在加载的时候,详细的加载信息会在tomcat_stdout.log这个文件中打出, 部分信息如下图所示:
image.png
图中包含每个类加载,以及这个类来自那个jar包,例如:org.apache.catalina.util.LifecycleBase 这个类被加载时,是来自catalina这个jar包,/opt/taobao/install/tomcat-7.0.59.3/lib/catalina.jar

2. 在应用中添加一个定时任务检测类:

myju中的定时任务检测类是com.taobao.ju.my.performance.CheckNoUsedJarsJob, 代码如下:

/**
 * 检查无用jar包
 * desheng.tds
 */
public class CheckNoUsedJarsJob implements SimpleJobProcessor {

    private static Logger logger = LoggerFactory.getLogger(CheckNoUsedJarsJob.class);

    @Override
    public ProcessResult process(SimpleJobContext simpleJobContext) {

        try {
            File tomcatStdoutLog = new File("/home/admin/myju/logs/tomcat_stdout.log");
            File jarDir = new File("/home/admin/myju/target/myju.war/WEB-INF/lib");
            String[] fileNames = jarDir.list();
            BufferedReader bufferedReader = new BufferedReader(new FileReader(tomcatStdoutLog));
            String line = null;

            Set<String> sysJars = new HashSet<>();
            Set<String> lines = new HashSet<>();
            List<String> noUsedJars = new LinkedList<>();
            while ( (line = bufferedReader.readLine()) != null ) {

                // System jar包
                if (line.startsWith("[Loaded") && line.contains("from")
                        && line.contains("taobao-hsf.sar") && line.contains("/opt/taobao/install")) {

                    int index = line.lastIndexOf("]");
                    String sysJar  = line.substring(0, index);
                    index = sysJar.lastIndexOf(" ");
                    sysJar = sysJar.substring(index+1);

                    if (sysJar.endsWith("!/") && sysJar.length() > 2) {
                        sysJar = sysJar.substring(0, sysJar.length() - 2);
                    }
                    index = sysJar.lastIndexOf("/");
                    sysJar = sysJar.substring(index+1);
                    sysJars.add(sysJar);
                }

                if (line.startsWith("[Loaded") &&
                        line.contains("from") &&
                        !line.contains("taobao-hsf.sar") &&
                        !line.contains("/opt/taobao/install")) {

                    int startIndex = line.lastIndexOf("/");
                    int endIndex = line.indexOf(".jar]");
                    if (startIndex >= 0 && endIndex>0 && endIndex + 4 <= line.length())
                        line = line.substring(startIndex+1, endIndex + 4);
                    lines.add(line);
                }
            }
            for (String fileName : fileNames) {
                if (!lines.contains(fileName) && !sysJars.contains(fileName)) {
                    noUsedJars.add(fileName);
                }
            }
            for (String fileName : noUsedJars) {
                File file = new File("/home/admin/myju/target/myju.war/WEB-INF/lib/" + fileName);
                String strLen = null;
                long len = file.length()>> 10;
                if (len > 1024) {
                    strLen = (len * 1.0 /1024) + "MB" ;
                } else {
                    strLen = len + "kB";
                }
                logger.warn("fileName=:{}, size={}", fileName, strLen);
            }
        } catch (Exception e) {
            logger.warn("[CheckNoUsedJarsJob.process] error e={}", e);
        }
        return new ProcessResult(true);
    }

}

代码的逻辑比较简单:在应用的war包WEB-INFO/lib目录中检查应用在启动和运行过程中不需要的jar包,一般依赖的war包的jar目录是"/home/admin/应用名/target/应用名.war/WEB-INF/lib/,例如myju应用的jar目录是:"/home/admin/myju/target/myju.war/WEB-INF/lib/,当然有些jar包是来自JDK和Pandaro容器,这部分jar最好也不要排除

3.添加任务检测任务的日志

myju中日志配置如下:

<appender name="CheckNoUsedJarsJob_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
      <file>${ju.my.loggingRoot}/myju_check_no_used_jars.log</file>
      <encoder>
          <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - %m%n</pattern>
          <charset>GBK</charset>
      </encoder>
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <fileNamePattern>${ju.my.loggingRoot}/myju_check_no_used_jars.log.%d{yyyy-MM-dd}</fileNamePattern>
          <maxHistory>7</maxHistory>
          <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
              <maxFileSize>5000MB</maxFileSize>
          </timeBasedFileNamingAndTriggeringPolicy>
      </rollingPolicy>
  </appender>
  
  <logger name="com.taobao.ju.my.performance.CheckNoUsedJarsJob" level="info" additivity="false">
      <appender-ref ref="CheckNoUsedJarsJob_LOG"/>
  </logger>

即检查任务的日志每天打印在myju_check_no_used_jars.log文件中

4.启动任务,可以看到日志文件中有打印出的相关无用jar和大小

myju中如下图所示:
image.png
这些无用的jar基本上都可以排除掉,排除掉以后,打出的war包会小很多:
下面是排包前后,打出的war包大小对比:
排包前:
image.png
排包后:
image.png
对比排包优化前后发现,war包的大小由340M左右减少到160M左右,整整减少一半以上,当然这160M中还包含了近200个被检测无用的jar包,如果把这200个左右的jar包也排掉的话, 估计最终打出的war包会更小;

5. 建议:

1.

提前把这个定时任务在线上加了,只有程序运行的时间越长,才能够更加确定那些jar包是无用的,这个时间一般要2-3周的时间,如果中间有一个大促会是再好不过了,不明确的地方,找熟悉的人进行功能回规,实在没有人熟悉的功能,就只能通过线上beta了,当然风险一定要可控,一般情况下,是发布了一个新版本,一定要及时拉一个新分支,随时应对jar包排错的风险

2.

优先排掉比较的无用而且比较大jar包,这样排包的效果会更明显;

3.

同一个jar包的不同版本,尽量排掉依赖更深的依赖,在不好处理的情况下,加直接依赖,然后再把间接依赖全部去掉,减少了间接依赖,有利于打包速度的提高,同时还可以防止jar间接升级导致的不可控的问题;

4 优化效果:

排包优化之前:
image.png
排包优化之后:
image.png
排包优化前后,打包构建时间整整减少一半以上,打包构建速度整理提高了100%以上

文章中有不足或没有写清楚的地方,欢迎大家指出纠正,一起进步成长

后面会介绍如何监控一个应用中启动比较慢性的Bean,以及在公司内部,如何优化这些启动比较慢性的Bean

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
22天前
|
自然语言处理 前端开发 Java
JBoltAI 框架完整实操案例 在 Java 生态中快速构建大模型应用全流程实战指南
本案例基于JBoltAI框架,展示如何快速构建Java生态中的大模型应用——智能客服系统。系统面向电商平台,具备自动回答常见问题、意图识别、多轮对话理解及复杂问题转接人工等功能。采用Spring Boot+JBoltAI架构,集成向量数据库与大模型(如文心一言或通义千问)。内容涵盖需求分析、环境搭建、代码实现(知识库管理、核心服务、REST API)、前端界面开发及部署测试全流程,助你高效掌握大模型应用开发。
126 5
|
1月前
|
前端开发 JavaScript Java
Java 学习路线规划及项目案例中的技术栈应用解析
内容包括:**Java 17核心特性**(如sealed class、record)与模块化开发;Spring Boot 3 + Spring Cloud微服务架构,涉及响应式编程(WebFlux)、多数据库持久化(JPA、R2DBC、MongoDB);云原生技术**如Docker、Kubernetes及CI/CD流程;性能优化(GraalVM Native Image、JVM调优);以及前后端分离开发(Vue 3、Spring Boot集成)。通过全栈电商平台项目实战,掌握从后端服务(用户、商品、订单)到前端应用(Vue 3、React Native)的全流程开发。
71 9
|
1月前
|
IDE Java 开发工具
Java 基础篇必背综合知识点最新技术与实操应用全面总结指南
本总结梳理了Java 17+的核心知识点与新技术,涵盖基础概念(模块化系统、GraalVM)、数据类型(文本块、模式匹配)、流程控制(增强switch)、面向对象(Record类、密封类)、常用类库(Stream API、HttpClient)、实战案例(文件处理)、构建工具(Maven、Gradle)、测试框架(JUnit 5)、开发工具(IDE、Git)及云原生开发(Spring Boot 3、Docker)。通过理论结合实操,帮助开发者掌握Java最新特性并应用于项目中。代码示例丰富,建议配合实践加深理解。
65 4
|
23天前
|
资源调度 安全 Java
Java 大数据在智能教育在线实验室设备管理与实验资源优化配置中的应用实践
本文探讨Java大数据技术在智能教育在线实验室设备管理与资源优化中的应用。通过统一接入异构设备、构建四层实时处理管道及安全防护双体系,显著提升设备利用率与实验效率。某“双一流”高校实践显示,设备利用率从41%升至89%,等待时间缩短78%。该方案降低管理成本,为教育数字化转型提供技术支持。
47 0
|
24天前
|
安全 Java API
Java 17 + 特性与现代开发技术实操应用详解
本指南聚焦Java 17+最新技术,涵盖模块化开发、Record类、模式匹配、文本块、Stream API增强、虚拟线程等核心特性,结合Spring Boot 3与Micronaut框架实战。通过实操案例解析现代Java开发技术栈,包括高性能并发编程、GraalVM原生编译及开发工具链配置。同时梳理面试高频考点,助力掌握Java新特性和实际应用,适合学习与项目实践。代码示例丰富,附带完整资源下载链接。
253 0
|
1月前
|
Java API 微服务
2025 年 Java 核心技术全面升级与实战应用详解
这份Java校招实操内容结合了最新技术趋势,涵盖核心技术、微服务架构、响应式编程、DevOps及前沿技术等六大模块。从函数式编程到Spring Cloud微服务,再到容器化与Kubernetes部署,帮助你掌握企业级开发技能。同时,提供AI集成、区块链实践和面试技巧,包括高频算法题与系统设计案例。通过学习这些内容,可应对90%以上的Java校招技术面试,并快速上手实际项目开发。资源链接:[点此获取](https://pan.quark.cn/s/14fcf913bae6)。
218 41
|
24天前
|
存储 数据采集 数据可视化
Java 大视界 -- 基于 Java 的大数据可视化在城市地下管网管理与风险预警中的应用(275)
本文系统阐述 Java 与大数据可视化技术在城市地下管网管理中的应用,涵盖数据采集、三维建模、风险预警及性能优化,结合真实案例提供可落地的技术方案。
|
24天前
|
数据采集 搜索推荐 算法
Java 大视界 -- Java 大数据在智能教育学习社区用户互动分析与社区活跃度提升中的应用(274)
本文系统阐述 Java 大数据技术在智能教育学习社区中的深度应用,涵盖数据采集架构、核心分析算法、活跃度提升策略及前沿技术探索,为教育数字化转型提供完整技术解决方案。
|
21天前
|
安全 Java API
Java 抽象类与接口在 Java17 + 开发中的现代应用实践解析
《Java抽象类与接口核心技术解析》 摘要:本文全面剖析Java抽象类与接口的核心概念与技术差异。抽象类通过模板设计实现代码复用,支持具体方法与状态管理;接口则定义行为规范,实现多态支持。文章详细对比了两者在实例化、方法实现、继承机制等方面的区别,并提供了模板方法模式(抽象类)和策略模式(接口)的典型应用示例。特别指出Java8+新特性为接口带来的灵活性提升,包括默认方法和静态方法。最后给出最佳实践建议:优先使用接口定义行为规范,通过抽象类实现代码复用,合理组合两者构建灵活架构。
34 2

热门文章

最新文章