springboot 优雅停机+线上部署shell脚本

本文详细介绍如何在Spring Boot项目中实现优雅的关闭过程,避免直接kill造成的请求不可用问题。通过引入actuator依赖,配置优雅关闭策略,实现服务的平滑停机。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需求:项目上线过程中,造成部分打入请求不可用,直接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

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值