记一次写Java项目启动管理脚本
来源:互联网 发布:云南大学软件学院导师 编辑:程序博客网 时间:2024/06/07 06:00
最近将大概每天三分之一的精力放到了改造Ambry上(改造后的叫AmbryX,项目地址),Ambry原来最蛋疼的一点就是居然没有启动shell脚本和终止shell脚本,对于运维太不友好了。昨天下午做了个Ambry的启动管理脚本,并且推到了github上,记录下写的思路。
首先,确定下需求。
需求
网上有一个标准的java启动脚本模板,感觉上和我的需求不太符合。他的脚本一个机器上限制只能启动一个JVM进程,我的不是这样,Ambry可能在同一个机器上启动多个JVM进程,每个JVM进程的主类不一样的。我们可能需要在同一台机器上启动Ambry-Server,Ambry-Frontend,Ambry-Admin,并且需要在启动脚本里面指定启动配置。每个进程需要的配置文件不一样,而且每中进程可能启动多个,每个进程的配置文件也不一样。我们需要提供一个启动脚本,提供如下功能:
- 指定启动Ambry-Server,Ambry-Frontend或者Ambry-Admin,在启动时,指定启动的配置文件。为了方便,我们扫描指定的目录(这里是打包目录的conf目录下)
- 查看当前机器上的Ambry-Server,Ambry-Frontend或者Ambry-Admin进程
- 停止当前机器上的某个Ambry-Server,Ambry-Frontend或者Ambry-Admin进程
设计与实现
首先,是否要拆分脚本。如果要拆分脚本,那么肯定需要设置环境变量或者传递变量。对于我们这个系统来说,由于脚本不是很长,每个系统重合的地方很多。所以不需要拆分增加复杂度。
对于基本变量,我们需要如下几个:
我们部署文件包的目录结构是:
--ambry-release|--bin目录:存放脚本目录|--conf目录:存放配置文件目录|--lib目录:存放库文件目录|--logs目录:存放日志目录
#利用cd `dirname $0`切换到脚本当前目录,$0代表脚本文件,pwd获取目录绝对路径BIN_DIR=$(cd `dirname $0`;pwd)#获取项目根目录DEPLOY_DIR=$(cd $BIN_DIR;cd ..;pwd)CONF_DIR=$DEPLOY_DIR/confLIB_DIR=$DEPLOY_DIR/libLIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"`LOG_DIR=$DEPLOY_DIR/logs
总结下知识点:
- 获取脚本目录不能直接pwd,因为这时的pwd返回的是用户操作指令时所处于的目录。我们需要利用
cd
dirname $0;
先进行切换在获取pwd
对于JVM配置,调试用,测试用还有生产配置不一样。但是我们不需要在脚本中引导用户去选,为了保持脚本的纯洁性,我们在启动脚本时,传入是否是测试的参数,来决定这个脚本启动的JVM进程是否是测试用。
主要是如下参数:
#远程JVM监控JVM_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "#JMX监控JVM_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false "#在最小内存配置下运行JVM_MEM_OPTS=" -server -Xmx256m -Xms128m "
在生产配置下运行时:
#在合适的内存配置,适合的GC策略下运行(禁止代码中显示调用GC,年老带并发回收(因为是对象存储,最近存储的利用率高,过一段时间利用率低,慢慢进入年老带),页内存调大存储大对象,多层编译)JVM_MEM_OPTS=" -server -Xmx2g -Xms1g -Xmn64m -XX:PermSize=64m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 "
如果启动脚本时传入参数debug,则JVM_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "
,如果传入jmx,则JVM_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false "
,如果传入minMem,则JVM_MEM_OPTS=" -server -Xmx256m -Xms128m "
,JVM_MEM_OPTS默认是-server -Xmx2g -Xms1g -Xmn64m -XX:PermSize=64m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70
利用shell脚本实现:
#读取传入参数,遍历for arg in $*do #参数debug,则激活debug参数 if [ "debug"x = "$arg"x ] then echo "In debug mode!" JVM_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n " #参数为jmx,则激活jmx参数 elif [ "jmx"x = "$arg"x ] then echo "Enable JMX!" JVM_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false " #参数为minMem,则修改JVM_MEM_OPTS参数 elif [ "minMem"x = "$arg"x ] then echo "In min memory mode!" JVM_MEM_OPTS=" -server -Xmx256m -Xms128m " fidone
之后设计主菜单,主菜单包括三个:
- 启动服务
- 查看当前机器服务列表
- 停止服务
进入启动服务的话,用户需要选择是:
- Ambry-Server
- Ambry-Frontend
- Ambry-Admin
选择好后,用户需要指定配置文件:
请指定system.properties文件(以下文件列表为conf目录下的文件):
1. admin.properties
2. frontend.properties
3. HardwareLayout.json
4. log4j.properties
5. PartitionLayout.json
6. server.properties
之后,请指定hardwareLayout(以下文件列表为conf目录下的文件):
1. admin.properties
2. frontend.properties
3. HardwareLayout.json
4. log4j.properties
5. PartitionLayout.json
6. server.properties
之后,请指定partitionLayout(以下文件列表为conf目录下的文件):
1. admin.properties
2. frontend.properties
3. HardwareLayout.json
4. log4j.properties
5. PartitionLayout.json
6. server.properties
指定好配置文件后,启动,并将输出打印到日志文件,监控日志文件,直到出现关键字判断成功与否。
如果是选择的查看当前机器服务列表,那么继续选择是:
- Ambry-Server
- Ambry-Frontend
- Ambry-Admin
之后会展示列表。
如果选择的是停止服务,那么先查看当前机器服务列表,之后选择要停止的服务。
由于shell脚本限制,我们必须将被调用的函数放在脚本前面位置,否则会报找不到的错误。
首先我们可以抽象出如下几个函数:
由于停止服务首先需要展示服务列表,所以showServer会调用stopServer。bootServer会先调用specifyConfiguration来指定配置文件,之后运行java,最后利用watchBootstrap确认是否启动成功。
首先来实现specifyConfiguration,这个函数的功能就是帮助用户指定配置文件:
specifyConfiguration(){ echo "which is the server properties(please put your configuration files in the configuration folder:${CONF_DIR})?" #利用``执行ls命令获取CONF_DIR目录下的所有文件 configurations=`ls ${CONF_DIR}` #遍历返回,展示文件列表 count=1 #注意,shell脚本语法很严格,for do done不能在同一行,如果要在同一行,则需要加; for var in $configurations do echo "${count}. ${var}" count=`expr $count + 1` done echo -n "Please input the sequence number of the Configuration for server properties: " #获取用户选择的文件 read number count=1 for var in $configurations do #注意,shell脚本语法很严格,if then fi,如果要在同一行,则需要加; #这里已经确保了count不为空,如果输入为空则会报错 #注意,if 后面的 [ 条件 ] 之间的空格是必须的 if [ $count -eq $number ] then SYS_PROPERTIES="--serverPropsFilePath ${CONF_DIR}/${var}" fi count=`expr $count + 1` done count=1 for var in $configurations do echo "${count}. ${var}" count=`expr $count + 1` done echo -n "Please input the sequence number of the Configuration for hardwareLayout: " #获取用户选择的文件 read number count=1 for var in $configurations do if [ $count -eq $number ] then SYS_CLUSTER_PARA="--hardwareLayoutFilePath ${CONF_DIR}/${var}" fi count=`expr $count + 1` done count=1 for var in $configurations do echo "${count}. ${var}" count=`expr $count + 1` done echo -n "Please input the sequence number of the Configuration for partitionLayout: " #获取用户选择的文件 read number count=1 for var in $configurations do if [ $count -eq $number ] then SYS_CLUSTER_PARA="${SYS_CLUSTER_PARA} --partitionLayoutFilePath ${CONF_DIR}/${var}" fi count=`expr $count + 1` done}
总结如下几点知识点:
- 注意,shell脚本语法很严格,顺序逻辑语法关键字不要放在同一行
- 该有的空格必须有(例如[ expression ]),不该有的不要加(例如变量赋值的=两边)
接下来实现watchBootstrap:
watchBootstrap () { ret=0; while [ $ret -eq 0 ] do #因为每种类型的日志如果成功日志最后一行都是包含Server start,所以根据这个来判断是否启动成功 #注意指定了日志文件的文职和目录,所以待会java 启动命令最后需要加上 > ${LOG_DIR}/stdout.out output=`cat ${LOG_DIR}/stdout.out|grep "Server start"` if [[ $output != "" ]] then ret=1 else #因为有任意异常日志最后一行都是包含Server shutdown,所以根据这个来判断是否启动成功 output=`cat ${LOG_DIR}/stdout.out|grep "Server shutdown"` if [[ $output != "" ]] then ret=2 fi fi sleep 1 echo -ne "." done if [ $ret -eq 2 ] then echo -e "\n************************Failed to start $1!************************\n" cat ${LOG_DIR}/stdout.out else echo -e "\n************************$1 started!************************\n" fi}
总结如下知识点:
- 我们启动Java进程一般后台启动,这时需要知道启动成功与否,我们可以在编写Java代码,在启动时加入特殊输出来表示是否启动成功。
- 在shell脚本中,我们可以将启动的标准输出指定到一个文件中输出。之后我们不断用cat命令来查找关键字来判断是否启动成功(注意,设置好延迟,一般1秒cat一次)。
接下来实现bootServer :
bootServer () { echo -e "\n************************Please specify the module you want to start:************************\n" echo "1. Ambry-Server" echo "2. Ambry-Frontend" echo "3. Ambry-Admin" echo -n "Your selection is(input 1,2 or 3):" read MODULE echo "" case $MODULE in 1) specifyConfiguration echo "Starting Ambry-Server" # 2>&1 代表(0是标准输入,1是标准输出,2是标准错误输出)将标准错误输出也输出到标准输出,末尾的 &代表后台启动,> ${LOG_DIR}/stdout.out代表将所有标准输出输出到文件${LOG_DIR}/stdout.out中 java $JVM_DEBUG_OPTS $JVM_JMX_OPTS $JVM_MEM_OPTS $JVM_PARAS -classpath $CONF_DIR:$LIB_JARS com.github.ambry.server.AmbryMain ${SYS_PROPERTIES} ${SYS_CLUSTER_PARA} > ${LOG_DIR}/stdout.out 2>&1 & watchBootstrap "Ambry-Server" echo -e "\n************************************************************************\n" ;; 2) specifyConfiguration echo "Starting Ambry-Frontend" java $JVM_DEBUG_OPTS $JVM_JMX_OPTS $JVM_MEM_OPTS $JVM_PARAS -classpath $CONF_DIR:$LIB_JARS com.github.ambry.frontend.AmbryFrontendMain ${SYS_PROPERTIES} ${SYS_CLUSTER_PARA} > ${LOG_DIR}/stdout.out 2>&1 & watchBootstrap "Ambry-Frontend" echo -e "\n************************************************************************\n" ;; 3) specifyConfiguration echo "Starting Ambry-Admin" java $JVM_DEBUG_OPTS $JVM_JMX_OPTS $JVM_MEM_OPTS $JVM_PARAS -classpath $CONF_DIR:$LIB_JARS com.github.ambry.admin.AdminMain ${SYS_PROPERTIES} ${SYS_CLUSTER_PARA} > ${LOG_DIR}/stdout.out 2>&1 & watchBootstrap "Ambry-Admin" echo -e "\n************************************************************************\n" ;; esac}
总结如下知识点:
- 2>&1 代表(0是标准输入,1是标准输出,2是标准错误输出)将标准错误输出也输出到标准输出,末尾的 &代表后台启动,
> ${LOG_DIR}/stdout.out
代表将所有标准输出输出到文件${LOG_DIR}/stdout.out中
实现了这些,stopServer还有showServer就很简单了,这里放上整个脚本:
#!/bin/bash# Author : Hash Zhang# Constants definition:#利用cd `dirname $0`切换到脚本当前目录,$0代表脚本文件,pwd获取目录绝对路径BIN_DIR=$(cd `dirname $0`;pwd)#获取项目根目录DEPLOY_DIR=$(cd $BIN_DIR;cd ..;pwd)CONF_DIR=$DEPLOY_DIR/confLIB_DIR=$DEPLOY_DIR/libLIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"`LOG_DIR=$DEPLOY_DIR/logsJVM_PARAS=" -Dlog4j.configuration=file:${CONF_DIR}/log4j.properties "JVM_DEBUG_OPTS=""JVM_JMX_OPTS=""JVM_MEM_OPTS=" -server -Xmx2g -Xms1g -Xmn64m -XX:PermSize=64m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 "SYS_PROPERTIES=""SYS_CLUSTER_PARA=""#读取传入参数,遍历for arg in $*do #参数debug,则激活debug参数 if [ "debug"x = "$arg"x ] then echo "In debug mode!" JVM_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n " #参数为jmx,则激活jmx参数 elif [ "jmx"x = "$arg"x ] then echo "Enable JMX!" JVM_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false " #参数为minMem,则修改JVM_MEM_OPTS参数 elif [ "minMem"x = "$arg"x ] then echo "In min memory mode!" JVM_MEM_OPTS=" -server -Xmx256m -Xms128m " fidonewatchBootstrap () { ret=0; while [ $ret -eq 0 ] do #因为每种类型的日志如果成功日志最后一行都是包含Server start,所以根据这个来判断是否启动成功 #注意指定了日志文件的文职和目录,所以待会java 启动命令最后需要加上 > ${LOG_DIR}/stdout.out output=`cat ${LOG_DIR}/stdout.out|grep "Server start"` if [[ $output != "" ]] then ret=1 else #因为有任意异常日志最后一行都是包含Server shutdown,所以根据这个来判断是否启动成功 output=`cat ${LOG_DIR}/stdout.out|grep "Server shutdown"` if [[ $output != "" ]] then ret=2 fi fi sleep 1 echo -ne "." done if [ $ret -eq 2 ] then echo -e "\n************************Failed to start $1!************************\n" cat ${LOG_DIR}/stdout.out else echo -e "\n************************$1 started!************************\n" fi}specifyConfiguration(){ echo "which is the server properties(please put your configuration files in the configuration folder:${CONF_DIR})?" #利用``执行ls命令获取CONF_DIR目录下的所有文件 configurations=`ls ${CONF_DIR}` #遍历返回,展示文件列表 count=1 #注意,shell脚本语法很严格,for do done不能在同一行,如果要在同一行,则需要加; for var in $configurations do echo "${count}. ${var}" count=`expr $count + 1` done echo -n "Please input the sequence number of the Configuration for server properties: " #获取用户选择的文件 read number count=1 for var in $configurations do #注意,shell脚本语法很严格,if then fi,如果要在同一行,则需要加; #这里已经确保了count不为空,如果输入为空则会报错 #注意,if 后面的 [ 条件 ] 之间的空格是必须的 if [ $count -eq $number ] then SYS_PROPERTIES="--serverPropsFilePath ${CONF_DIR}/${var}" fi count=`expr $count + 1` done count=1 for var in $configurations do echo "${count}. ${var}" count=`expr $count + 1` done echo -n "Please input the sequence number of the Configuration for hardwareLayout: " #获取用户选择的文件 read number count=1 for var in $configurations do if [ $count -eq $number ] then SYS_CLUSTER_PARA="--hardwareLayoutFilePath ${CONF_DIR}/${var}" fi count=`expr $count + 1` done count=1 for var in $configurations do echo "${count}. ${var}" count=`expr $count + 1` done echo -n "Please input the sequence number of the Configuration for partitionLayout: " #获取用户选择的文件 read number count=1 for var in $configurations do if [ $count -eq $number ] then SYS_CLUSTER_PARA="${SYS_CLUSTER_PARA} --partitionLayoutFilePath ${CONF_DIR}/${var}" fi count=`expr $count + 1` done}bootServer () { echo -e "\n************************Please specify the module you want to start:************************\n" echo "1. Ambry-Server" echo "2. Ambry-Frontend" echo "3. Ambry-Admin" echo -n "Your selection is(input 1,2 or 3):" read MODULE echo "" case $MODULE in 1) specifyConfiguration echo "Starting Ambry-Server" # 2>&1 代表(0是标准输入,1是标准输出,2是标准错误输出)将标准错误输出也输出到标准输出,末尾的 &代表后台启动,> ${LOG_DIR}/stdout.out代表将所有标准输出输出到文件${LOG_DIR}/stdout.out中 java $JVM_DEBUG_OPTS $JVM_JMX_OPTS $JVM_MEM_OPTS $JVM_PARAS -classpath $CONF_DIR:$LIB_JARS com.github.ambry.server.AmbryMain ${SYS_PROPERTIES} ${SYS_CLUSTER_PARA} > ${LOG_DIR}/stdout.out 2>&1 & watchBootstrap "Ambry-Server" echo -e "\n************************************************************************\n" ;; 2) specifyConfiguration echo "Starting Ambry-Frontend" java $JVM_DEBUG_OPTS $JVM_JMX_OPTS $JVM_MEM_OPTS $JVM_PARAS -classpath $CONF_DIR:$LIB_JARS com.github.ambry.frontend.AmbryFrontendMain ${SYS_PROPERTIES} ${SYS_CLUSTER_PARA} > ${LOG_DIR}/stdout.out 2>&1 & watchBootstrap "Ambry-Frontend" echo -e "\n************************************************************************\n" ;; 3) specifyConfiguration echo "Starting Ambry-Admin" java $JVM_DEBUG_OPTS $JVM_JMX_OPTS $JVM_MEM_OPTS $JVM_PARAS -classpath $CONF_DIR:$LIB_JARS com.github.ambry.admin.AdminMain ${SYS_PROPERTIES} ${SYS_CLUSTER_PARA} > ${LOG_DIR}/stdout.out 2>&1 & watchBootstrap "Ambry-Admin" echo -e "\n************************************************************************\n" ;; esac}stopServer (){ count=1 pids=$1 for var in $pids do echo "${count}. ${var}" count=`expr $count + 1` done if [ -n "$2" -a $count -gt 1 ] then echo -n "Please input the sequence number of the PID you want to stop: " read pid count=1 for var in $pids do if [ $count -eq $pid ] then ret=`kill -9 "${var}"` echo $ret fi count=`expr $count + 1` done elif [ $count -lt 2 ] then echo "No Alive Ambry-Server exists!" fi}showServer () { echo "" echo "1. Ambry-Server" echo "2. Ambry-Frontend" echo "3. Ambry-Admin" echo -n "Your selection is(input 1,2 or 3):" read MODULE echo "" case $MODULE in 1) pids=`ps -ef|grep ambry|grep "${DEPLOY_DIR}"|grep com.github.ambry.server.AmbryMain|awk '{print $2}'` echo -e "\n************************Current Ambry-Server Pids:************************\n" stopServer $pids $1 echo -e "\n************************************************************************\n" ;; 2) pids=`ps -ef|grep ambry|grep "${DEPLOY_DIR}"|grep com.github.ambry.frontend.AmbryFrontendMain|awk '{print $2}'` echo -e "\n************************Current Ambry-Frontend Pids:************************\n" stopServer $pids $1 echo -e "\n************************************************************************\n" ;; 3) pids=`ps -ef|grep ambry|grep "${DEPLOY_DIR}"|grep com.github.ambry.admin.AdminMain|awk '{print $2}'` echo -e "\n************************Current Ambry-Admin Pids:************************\n" stopServer $pids $1 echo -e "\n************************************************************************\n" ;; esac}while [ 1 = 1 ]do echo -e "\n************************Welcome to ambry!************************\n" echo "1. Boot a server" echo "2. Watch the server list in current host" echo "3. Stop a server" echo -n "Your selection is(input 1,2 or 3):" read SELECTION echo "" case $SELECTION in 1) bootServer ;; 2) showServer ;; 3) showServer true ;; esacdone
- 记一次写Java项目启动管理脚本
- Java web项目启动后运行一次的方法
- 写脚本,布署项目
- 项目管理-项目启动
- 项目管理--项目启动
- 一次项目管理交流会总结
- 项目的启动脚本
- 记一次听岳老师“项目管理”讲座
- java maven项目常用 build配置及启动脚本
- JIRA纯java写的开源项目管理平台
- 项目管理-项目启动会
- java应用启动脚本
- java启动停止脚本
- java程序启动脚本
- java 启动脚本
- tomcat部署项目,仅启动一次
- 启动一个shell脚本项目
- django 项目中脚本启动
- Android N上语言列表
- 01-redis学习第一章
- 代码质量之命名(一)[部分看来的部分自己感悟]
- Http响应案例、Http响应编码问题、Servlet项目编码问题总结图
- 初学swift_002
- 记一次写Java项目启动管理脚本
- ROS进二阶学习笔记(2)- SMACH:用状态机来管理机器人任务
- Oracle数据库简单查询的流程
- 协同过滤算法笔记
- noip模拟11.16~11.17总结
- linux kernel总结
- 07-redis学习第七章
- ASP.NET Ajax 三层架构 考试系统实现(试卷模块,考试模块,评分模块,计时模块)
- 当你改变了看世界的方式,你就能改变所看的世界