记录一次RPC服务有损上线的分析过程
1. 问题背景
某应用在启动完提供JSF服务后,短时间内出现了大量的空指针异常。
分析日志,发现是服务依赖的藏经阁配置数据未加载完成导致。即所谓的有损上线或者是直接发布,当应用启动时,service还没加载完,就开始对外提供服务,导致失败调用。
关键代码如下
数据的初始化加载是通过实现CommandLineRunner接口完成的
@Component
public class LoadSystemArgsListener implements CommandLineRunner {
@Resource
private CacheLoader cjgConfigCacheLoader;
@Override
public void run(String... args) {
// 加载藏经阁配置
cjgConfigCacheLoader.refresh();
}
}
cjgConfigCacheLoader.refresh()方法内部会将数据加载到内存中
/** 藏经阁配置数据 key:租户 value:配置数据 */
public static Map<String, CjgRuleConfig> cjgRuleConfigMap = new HashMap<>();
如果此时还未加载完数据,调用cjgRuleConfigMap.get("301").getXX(),则会报空指针异常
总结根因:JSF Provider发布早于服务依赖的初始化数据加载,导致失败调用
2. 问题解决
在解决此问题前,我们需要先回忆并熟悉下Spring Boot的启动过程、JSF服务的发布过程
1)Spring Boot的启动过程(版本2.0.7.RELEASE)
run方法,主要关注refreshContext(context)刷新上下文
public ConfigurableApplicationContext run(String... args) {
// 创建 StopWatch 实例:用于计算启动时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 获取SpringApplicationRunListeners:这些监听器会在启动过程的各个阶段发送对应的事件
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 创建并配置Environment:包括准备好对应的`Environment`,以及将`application.properties`或`application.yml`中的配置项加载到`Environment`中
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印Banner:如果 spring.main.banner-mode 不为 off,则打印 banner
Banner printedBanner = printBanner(