在实际开发中,经常会遇到类似使用了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