CrazyAirhead

疯狂的傻瓜,傻瓜也疯狂——傻方能执著,疯狂才专注!

0%

Solon 项目无法启动或者启动慢问题

说明

最近已经陆续把公司的 Spring Boot 服务迁移到了 Solon 框架中来,期间碰到几次迁移后无法启动的问题或者启动慢问题。以下是问题的记录和处理办法。

问题 1

启动提示循环依赖,启动失败。

img

通常情况 Solon 的 Inject 是支持循环依赖的,有两种情况会因为循环依赖而启动失败,具体细节可以参考官网文档,「问题:产生 Bean 循环依赖怎么办?」

  1. 由构造函数产生的依赖
  2. 由初始化的依赖

而我碰的的刚好就是第二种情况,在就的 Spring Boot 服务中有一些 Spring Event 事件是通过过 PostConstruct 注解来注册。所以迁移过来的时候使用了 Init 的注解,如下代码。

img

但因为另一个类也存在 Init 的注解且相互依赖,所以无法正常启动,解决办法就是设置不同的优先级。

类 1

1
2
3
4
@Init(index = 2)
public void init() {
EventUtils.listen(ManageEvent.TOPIC_USER_UPDATE, new UpdateUserListener());
}

类 2

1
2
3
4
@Init(index = 1)
public void init() {
EventUtils.listen(ManageEvent.TOPIC_ORG_CREATE, new CreateOrgListener());
}

问题 2

应用启动慢

img

迁移了几个服务,通常情况下Solon 服务的启动会比 Spring Boot 的服务启动快。但有个服务器迁移过来的时候启动很慢,表现为扫描 Bean 对象很慢。

一开始时没有头绪的,后面问了作者,之后通过直接跟踪源代码,定位到慢的类,发现这个类是写得比较点怪的,是一个工具类,但为了获取其他 Bean 对象方便,把其他用的的类一起注入进来了,所以给这个类,用 PostConstruct 来做一次初始化,迁移过来的是自然就用了 Init 注解。这次扫描 Bean 慢,可能出现了较深的依赖关系,但还没形成循环依赖。

img

处理办法就是取消了 Init 的注解,AppLoadEndEvent 事件中,执行一次 Init 操作。

1
2
3
4
5
6
7
8
9
public class AppLoadEndEventListener implements EventListener<AppLoadEndEvent> {
@Inject private ESHelper esHelper;

@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {

esHelper.init();
}
}

问题 3

旧的 Spring Boot 有个 Redis 消息队列的监听,于是修改成如下的代码。但发现项目启动的时候卡住了无法启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class RedisMessageConfig {

public static final String SCAN_JOB_MESSAGE = "scanJob-message";
/* 单次任务队列 */
public static final String SCAN_ONCE_MESSAGE = "scanOnce-message";

@Bean
public void init() {
RedisUtils.subscribe(new ScanJobSubscribe(), SCAN_JOB_MESSAGE);
RedisUtils.subscribe(new ScanJobSubscribe(), SCAN_ONCE_MESSAGE);
}
}

后面跟踪了代码才发现 redisx 的 subscribe,里面包含一个 process 其实是一个无限循环。在官网文档「应用生命周期」中有个提示:

重要提醒

  • 启动过程完成后,项目才能正常运行(启动过程中,不能把线程卡死了)
  • AppBeanLoadEndEvent 之前的事件,需要启动前完成订阅!!!(否则,时机错过了)

知道原因,处理起来就比较简单了,统一在 AppLoadEndEvent 事件中处理,并使用异步订阅。

1
2
3
4
5
6
7
8
9
10
@Slf4j
@Component
public class AppLoadEndEventListener implements EventListener<AppLoadEndEvent> {
@Inject private ScanJobSubscribe scanJobSubscribe;

@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
RedisUtils.subscribeFuture(scanJobSubscribe, SCAN_JOB_MESSAGE);
RedisUtils.subscribeFuture(scanJobSubscribe, SCAN_ONCE_MESSAGE);
}

小结

Solon 可能出于简单、快速等原因考虑,对待循环依赖是比较粗暴的,直接报错。但出现启动慢等问题时可能就不容易立刻定位问题,此时可以优先看看有没有使用 Init 注解,有没有使用 Bean 注解。这些相关的类是否有依赖,这些初始化函数是否把线程卡死了。

反过来说, Spring Boot 为了解决循环依赖方面还是做了很多的检查或者调整,所以可能导致启动速度慢一些。不过听说 Spring Boot 3.x 默认也禁用了循环依赖。

欢迎关注我的其它发布渠道