SOFABoot 入门及基本使用

1.前言

SOFABoot是蚂蚁金服开源的基于 Spring Boot 的研发框架,它在 Spring Boot 的基础上,提供了诸如 Readiness Check,类隔离,日志空间隔离等能力。在增强了 Spring Boot 的同时,SOFABoot 提供了让用户可以在 Spring Boot 中非常方便地使用 SOFA 中间件的能力。阿里官方文档

由于是基于Spring Boot 开发,故与其有着版本对应关系,SOFABoot 3.x 系列版本将构建在 Spring Boot 2.x 基础之上,SOFABoot 4.x 系列版本将构建在 Spring Boot 3.x 基础之上。

注:本文以SOFABoot 3.17.0 和 Spring Boot 2.7.8 (maven 3.8.8)为例进行说明!

2.快速开始

 为了能顺利拉取jar包,需要先配置本地maven,在setting.xml 文件增加如下 profile 配置:

<profile>
    <id>default</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <repositories>
        <repository>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <id>maven-snapshot</id>
            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <id>maven-snapshot</id>
            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        </pluginRepository>
    </pluginRepositories>
</profile>

首先新建一个SpringBoot的项目,我以阿里云源进行创建,创建后在依赖管理中用 SOFABoot 替换SpringBoot的版本

 

<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>sofaboot-dependencies</artifactId>
    <version>${sofa.boot.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

其中sofa版本如下

 接着引入SOFABoot 健康检查扩展能力的依赖及 Web 依赖(方便查看健康检查结果)

<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>healthcheck-sofa-boot-starter</artifactId>
</dependency>

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

在配置目录中新增配置文件 application.properties 文件,添加 SOFABoot 工程常用的参数配置,其中 spring.application.name 是必需的参数,用于标示当前应用的名称;logging path 用于指定日志的输出目录。

spring.application.name=SOFABoot Demo
logging.path=./logs

最后直接在启动类上运行main方法,启动成功后在浏览器访问 http://localhost:8080/actuator/versions 来查看当前 SOFABoot 中使用 Maven 插件生成的版本信息汇总。如果是SOFABoot 4.x 则需要把路径替换为  /sofaboot/versions 

在浏览器输入 http://localhost:8080/actuator/readiness 查看应用 Readiness Check 的状况。如果是SOFABoot 4.x 则需要把路径替换为  /health/readiness 

 status: "UP" 表示应用 Readiness Check 健康的。

另外也可以查看日志,由于上述配置了日志文件的目录是项目目录下,故打开可以看到有个log目录,里面的结构如下

自此SOFABoot 集成和启动就完成了。

但是,它具体有何用,目前还未体现出来,下面进行说明。

3.启动加速

在实际使用 Spring/Spring Boot 开发中,会有一些 Bean 在初始化过程中执行准备操作,如拉取远程配置、初始化数据源等等。在应用启动期间,这类 Bean 会增加 Spring 上下文刷新时间,导致应用启动耗时变长。

为了加速应用启动,SOFABoot 通过配置可选项,将 Bean 的初始化方法(init-method) 使用单独线程异步执行,加快 Spring 上下文加载过程,提高应用启动速度。那么是如何去做的?

先来demo

首先是两个类,分别有两个初始化的方法(有延迟),然后手动方式注入到Spring

 启动后查看启动时间

 这是正常逻辑下的bean注入,没有问题对吧。下面就模拟在启动过程中有耗时操作(这里就指定初始化的方法)

 重启后再次查看日志,有初始化的打印日志,并且时间都9S多,这是在情理之中的,因为 Bean 的初始化动作是串行的,3S+5S,再加系统自带的启动时间

 如果一个大型项目启动过程中有大量的耗时操作,那么按照这种方式是不是启动非常缓慢。而Sofa Boot就解决了这个问题,其实逻辑很简单,就是将上述Bean 串行初始化改为并行异步。

如果使用呢?在注入bean时添加注解@SofaAsyncInit,再次启动观察日志

 是不是很神奇,只是简单加了个注解,就减少了启动的时间。

再看初始化方法的日志,发现并不是使用的main线程,而是其他线程执行的,且是并行

看起来是不是非常简单,在实际场景中可有效的加快启动时间。但是,它也有弊端,就像上面的两个类的init方法,需要手动在这种初始化耗时的方法上添加注解@SofaAsyncInit,也就是说需要我们开发人员去判断哪些类是可以异步初始化的。

用法讲完了,下一步就是啃原理了,进入注解 @SofaAsyncInit  ,下载源码

com.alipay.sofa.runtime.api.annotation.SofaAsyncInit

这个注解也只有一个value属性,默认值是true,根据注释是说为true时才对初始化的方法进行异步调用

 通过点击这个value(),可以看到只有一个地方调用

 进入这个类,

com.alipay.sofa.runtime.spring.AsyncInitBeanFactoryPostProcessor#registerAsyncInitBea

可以看出在异步初始化的方法中只有value属性为true时才调用 registerAsyncInitBean()  ,和上面的注释不谋而合。

 那么这个方法 registerAsyncInitBean() 是干啥的?

com.alipay.sofa.runtime.spring.async.AsyncInitBeanHolder

这个类中有一个map和两个方法

 其中map看起来应该是存储bean的,那么究竟存储的数据是什么格式?打断点后重启项目

 原来它是以模块进行区分,内层的map即为bean名称和初始化的方法名。也就是说这个方法就是把模块与bean名称和初始化的方法名放入map。方法 registerAsyncInitBean() 是放入,那么什么时候来取这个对应关系?那就是 getAsyncInitMethodName() 的作用,稍后再看。

那么如何知道哪些类的方法在初始化时需要异步?那肯定是标记了 @SofaAsyncInit 注解的方法,既然如此,我们来看有哪些类引入了此注解。可以看出,除了我们自己使用外,只有 AsyncInitBeanFactoryPostProcessor  这个类引入了

进入这个类

com.alipay.sofa.runtime.spring.AsyncInitBeanFactoryPostProcessor

 

 其他代码无需关注,只从L45开始阅读,是走if分支还是else?不知道就打断点看执行过程,可以看出都会执行L145的if的分支,而在if分支里面就是获取@SofaAsyncInit 注解,然后调用 registerAsyncInitBean() ,而这个方法是不是在哪见过

这就和上述的value判断串起来了。

 下面来看 getAsyncInitMethodName() 的作用,通过调用可以看到在AsyncProxyBeanPostProcessor类中

com.alipay.sofa.runtime.spring.AsyncProxyBeanPostProcessor

会从map中获取方法名并传入类AsyncInitializeBeanMethodInvoker

 那么AsyncInitializeBeanMethodInvoker是干啥的,继续看

此类定义了几个参数,重要看下面三个 

  • initCountDownLatch:CountDownLatch 对象,其中 count 初始化为 1
  • isAsyncCalling:表示是否正在异步执行 init 方法。
  • isAsyncCalled:表示是否已经异步执行过 init 方法。

那么是否可以推测通过这几个字段就可以判断一个bean是否已经执行或正在执行初始化方法?由于此类实现了MethodInterceptor接口,我们看起invoke(),

其中L121~L143的if分支,逻辑是什么?即没有异步执行初始化bean的init方法并且是异步的方法,那么就会放入线程池去执行。

如果不满足if,那就是后续的逻辑。不满足的条件是什么?可能是正在初始化或者已经初始化过了,那么这种情况下也不会重复把初始化丢入线程池去执行,也就是一个异步执行bean的初始化方法只会执行一次。

如此来看,加速bean初始化是不是还是比较简单。

 

其他功能待续

 

参考https://www.cnblogs.com/thisiswhy/p/17457499.html

 

posted @ 2024-10-14 15:35  钟小嘿  阅读(230)  评论(0编辑  收藏  举报