应用初始化完成后and应用销毁前自定义操作

在实际开发中,经常会遇到类似使用了mq消息队列,需要应用完全实例化或者加载完毕后再让消费者
开始消费;在发版或者关闭程序前,又希望能先将mq消费者下线,避免出现一直有程序在运行,关闭
不掉应用,最后kill -9则会导致程序中断。

结合阿里云社区、csdn等博客和自己的摸索,总结如下

一:监听应用初始化完成时

  • Springboot中使用如下方式

    /**
     * 【项目中可能会用到很多在@bean之后的注解和实例等  如@EventListener注解】
     *
     * @author bincain
     */
    @Slf4j
    @Component
    public class XxxxxListener implements ApplicationListener<ApplicationReadyEvent> {
    
        @Override
        public void onApplicationEvent(ApplicationReadyEvent event) {
            // root application context 没有parent,最顶层.【防止多次进入流程】
            if (event.getApplicationContext().getParent() == null) {
                // 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
            }
        }
    }
    
  • spring 使用如下方式

    /**
     *  在应用所有都准备好后再注册成功
     *
     * @author bincain
     */
    @Slf4j
    @Configuration
    public class XxxxxListener implements ApplicationListener<ContextRefreshedEvent> {
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            // root application context 没有parent,最顶层.【防止多次进入流程】
            if (event.getApplicationContext().getParent() == null) {
                //需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
            }
        }
    }
    

二:监听应用关闭之前

项目kill时 触发应用停机之前 关闭服务 【可行方案】
  • 实现 DisposableBean接口 覆写destroy方法
  • 实现 SmartLifecycle 覆写stop方法
  • Runtime.getRuntime().addShutdownHook —> 通过注册回调调用停机的逻辑ProtocolConfig.destroyAll()
  • @PreDestroy 添加到某个方法上 【关闭的时间较往后,需要提前关闭的如:mq消费者下线,则不适合】
  • springboot引入依赖spring-boot-starter-actuator 2.x 【 较麻烦: 需要提供关闭的接口 需要做好安全验证】
几种常见服务的优雅关闭方式
  • Dubbo 优雅停机 详情请参看

    • dubbo的优雅停机是建立在JVM的addShutdownHook回调的机制上的,通过注册回调调用停机的逻辑ProtocolConfig.destroyAll()

    • ProtocolConfig.destroyAll()执行逻辑是:1、关闭注册中心;2、关闭发布协议服务。

    • 关闭注册中心:AbstractRegistryFactory.destroyAll()。

    • 关闭发布的协议服务:protocol.destroy()。

      public abstract class AbstractConfig implements Serializable {
          static {
              Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
                  public void run() {
                      if (logger.isInfoEnabled()) {
                          logger.info("Run shutdown hook now.");
                      }
                      ProtocolConfig.destroyAll();
                  }
              }, "DubboShutdownHook"));
          }
      }
      
      public class ProtocolConfig extends AbstractConfig {
      
          public static void destroyAll() {
              if (!destroyed.compareAndSet(false, true)) {
                  return;
              }
      
              // 关闭注册中心
              AbstractRegistryFactory.destroyAll();
      
              // 关闭所有已发布的协议如dubbo服务
              ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
              for (String protocolName : loader.getLoadedExtensions()) {
                  try {
                      Protocol protocol = loader.getLoadedExtension(protocolName);
                      if (protocol != null) {
                          protocol.destroy();
                      }
                  } catch (Throwable t) {
                      logger.warn(t.getMessage(), t);
                  }
              }
          }
      }
      
  • Xxl-Job 停机原理

    • xxl-job停机是实现 DisposableBean接口并 覆写 destroy方法

      public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
          @Override
          public void destroy() throws Exception {
              // 销毁服务在这里执行就行了
          }
      }
      
  • 自己遇到的需要先下线rocketmq消费者

    • 实现SmartLifecycle接口并 覆写stop 方法

      	
      @Slf4j
      @Component
      public class CommonSmartLifecycle implements SmartLifecycle {
      
      
          /**
           * smartLifecycle 运行状态,  默认:false
           */
          private volatile boolean running = false;
          /**
           * 消费者 实例 列表map    bean的名字-bean实例
           * 必须要添加 required=false
           * 必须要添加 Lazy 懒加载【注入bean方式  1:@bean注解   2:spring的BeanFactory在onApplicationEvent中创建爱你】
           */
          @Lazy
          @Autowired(required = false)
          Map<String, RocketMqConsumerService> rocketMqConsumerServiceMap;
      
          @Override
          public void start() {
              log.info("CommonSmartLifecycle-bean初始化完成");
              running = true;
              log.info("========> smartLifecycle 检查运行状态: {}  <===========", running);
          }
      
          @Override
          public void stop() {
              log.info(" 进入自定义CommonSmartLifecycle-提前销毁BEAN的逻辑========>");
              if (MapUtils.isNotEmpty(rocketMqConsumerServiceMap)) {
                  log.info("RocketMQ 存在消费者【{}】个-开始关闭RocketMq的消费者========>", rocketMqConsumerServiceMap.size());
                  rocketMqConsumerServiceMap.forEach((beanName, rocketMqConsumerService) -> {
                      try {
                          rocketMqConsumerService.shutdownConsumer();
                      } catch (Exception e) {
                          log.info("如果下方是报错:Shutdown in progress 可先忽略,此异常是 traceMessage关闭时触发的,暂时看影响不大");
                          log.warn("{} 环境下 topic:{}  消费者实例shutdown关闭打印异常,message:{}", env, topic, e.getMessage(), e);
                      }
                  });
              } else {
                  log.info(" RocketMQ 不存在需要关闭的RocketMq的消费者========>");
              }
              // 如果有其他的需要提前关闭的服务 可再次依次添加 【注意各项服务之间的依赖关系】
              try {
                  log.info(" 关闭需要提前关闭的服务后,SLEEP 20_000毫秒 稍微等待一下");
                  Thread.sleep(20_000);
              } catch (InterruptedException interruptedException) {
                  log.info(" 等待20_000毫秒出现异常", interruptedException);
                  Thread.currentThread().interrupt();
              }
              log.info("[ ========>关闭自定义的SmartLifecycle完美结束 stop ending <===========");
              running = false;
          }
      
          @Override
          public boolean isRunning() {
              log.info("========> smartLifecycle 检查运行状态: {}  <===========", running);
              return running;
          }
      
          /// 以下override是为了兼容低版本的spring [高版本中SmartLifecycle 中已default默认实现]
      
          @Override
          public boolean isAutoStartup() {
              return true;
          }
      
          @Override
          public void stop(Runnable callback) {
              stop();
              callback.run();
          }
      
          @Override
          public int getPhase() {
              return Integer.MAX_VALUE;
          }
      }
      	
      
  • spring容器的关闭顺序 【图来源网络】
    在这里插入图片描述

  • 以上的几种方式都是建立在运维的 kill 不是-9 直接杀掉进程的基础上的。 需要kill发出 SIGTERM信号 【2 or 15】

  • 附上最近测试的kill应用程序的shell脚本 【实测 centos6 kill -2未生效 使用默认的15】

    #!/usr/bin/env bash
    echo '######## kill springboot #########'
    springBootName=$1
    
    findName="${springBootName}/"
    ## 第一步: 检测需要关闭的PID
    echo "springBootName ${findName}"
    pidlist=`ps -ef |grep ${findName}  |grep -v "grep"|awk '{print $2}'`
    echo "一共有需要kill的pid- list:${pidlist}"
    
    ## 第二步: kill [-15] PID
    echo "kill grep ${findName} list:${pidlist}"
    kill  $pidlist
    ## 休眠3秒【程序关的没有那么快】
    sleep 3
    ## 第三步: 检测是否已经不存在 【默认30秒内】
    echo "开始检测一是否已关闭"
    timecount=1
    while [ $timecount -lt 30 ]
    do
        pidlist=`ps -ef |grep ${findName}  |grep -v "grep"|awk '{print $2}'`
        echo "待检测: ${pidlist}"
        if [ ! -n "${pidlist}" ]; then
                break
        else
                echo "第${timecount} 秒检测  普通的kill命令 还没有关闭"
                timecount=$(($timecount+1))
                sleep 1
        fi
    done
    ## 第四步: 检测如果没有关闭 则强制 kill -9结束进程
    echo "开始检测二是否已关闭"
    pidlist=`ps -ef |grep ${findName}  |grep -v "grep"|awk '{print $2}'`
    ## 判断是否为空
    if [ ! -n "${pidlist}" ]; then
            echo "kill -15生效了 非常棒"
    else
            ## 判断是否不存在
            if [ -z "${pidlist}" ]; then
                    echo "kill -15生效了 非常棒!!!!"
            else
                    echo "开始kill -9强制进程结束"
                    kill -9 $pidlist
            fi
    fi
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值