需求:项目上线过程中,造成部分打入请求不可用,直接kill,太粗暴,随引入spring优雅关闭
version : Springboot 2.2.5.RELEASE
1、pom文件引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、yml配置
management:
# 线上最好只允许本机请求
# server:
# address: 127.0.0.1
# port: 12581
endpoints:
web:
exposure:
include: "*"
path-mapping:
shutdown: "/gateway_shutdown"
endpoint:
shutdown:
enabled: true
3、全局config
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* @author bigwang
* @date 2020/3/23 4:29 下午
* @description
**/
@Configuration
public class GracefulShutdownConfig {
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
tomcatServletWebServerFactory.addConnectorCustomizers(gracefulShutdown());
return tomcatServletWebServerFactory;
}
private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private final Logger logger = LoggerFactory.getLogger(GracefulShutdown.class);
private volatile Connector connector;
private final int waitTime = 10;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
logger.info("close event:{}", contextClosedEvent);
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
try {
if (executor instanceof ThreadPoolExecutor) {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
logger.warn("Tomcat 进程在" + waitTime + " 秒内无法结束,尝试强制结束");
}
}
} catch (Exception e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
}
4、启动测试
curl -X POST http://localhost:8090/actuator/gateway_shutdown
控制台输出:{"message":"Shutting down, bye..."}%
配置结束!下面为上线脚本
1、model-1
#!/bin/bash
#抛异常终止程序
set -e
# 平滑关闭和启动 Spring Boot 程序
#设置端口
SERVER_PORT="8090"
#设置应用名称
JAR_NAME="shutdown-0.0.1-SNAPSHOT"
#设置 JAVA 启动参数
#JAVA_OPTIONS="-server -Xms1024M -Xmx1024M"
#Actuator 方式远程关闭应用
curl -X POST "http://127.0.0.1:$SERVER_PORT/actuator/gateway_shutdown"
#检查程序是否在运行
is_exist(){
pid=`ps -ef|grep $JAR_NAME|grep -v grep|awk '{print $2}'`
#如果不存在返回1,存在返回0
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
}
#循环遍历应用端口是否被使用,作为应用运作状态的标志
echo "关闭旧应用开始"
UP_STATUS=1
while(( $UP_STATUS>0 ))
do
UP_STATUS=$(lsof -i:"$SERVER_PORT" | wc -l)
done
echo "\n关闭旧应用结束"
sleep 1
#查看端口是否关闭
is_exist
if [ $? -eq 0 ]; then
echo "${JAR_NAME} is already running. pid=${pid}"
kill -9 $pid
fi
sleep 3
# 备份应用
echo "备份开始。。。"
cd /data/service
backupname="${JAR_NAME}.jar.`date +%y%m%d%H`"
cp "$JAR_NAME".jar backup/"$backupname"
echo "备份开始结束 backup/$backupname"
sleep 1
rm -rf "$JAR_NAME".jar
sudo mv /tmp/"$JAR_NAME".jar /data/service/"$JAR_NAME".jar
echo "启动应用开始"
source /etc/profile && cd /data/service/
#非挂起方式启动应用,并且跟踪启动日志文件
nohup java -jar "$JAR_NAME".jar >> logs/`date +"%Y_%m_%d.log"` 2>>logs/`date +"%Y_%m_%d.err"` &
echo "启动应用中" && tail -20f logs/`date +"%Y_%m_%d.log"`
2、model-2
export JAVA_HOME=/usr/local/java
export JRE_HOME=/$JAVA_HOME/jre
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar
export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
#这里可替换为你自己的执行程序,其他代码无需更改
APP_NAME=/data/servers/service-memory/memory-0.0.1-SNAPSHOT.jar
#使用说明,用来提示输入参数
usage() {
echo "Usage: sh shell-memory-service.sh [start|stop|restart|status]"
exit 1
}
#检查程序是否在运行
is_exist(){
pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}'`
#如果不存在返回1,存在返回0
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
}
#启动方法
start(){
is_exist
if [ $? -eq 0 ]; then
echo "${APP_NAME} is already running. pid=${pid}"
else
nohup java -jar ${APP_NAME} --server.port=8080 >shell-memory-service.out 2>&1 &
fi
}
#停止方法
stop(){
is_exist
if [ $? -eq "0" ]; then
kill -9 $pid
else
echo "${APP_NAME} is not running"
fi
}
#输出运行状态
status(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is running. Pid is ${pid}"
else
echo "${APP_NAME} is NOT running."
fi
}
#重启
restart(){
stop
sleep 5
start
}
#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
"start")
start
;;
"stop")
stop
;;
"status")
status
;;
"restart")
restart
;;
*)
usage
;;
esac