服务版本发布问题(spring的bean生命周期控制)

5 篇文章 0 订阅


问题:

公司报警群,在项目上线时,会多出一些redis连接异常,在项目系统日志查到,多数错误信息,如下:

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

根据对应的报错类行中找出,其报错是mq消费者Consumer,在消费mq代码逻辑用到redis,大多报出上述redis线程池无连接错误。

分析:

出现问题,肯定是服务关闭时,redis线程池已经销毁了。查阅代码:


redis客户端的销毁方式

    @PreDestroy
    public void destroy() {
        if (this.scheduler != null) {
            this.scheduler.shutdown();
        }

    }

consumer客户端的销毁方式

    @Bean(
        destroyMethod = "shutdown"
    )
    @ConditionalOnMissingBean
    public MQConsumerArray messageQueueConsumer(MqConfigArray mqConfigArray) {

        。。。
        return mqConsumerArray;
    }

从上面代码看出,redis和consumer都是依赖spring管理bean的生命周期,进行管理的。首先想到,可不可以控制下,spring的bean销毁顺序来进行,或者先销毁consumer客户端,再处理其他bean的声明周期呢?

@DependsOn注解

之前了解到@DependsOn注解,可以控制bean之间的依赖,进而控制bean的创建顺序以及销毁顺序。

上手测试一下,很久没用这个注解了,上代码:

Person类

public class Person {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    @PreDestroy
    public void destroy(){
        System.out.println("Person.destroy");
    }
    public void shutdown(){
        System.out.println("Person.shutdown");
    }
}

Student类

public class Student {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @PreDestroy
    public void destroy(){
        System.out.println("Student.destroy");
    }
    public void shutdown(){
        System.out.println("Student.shutdown");
    }
}

ConfigBean类

@Configuration
public class ConfigBean {
    @DependsOn("student")
    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean
    public Person buildPerson(){
        System.out.println("Person.build");
        return new Person();
    }
    @Bean(name = "student",destroyMethod = "shutdown")
    @ConditionalOnMissingBean
    public Student buildStudent(){
        System.out.println("Student.build");
        return new Student();
    }
}

 启动项目:

关闭项目:

  

 注释掉ConfigBean的@DependsOn的使用后:

启动项目:

关闭项目:

 

 从上述实践可以看出,@DependsOn可以通过依赖关系控制促使bean之间生命周期的顺序。但是,redis和consumer客户端都是基础架构提供的,在spring实例化对应的bean时,没有对应的实例名称,所以@DependsOn用不上。

提前销毁Consumer客户端

如果控制不了redis和consumer之间的生命周期顺序,单一先销毁consumer也可以。
先看下,spring关闭方法的源码org.springframework.context.support.AbstractApplicationContext#registerShutdownHook
 

    public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            this.shutdownHook = new Thread("SpringContextShutdownHook") {
                public void run() {
                    synchronized(AbstractApplicationContext.this.startupShutdownMonitor) {
                        AbstractApplicationContext.this.doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }

    }
    protected void doClose() {
        if (this.active.get() && this.closed.compareAndSet(false, true)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Closing " + this);
            }

            LiveBeansView.unregisterApplicationContext(this);
            //发布上下文关闭事件
            try {
                this.publishEvent((ApplicationEvent)(new ContextClosedEvent(this)));
            } catch (Throwable var3) {
                this.logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", var3);
            }

            if (this.lifecycleProcessor != null) {
                try {
                    this.lifecycleProcessor.onClose();
                } catch (Throwable var2) {
                    this.logger.warn("Exception thrown from LifecycleProcessor on context close", var2);
                }
            }
            //销毁bean
            this.destroyBeans();
            this.closeBeanFactory();
            this.onClose();
            if (this.earlyApplicationListeners != null) {
                this.applicationListeners.clear();
                this.applicationListeners.addAll(this.earlyApplicationListeners);
            }

            this.active.set(false);
        }

    }

从spring关闭的过程中可以看到,spring利用了JVM虚拟机关闭的钩子。

    public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }

解决办法

上述分析可以知道,spring关闭事件发布,早于bean的销毁。可以利用关闭事件监听器提前处理需要的逻辑。鉴于这次只是解决consumer客户端没提前结束的问题。有两种选择:

1.在监听到spring关闭事件的同时,利用consumer提供的shutdown接口关闭consumer。

2.在监听到spring关闭事件之后,对mq的消息接收做拦截。

从解决方案上分析,都可以,考虑到处理问题的可控影响,选择第二种方案。代码如下:
 

/**
 * 拦截mq容器类
 */
@Component
public class MqInterceptContainer implements ApplicationListener<ContextClosedEvent> {
    /**
     * 系统活跃标识 0关闭;1活跃
     */
    public volatile static boolean activeCode = true;

    /**
     * 判断系统活跃 活跃返回true,关闭返回false
     * @return
     */
    public static boolean isActive(){
        return activeCode;
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
        activeCode = false;
    }
}

再需要拦截的地方用MqInterceptContainer.isActive()即可。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值