背景
传统springboot构建的项目打包后形成一个巨大的可执行jar包、配置文件不易修改,线上部署更新包传输时间长。基于此规范执行mvn package命令后将会形成结构分明易于部署的tar包,将开发的业务代码与依赖的jar进行分离,为线上环境提供增量部署能力。
效果
基于此规范以demo-package项目为例,最终生成的tar包目录结构如下:
demo-package
├── bin // 运行脚本目录
│ └── startup.sh // 启动脚本
│ └── shutdown.sh // 停止脚本
│ └── daemon.sh // 守护进程脚本
├── config // 配置文件目录
│ └── logback.xml // 日志配置
│ └── banner.txt // banner配置
│ └── application.yml // 应用权限
├── lib // 项目依赖目录
├── demo-package.jar // 业务代码jar包
一、
添加运行脚本
1.
新建目录
${basedir}/src/main/bin
2.
添加启动脚本
在路径${basedir}/src/main/bin下添加startup.sh文件,脚本支持检查java运行环境,配置了gc日志,内存溢出时生成堆栈快照,默认使用prod配置文件,使用其他配置文件时通过-m参数修改,如果-m dev则默认开启调试端口5005;
#!/bin/bash
#
########################################################
#
# 脚本名称:startup.sh
#
# 功 能: 服务启动
#
# 用 法: sh startup.sh
#
# 作 者: xiechenglong
#
# 日 期: 2023-01-12
#
########################################################
#
cygwin=false
darwin=false
os400=false
case "`uname`" in
CYGWIN*) cygwin=true;;
Darwin*) darwin=true;;
OS400*) os400=true;;
esac
error_exit ()
{
echo "ERROR: $1 !!"
exit 1
}
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/opt/taobao/java
[ ! -e "$JAVA_HOME/bin/java" ] && unset JAVA_HOME
if [ -z "$JAVA_HOME" ]; then
if $darwin; then
if [ -x '/usr/libexec/java_home' ] ; then
export JAVA_HOME=`/usr/libexec/java_home`
elif [ -d "/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home" ]; then
export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home"
fi
else
JAVA_PATH=`dirname $(readlink -f $(which javac))`
if [ "x$JAVA_PATH" != "x" ]; then
export JAVA_HOME=`dirname $JAVA_PATH 2>/dev/null`
fi
fi
if [ -z "$JAVA_HOME" ]; then
error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better!"
fi
fi
export MODE="prod"
while getopts ":m:" opt
do
case $opt in
m)
MODE=$OPTARG;;
?)
echo "Unknown parameter"
exit 1;;
esac
done
export JAVA_HOME
export JAVA="$JAVA_HOME/bin/java"
export BASE_DIR=`cd $(dirname $0)/..; pwd`
for ITEM in $(ls ${BASE_DIR}); do
if [ ${ITEM##*.} = jar ]; then
SERVICE_JAR=${ITEM}
fi
done
export SERVICE_NAME=${SERVICE_JAR%.*}
#jvm configuration
JAVA_OPT="${JAVA_OPT} -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${BASE_DIR}/logs/heapdump.hprof"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"
JAVA_MAJOR_VERSION=$($JAVA -version 2>&1 | sed -E -n 's/.* version "([0-9]*).*$/\1/p')
if [[ "$JAVA_MAJOR_VERSION" -ge "9" ]] ; then
JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${BASE_DIR}/logs/gc.log:time,tags:filecount=10,filesize=102400"
else
JAVA_OPT="${JAVA_OPT} -Xloggc:${BASE_DIR}/logs/gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M"
fi
if [[ "${MODE}" == "dev" ]]; then
echo "${SERVICE_NAME} is starting with dev"
JAVA_OPT="${JAVA_OPT} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
fi
JAVA_OPT="${JAVA_OPT} -Dservice.home=${BASE_DIR}"
JAVA_OPT="${JAVA_OPT} --Dspring.profiles.active=${MODE}"
JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/${SERVICE_NAME}.jar"
# check the start.out log output file
if [ ! -d "${BASE_DIR}/logs" ]; then
mkdir ${BASE_DIR}/logs
fi
if [ ! -f "${BASE_DIR}/logs/start.out" ]; then
touch "${BASE_DIR}/logs/start.out"
fi
# start
echo "$JAVA ${JAVA_OPT}" > ${BASE_DIR}/logs/start.out 2>&1 &
nohup "$JAVA" ${JAVA_OPT} >> ${BASE_DIR}/logs/start.out 2>&1 &
echo "${SERVICE_NAME} is starting,you can check the ${BASE_DIR}/logs/start.out"
3.
添加停止脚本
在路径${basedir}/src/main/bin下添加shutdown.sh文件
#!/bin/bash
#
########################################################
#
# 脚本名称:shutdown.sh
#
# 功 能: 服务停止
#
# 用 法: sh shutdown.sh
#
# 作 者: xiechenglong
#
# 日 期: 2023-01-12
#
########################################################
#
export BASE_DIR=`cd $(dirname $0)/..; pwd`
for ITEM in $(ls ${BASE_DIR}); do
if [ ${ITEM##*.} = jar ]; then
SERVICE_JAR=${ITEM}
fi
done
export SERVICE_NAME=${SERVICE_JAR%.*}
echo "stop ${SERVICE_NAME}"
PID=""
query(){
PID=`ps -ef |grep java|grep ${SERVICE_NAME}|grep -v grep|awk '{print $2}'`
}
query
if [ x"$PID" != x"" ]; then
kill -TERM $PID
echo "${SERVICE_NAME} (pid:$PID) exiting..."
while [ x"$PID" != x"" ]
do
sleep 1
query
done
echo "${SERVICE_NAME} exited."
else
echo "${SERVICE_NAME} already stopped."
fi
4.
添加守护进程脚本
在路径${basedir}/src/main/bin下添加daemon.sh文件,守护进程脚本用于系统内存溢出时自动重启,需要nohup后台启动。
#!/bin/bash
#
########################################################
#
# 脚本名称:daemon.sh
#
# 功 能: 守护进程
#
# 用 法: sh daemon.sh
#
# 作 者: xiechenglong
#
# 日 期: 2023-02-24
#
########################################################
#
#根目录
BASE_DIR=`cd $(dirname $0)/..; pwd`
#获取服务名
for ITEM in $(ls ${BASE_DIR}); do
if [ ${ITEM##*.} = jar ]; then
SERVICE_JAR=${ITEM}
fi
done
SERVICE_NAME=${SERVICE_JAR%.*}
#定义监听日志文件
LOG_FILE=${BASE_DIR}/logs/error.log
#定义监听日志行数
LOG_NO=50
#循环监听OutOfMemoryError
while true
do
if [ `tail -n ${LOG_NO} ${LOG_FILE} | grep OutOfMemoryError | wc -l` -ne 0 ];then
echo "The ${SERVICE_NAME} is restarting"
sh ${BASE_DIR}/bin/shutdown.sh && sh ${BASE_DIR}/bin/startup.sh
for (( i=0; i<${LOG_NO}; i++ ))
do
echo "The ${SERVICE_NAME} is restarted" >> ${LOG_FILE}
done
fi
sleep 5
done
二、
添加assembly.xml
在路径${basedir}/src/main/resources下添加assembly.xml文件
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<!-- 可自定义,这里指定的是项目名称,打包唯一标识-->
<id>${project.version}</id>
<formats>
<!-- 打包的类型,如果有N个,将会打N个类型的包 -->
<format>tar.gz</format>
</formats>
<!--外层是否包一层-->
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<!-- config files -->
<fileSet>
<directory>${basedir}/src/main/resources</directory>
<excludes>
<exclude>assembly.xml</exclude>
</excludes>
<includes>
<include>*.properties</include>
<include>*.yml</include>
<include>*.xml</include>
<include>banner.txt</include>
</includes>
<!--设置文件权限-->
<fileMode>0644</fileMode>
<!--设置配置文件输出的外部文件夹-->
<outputDirectory>/config</outputDirectory>
</fileSet>
<!-- scripts -->
<fileSet>
<directory>${basedir}/src/main/bin</directory>
<includes>
<include>*.sh</include>
</includes>
<fileMode>0755</fileMode>
<lineEnding>unix</lineEnding>
<!--设置项目相关脚本输出的外部文件夹-->
<outputDirectory>/bin</outputDirectory>
</fileSet>
<!-- executable jar -->
<fileSet>
<directory>${project.build.directory}</directory>
<includes>
<include>*.jar</include>
<!--注意此处是将项目依赖的第三方jar全部copy至assembly打包出来的目录中
如果没有此段,则lib目录是在target目录下,而不会存放于assembly打包的目录中-->
<include>/lib/*</include>
</includes>
<fileMode>0755</fileMode>
<!--设置项目jar以及项目依赖的第三方jar输出的外部文件夹-->
<outputDirectory>/</outputDirectory>
</fileSet>
</fileSets>
</assembly>
三、
添加logback.xml
在路径${basedir}/src/main/resources下添加logback.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 日志存放路径 -->
<property name="log.path" value="${service.home}/logs"/>
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.inspur" level="info"/>
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn"/>
<root level="info">
<appender-ref ref="console"/>
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info"/>
<appender-ref ref="file_error"/>
</root>
</configuration>
四、
添加banner.txt
在路径${basedir}/src/main/resources下添加banner.txt文件
Spring Boot Version: ${spring-boot.version}
Spring Application Name: ${spring.application.name}
五、
修改pom.xml
pom.xml文件中build标签修改为如下内容,mainClass标签中的启动类按照实际修改
<build>
<!--jar包名字 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- maven编译 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<!-- 打包时跳过测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!--将项目中代码文件打成jar包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<!-- 打包后的jar包中不包括配置文件 -->
<!-- 通常是指classpath下目录下的文件,这样可以避免编写时的找不到相应文件 -->
<excludes>
<exclude>*.properties</exclude>
<exclude>*.yml</exclude>
<exclude>*.xml</exclude>
<exclude>banner.txt</exclude>
</excludes>
<archive>
<manifest>
<!-- 项目启动类 -->
<mainClass>com.inspur.demo.DemoPackageApplication</mainClass>
<!-- 依赖的jar的目录前缀 -->
<classpathPrefix>./lib/</classpathPrefix>
<!-- 将依赖加进 Class-Path -->
<addClasspath>true</addClasspath>
<!-- 是否使用唯一的时间戳快照版本而不是-快照版本。默认值为 true -->
<useUniqueVersions>false</useUniqueVersions>
</manifest>
<!-- 将config目录加入classpath目录 -->
<manifestEntries>
<Class-Path>./config/</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!--设置jar所依赖的三方jar包存放的路径 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dep</id>
<phase>prepare-package</phase>
<goals>
<goal>
copy-dependencies
</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<!--利用maven的assembly插件打包-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<configuration>
<!--生成的tar包是否携带版本号-->
<appendAssemblyId>false</appendAssemblyId>
<outputDirectory>target</outputDirectory>
<descriptors>
<descriptor>src/main/resources/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>${project.version}</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
版权声明:本文为chenglongxie原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。