说明
最近已经陆续把公司的 Spring Boot 服务迁移到了 Solon 框架中来,期间碰到几次迁移后无法启动的问题或者启动慢问题。以下是问题的记录和处理办法。
问题 1
启动提示循环依赖,启动失败。
通常情况 Solon 的 Inject 是支持循环依赖的,有两种情况会因为循环依赖而启动失败,具体细节可以参考官网文档,「问题:产生 Bean 循环依赖怎么办?」
- 由构造函数产生的依赖
- 由初始化的依赖
而我碰的的刚好就是第二种情况,在就的 Spring Boot 服务中有一些 Spring Event 事件是通过过 PostConstruct 注解来注册。所以迁移过来的时候使用了 Init 的注解,如下代码。
但因为另一个类也存在 Init 的注解且相互依赖,所以无法正常启动,解决办法就是设置不同的优先级。
类 1
1 |
|
类 2
1 |
|
问题 2
应用启动慢
迁移了几个服务,通常情况下Solon 服务的启动会比 Spring Boot 的服务启动快。但有个服务器迁移过来的时候启动很慢,表现为扫描 Bean 对象很慢。
一开始时没有头绪的,后面问了作者,之后通过直接跟踪源代码,定位到慢的类,发现这个类是写得比较点怪的,是一个工具类,但为了获取其他 Bean 对象方便,把其他用的的类一起注入进来了,所以给这个类,用 PostConstruct 来做一次初始化,迁移过来的是自然就用了 Init 注解。这次扫描 Bean 慢,可能出现了较深的依赖关系,但还没形成循环依赖。
处理办法就是取消了 Init 的注解,AppLoadEndEvent 事件中,执行一次 Init 操作。
1 | public class AppLoadEndEventListener implements EventListener<AppLoadEndEvent> { |
问题 3
旧的 Spring Boot 有个 Redis 消息队列的监听,于是修改成如下的代码。但发现项目启动的时候卡住了无法启动。
1 |
|
后面跟踪了代码才发现 redisx 的 subscribe,里面包含一个 process 其实是一个无限循环。在官网文档「应用生命周期」中有个提示:
重要提醒:
- 启动过程完成后,项目才能正常运行(启动过程中,不能把线程卡死了)
- AppBeanLoadEndEvent 之前的事件,需要启动前完成订阅!!!(否则,时机错过了)
知道原因,处理起来就比较简单了,统一在 AppLoadEndEvent 事件中处理,并使用异步订阅。
1 |
|
小结
Solon 可能出于简单、快速等原因考虑,对待循环依赖是比较粗暴的,直接报错。但出现启动慢等问题时可能就不容易立刻定位问题,此时可以优先看看有没有使用 Init 注解,有没有使用 Bean 注解。这些相关的类是否有依赖,这些初始化函数是否把线程卡死了。
反过来说, Spring Boot 为了解决循环依赖方面还是做了很多的检查或者调整,所以可能导致启动速度慢一些。不过听说 Spring Boot 3.x 默认也禁用了循环依赖。