起因
起源:在使用Redisson实现分布式定时调度的过程中,遇到一个非常奇怪的问题:经过一些操作后,调度器会报一个莫名其妙的错误,status is shutdown,经过多次观察发现是在rancher平台重新发布Spring Boot项目后就会报这个错误,怀疑和Spring Boot优雅关闭有关系,所以进行一个追溯确认的过程。
报错信息:

Reids数据:

然后翻阅 RScheduledExecutorService 的方法,发现shutdown方法代码执行的 lua 脚本和目前看到的结果很相似,但是,身为程序员的我们是看证据的,没有证据都是耍流氓。

经过
方案1
尝试使用AOP拦截目标对象的方法,然后触发优雅关闭,看看是否能被拦截到,如果拦截到的话,它的调用栈信息也就随之出来了。
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Slf4j
@Aspect
public class ExecutorServiceAspect {
//定义切点
@Pointcut("execution(public * org.redisson.*.*(..))")
public void pointCut(){}
// 抛异常
@Around("pointCut()")
public Object aroundMethod(ProceedingJoinPoint pjd) throws Throwable {
log.info("invoke method. {}", pjd.getTarget());
throw new RuntimeException("切点被捕捉到!");
}
}
然后调用使用postman调用http://127.0.0.1:8080/actuator/shutdown,实现优雅关闭,但是多次尝试,改变切点信息,发现都不能切到对象。后来仔细想了想,这个对象被代理了,甚至多层代理,肯定切不到这个对象的实际操作,所以这个方案失败。
方案2
在Redisson包中的shutdown等方法入口,尝试搜索一下调用关系。
通过schedule方法作为入口,看看能否找到一些线索。

发现大致经过以下几个流程:

并没有太大的参考价值,所以这个方案也有点行不通。
方案3
因为对象是在Spring 容器平缓关闭的时候触发的shutdown操作,而在Spring中每一个对象都是一个bean,bean 的生命周期中有一个destory的过程,所以可以基本断定是destory方法中执行了shutdown方法,并且RScheduledExecutorService是实现了java中的ExecutorService接口,而这个接口中定义了shutdown方法,而Spring可能使用ExecutorService统一管理这些bean。
DisposableBean这个接口定义的destory方法,是Spring bean生命周期中的一个方法,通过查看他的实现发现有一个可疑的对象 TaskExecutorFactoryBean

通过查看源码发现,其中确实调用了shutdown方法。

而这个executor对象,实际就是ExecutorService这个接口

就在这一刻,突然感觉真相大白了,终于找到证据了,所以可以确信是Spring Boot的平缓关闭导致的这个问题
小总结
程序猿要证据说话!
通过本次追溯源码确实看到了许多大佬们设计这个框架的思想,特别是Spring设计的巧妙之处,使用这个统一的方式管理了Spring bean对象。

本文讲述了在Spring Boot应用中,由于Redisson的ExecutorService在平缓关闭过程中引发的错误问题。作者通过分析源码,探讨了三种解决方案,最终确定问题出在Spring Boot的destory方法调用了ExecutorService的shutdown方法,揭示了Spring管理bean的生命周期机制。
3991





