模块研发
- 1: 编码规范
- 2: 模块瘦身
- 3: 模块与模块、模块与基座通信
- 4: 模块本地开发
- 5: 模块测试
- 6: 复用基座拦截器
- 7: 复用基座数据源
- 8: 静态合并部署
- 9: 模块中官方支持的中间件客户端
- 10: SOFAArk 关键用户文档
- 11:
1 - 编码规范
基础规范
- SOFAServerless 模块中官方验证并兼容的中间件客户端列表详见此处。基座中可以使用任意中间件客户端。
- 如果使用了模块热卸载能力,模块自定义的 Timer、ThreadPool 等需要在模块卸载时手动清理。您可以监听 Spring 的 ContextClosedEvent 事件,在事件处理函数中清理必要资源,也可以在 Spring XML 定义 Timer、ThreadPool 的地方指定它们的 destroy-method,在模块卸载时,Spring 会自动执行 destroy-method。
- 基座启动时会部署所有模块,所以基座编码时,一定要向所有模块兼容,否则基座会发布失败。如果遇到无法绕过的不兼容变更(一般是在模块拆分过程中会有比较多的基座与模块不兼容变更),请参见基座与模块不兼容发布。
知识点
模块瘦身 (重要)
模块与模块、模块与基座通信 (重要)
模块测试 (重要)
模块复用基座拦截器
模块复用基座数据源
基座与模块间类委托加载原理介绍
2 - 模块瘦身
为什么要瘦身
为了让模块安装更快、内存消耗更小:
- 提高模块安装的速度,减少模块包大小,减少启动依赖,控制模块安装耗时 < 30秒,甚至 < 5秒。
- 模块启动后 Spring 上下文中会创建很多对象,如果启用了模块热卸载,可能无法完全回收,安装次数过多会造成 Old 区、Metaspace 区开销大,触发频繁 FullGC,所有要控制单模块包大小 < 5MB。这样不替换或重启基座也能热部署热卸载数百次。
一键自动瘦身
瘦身原则
构建 ark-biz jar 包的原则是,在保证模块功能的前提下,将框架、中间件等通用的包尽量放置到基座中,模块中复用基座的包,这样打出的 ark-biz jar 会更加轻量。在复杂应用中,为了更好的使用模块自动瘦身功能,需要在模块瘦身配置 (模块根目录/conf/ark/文件名.txt) 中,在样例给出的配置名单的基础上,按照既定格式,排除更多的通用依赖包。
步骤一
在「模块项目根目录/conf/ark/文件名.txt」中(比如:my-module/conf/ark/rules.txt),按照如下格式配置需要下沉到基座的框架和中间件常用包。您也可以直接复制默认的 rules.txt 文件内容到您的项目中。
excludeGroupIds=org.apache*
excludeArtifactIds=commons-lang
步骤二
在模块打包插件中,引入上述配置文件:
<!-- 插件1:打包插件为 sofa-ark biz 打包插件,打包成 ark biz jar -->
<plugin>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-maven-plugin</artifactId>
<version>2.2.5</version>
<executions>
<execution>
<id>default-cli</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<skipArkExecutable>true</skipArkExecutable>
<outputDirectory>./target</outputDirectory>
<bizName>biz1</bizName>
<!-- packExcludesConfig 模块瘦身配置,文件名自定义,和配置对应即可-->
<!-- 配置文件位置:biz1/conf/ark/rules.txt-->
<packExcludesConfig>rules.txt</packExcludesConfig>
<webContextPath>biz1</webContextPath>
<declaredMode>true</declaredMode>
<!-- 打包、安装和发布 ark biz-->
<!-- 静态合并部署需要配置-->
<!-- <attach>true</attach>-->
</configuration>
</plugin>
步骤三
打包构建出模块 ark-biz jar 包即可,您可以明显看出瘦身后的 ark-biz jar 包大小差异。
您可点击此处查看完整模块瘦身样例工程。您也可以阅读下文继续了解模块的瘦身原理。
基本原理
SOFAServerless 底层借助 SOFAArk 框架,实现了模块之间、模块和基座之间的相互隔离,以下两个核心逻辑对编码非常重要,需要深刻理解:
- 基座有独立的类加载器和 Spring 上下文,模块也有独立的类加载器和** Spring 上下文**,相互之间 Spring 上下文都是隔离的。
- 模块启动时会初始化各种对象,会优先使用模块的类加载器去加载构建产物 FatJar 中的 class、resource 和 Jar 包,找不到的类会委托基座的类加载器去查找。
基于这套类委托的加载机制,让基座和模块共用的 class、resource 和 Jar 包通通下沉到基座中,可以让模块构建产物非常小,更重要的是还能让模块在运行中大量复用基座已有的 class、bean、service、IO 连接池、线程池等资源,从而模块消耗的内存非常少,启动也能非常快。
所谓模块瘦身,就是让基座已经有的 Jar 依赖务必在模块中剔除干净,在主 pom.xml 和 bootstrap/pom.xml 将共用的 Jar 包 scope 都声明为 provided,让其不参与打包构建。
手动排包瘦身
模块运行时装载类时,会优先从自己的依赖里找,找不到的话再委托基座的 ClassLoader 去加载。
所以对于基座已经存在的依赖,在模块 pom 里将其 scope 设置成 provided,避免其参与模块打包。
如果要排除的依赖无法找到,可以利用 maven helper 插件找到其直接依赖。举个例子,图示中要排除的依赖为 spring-boot-autoconfigure,右边的直接依赖有 sofa-boot-alipay-runtime,ddcs-alipay-sofa-boot-starter等(只需要看 scope 为 compile 的依赖):
确定自己代码 pom.xml 中有 ddcs-alipay-sofa-boot-starter,增加 exlcusions 来排除依赖:
在 pom 中统一排包(更彻底)
有些依赖引入了过多的间接依赖,手动排查比较困难,此时可以通过通配符匹配,把那些中间件、基座的依赖全部剔除掉,如 org.apache.commons、org.springframework 等等,这种方式会把间接依赖都排除掉,相比使用 sofa-ark-maven-plugin 排包效率会更高:
<dependency>
<groupId>com.serverless.mymodule</groupId>
<artifactId>mymodule-core</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>......</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
在 sofa-ark-maven-plugin 中指定排包
通过使用 **excludeGroupIds、excludeGroupIds **能够排除大量基座上已有的公共依赖:
<plugin>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-maven-plugin</artifactId>
<executions>
<execution>
<id>default-cli</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<excludeGroupIds>io.netty,org.apache.commons,......</excludeGroupIds>
<excludeArtifactIds>validation-api,fastjson,hessian,slf4j-api,junit,velocity,......</excludeArtifactIds>
<outputDirectory>../../target</outputDirectory>
<bizName>mymodule</bizName>
<finalName>mymodule-${project.version}-${timestamp}</finalName>
<bizVersion>${project.version}-${timestamp}</bizVersion>
<webContextPath>/mymodule</webContextPath>
</configuration>
</plugin>
3 - 模块与模块、模块与基座通信
基座与模块之间、模块与模块之间存在 spring 上下文隔离,互相的 bean 不会冲突、不可见。然而很多应用场景比如中台模式、独立模块模式等存在基座调用模块、模块调用基座、模块与模块互相调用的场景。 当前支持3种方式调用,@AutowiredFromBiz, @AutowiredFromBase, SpringServiceFinder 方法调用,注意三个方式使用的情况不同。
Spring 环境
基座调用模块
只能使用 SpringServiceFinder
@RestController
public class SampleController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String hello() {
Provider studentProvider = SpringServiceFinder.getModuleService("biz", "0.0.1-SNAPSHOT",
"studentProvider", Provider.class);
Result result = studentProvider.provide(new Param());
Provider teacherProvider = SpringServiceFinder.getModuleService("biz", "0.0.1-SNAPSHOT",
"teacherProvider", Provider.class);
Result result1 = teacherProvider.provide(new Param());
Map<String, Provider> providerMap = SpringServiceFinder.listModuleServices("biz", "0.0.1-SNAPSHOT",
Provider.class);
for (String beanName : providerMap.keySet()) {
Result result2 = providerMap.get(beanName).provide(new Param());
}
return "hello to ark master biz";
}
}
模块调用基座
方式一:注解 @AutowiredFromBase
@RestController
public class SampleController {
@AutowiredFromBase(name = "sampleServiceImplNew")
private SampleService sampleServiceImplNew;
@AutowiredFromBase(name = "sampleServiceImpl")
private SampleService sampleServiceImpl;
@AutowiredFromBase
private List<SampleService> sampleServiceList;
@AutowiredFromBase
private Map<String, SampleService> sampleServiceMap;
@AutowiredFromBase
private AppService appService;
@RequestMapping(value = "/", method = RequestMethod.GET)
public String hello() {
sampleServiceImplNew.service();
sampleServiceImpl.service();
for (SampleService sampleService : sampleServiceList) {
sampleService.service();
}
for (String beanName : sampleServiceMap.keySet()) {
sampleServiceMap.get(beanName).service();
}
appService.getAppName();
return "hello to ark2 dynamic deploy";
}
}
方式二:编程API SpringServiceFinder
@RestController
public class SampleController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String hello() {
SampleService sampleServiceImplFromFinder = SpringServiceFinder.getBaseService("sampleServiceImpl", SampleService.class);
String result = sampleServiceImplFromFinder.service();
System.out.println(result);
Map<String, SampleService> sampleServiceMapFromFinder = SpringServiceFinder.listBaseServices(SampleService.class);
for (String beanName : sampleServiceMapFromFinder.keySet()) {
String result1 = sampleServiceMapFromFinder.get(beanName).service();
System.out.println(result1);
}
return "hello to ark2 dynamic deploy";
}
}
模块调用模块
参考模块调用基座,注解使用 @AutowiredFromBiz 和 编程API支持 SpringServiceFinder。
方式一:注解 @AutowiredFromBiz
@RestController
public class SampleController {
@AutowiredFromBiz(bizName = "biz", bizVersion = "0.0.1-SNAPSHOT", name = "studentProvider")
private Provider studentProvider;
@AutowiredFromBiz(bizName = "biz", name = "teacherProvider")
private Provider teacherProvider;
@AutowiredFromBiz(bizName = "biz", bizVersion = "0.0.1-SNAPSHOT")
private List<Provider> providers;
@RequestMapping(value = "/", method = RequestMethod.GET)
public String hello() {
Result provide = studentProvider.provide(new Param());
Result provide1 = teacherProvider.provide(new Param());
for (Provider provider : providers) {
Result provide2 = provider.provide(new Param());
}
return "hello to ark2 dynamic deploy";
}
}
方式二:编程API SpringServiceFinder
@RestController
public class SampleController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String hello() {
Provider teacherProvider1 = SpringServiceFinder.getModuleService("biz", "0.0.1-SNAPSHOT", "teacherProvider", Provider.class);
Result result1 = teacherProvider1.provide(new Param());
Map<String, Provider> providerMap = SpringServiceFinder.listModuleServices("biz", "0.0.1-SNAPSHOT", Provider.class);
for (String beanName : providerMap.keySet()) {
Result result2 = providerMap.get(beanName).provide(new Param());
}
return "hello to ark2 dynamic deploy";
}
}
SOFABoot 环境
4 - 模块本地开发
Arkctl 工具安装
方法一:
- 本地安装 go 环境,go 依赖版本在 1.21 以上。
- 执行 go install
todo 独立的 arkctl go 仓库
命令,安装 arkctl 工具。
方法二:
- 在 二进制列表 中下载对应的二进制并加入到本地 path 中。
本地快速部署
你可以使用 arkctl 工具快速地进行模块的构建和部署,提高本地调试和研发效率。
场景 1:模块 jar 包构建 + 部署到本地运行的基座中。
准备:
- 在本地启动一个基座。
- 打开一个模块项目仓库。
执行命令:
# 需要在仓库的根目录下执行。
# 比如,如果是 maven 项目,需要在根 pom.xml 所在的目录下执行。
arkctl deploy
命令执行完成后即部署成功,用户可以进行相关的模块功能调试验证。
场景 2:部署一个本地构建好的 jar 包到本地运行的基座中。
准备:
- 在本地启动一个基座。
- 准备一个构建好的 jar 包。
执行命令:
arkctl deploy /path/to/your/pre/built/bundle-biz.jar
命令执行完成后即部署成功,用户可以进行相关的模块功能调试验证。
场景 3: 模块 jar 包构建 + 部署到远程运行的 k8s 基座中。
准备:
- 在远程已经运行起来的基座 pod。
- 打开一个模块项目仓库。
- 本地需要有具备 exec 权限的 k8s 证书以及 kubectl 命令行工具。
执行命令:
# 需要在仓库的根目录下执行。
# 比如,如果是 maven 项目,需要在根 pom.xml 所在的目录下执行。
arkctl deploy --pod {namespace}/{podName}
命令执行完成后即部署成功,用户可以进行相关的模块功能调试验证。
场景 4: 在多模块的 Maven 项目中,在 Root 构建并部署子模块的 jar 包。
准备:
- 在本地启动一个基座。
- 打开一个多模块 Maven 项目仓库。
执行命令:
# 需要在仓库的根目录下执行。
# 比如,如果是 maven 项目,需要在根 pom.xml 所在的目录下执行。
arkctl deploy --sub ./path/to/your/sub/module
命令执行完成后即部署成功,用户可以进行相关的模块功能调试验证。
场景 5: 查询当前基座中已经部署的模块。
准备:
- 在本地启动一个基座。
执行命令:
arkctl status
场景 6: 查询远程 k8s 环境基座中已经部署的模块。
准备:
- 在远程 k8s 环境启动一个基座。
- 确保本地有 kube 证书以及有关权限。
执行命令:
arkctl status --pod {namespace}/{name}
5 - 模块测试
本地调试
您可以在本地或远程先启动基座,然后使用客户端 Arklet 暴露的 HTTP 接口在本地或远程部署模块,并且可以给模块代码打断点实现模块的本地或远程 Debug。
Arklet HTTP 接口主要提供了以下能力:
- 部署和卸载模块。
- 查询所有已部署的模块信息。
- 查询各项系统和业务指标。
部署模块
curl -X POST -H "Content-Type: application/json" http://127.0.0.1:1238/installBiz
请求体样例:
{
"bizName": "test",
"bizVersion": "1.0.0",
// local path should start with file://, alse support remote url which can be downloaded
"bizUrl": "file:///Users/jaimezhang/workspace/github/sofa-ark-dynamic-guides/dynamic-provider/target/dynamic-provider-1.0.0-ark-biz.jar"
}
部署成功返回结果样例:
{
"code":"SUCCESS",
"data":{
"bizInfos":[
{
"bizName":"dynamic-provider",
"bizState":"ACTIVATED",
"bizVersion":"1.0.0",
"declaredMode":true,
"identity":"dynamic-provider:1.0.0",
"mainClass":"io.sofastack.dynamic.provider.ProviderApplication",
"priority":100,
"webContextPath":"provider"
}
],
"code":"SUCCESS",
"message":"Install Biz: dynamic-provider:1.0.0 success, cost: 1092 ms, started at: 16:07:47,769"
}
}
部署失败返回结果样例:
{
"code":"FAILED",
"data":{
"code":"REPEAT_BIZ",
"message":"Biz: dynamic-provider:1.0.0 has been installed or registered."
}
}
卸载模块
curl -X POST -H "Content-Type: application/json" http://127.0.0.1:1238/uninstallBiz
请求体样例:
{
"bizName":"dynamic-provider",
"bizVersion":"1.0.0"
}
卸载成功返回结果样例:
{
"code":"SUCCESS"
}
卸载失败返回结果样例:
{
"code":"FAILED",
"data":{
"code":"NOT_FOUND_BIZ",
"message":"Uninstall biz: test:1.0.0 not found."
}
}
查询模块
curl -X POST -H "Content-Type: application/json" http://127.0.0.1:1238/queryAllBiz
请求体样例:
{}
返回结果样例:
{
"code":"SUCCESS",
"data":[
{
"bizName":"dynamic-provider",
"bizState":"ACTIVATED",
"bizVersion":"1.0.0",
"mainClass":"io.sofastack.dynamic.provider.ProviderApplication",
"webContextPath":"provider"
},
{
"bizName":"stock-mng",
"bizState":"ACTIVATED",
"bizVersion":"1.0.0",
"mainClass":"embed main",
"webContextPath":"/"
}
]
}
获取帮助
Arklet 暴露的所有对外 HTTP 接口,可以查看 Arklet 接口帮助:
curl -X POST -H "Content-Type: application/json" http://127.0.0.1:1238/help
请求体样例:
{}
返回结果样例:
{
"code":"SUCCESS",
"data":[
{
"desc":"query all ark biz(including master biz)",
"id":"queryAllBiz"
},
{
"desc":"list all supported commands",
"id":"help"
},
{
"desc":"uninstall one ark biz",
"id":"uninstallBiz"
},
{
"desc":"switch one ark biz",
"id":"switchBiz"
},
{
"desc":"install one ark biz",
"id":"installBiz"
}
]
}
本地构建如何不改变模块版本号
添加以下 maven profile,本地构建模块使用命令 mvn clean package -Plocal
<profile>
<id>local</id>
<build>
<plugins>
<plugin>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-maven-plugin</artifactId>
<configuration>
<finalName>${project.artifactId}-${project.version}</finalName>
<bizVersion>${project.version}</bizVersion>
</configuration>
</plugin>
</plugins>
</build>
</profile>
单元测试
模块里支持使用标准 JUnit4 和 TestNG 编写和执行单元测试。
6 - 复用基座拦截器
诉求
基座中会定义很多 Aspect 切面(Spring 拦截器),你可能希望复用到模块中,但是模块和基座的 Spring 上下文是隔离的,就导致 Aspect 切面不会在模块中生效。
解法
为原有的切面类创建一个代理对象,让模块能调用到这个代理对象,然后模块通过 AutoConfiguration 注解初始化出这个代理对象。完整步骤和示例代码如下:
步骤 1:
基座代码定义一个接口,定义切面的执行方法。这个接口需要对模块可见(在模块里引用相关依赖):
public interface AnnotionService {
Object doAround(ProceedingJoinPoint joinPoint) throws Throwable;
}
步骤 2:
在基座中编写切面的具体实现,这个实现类需要加上 @SofaService 注解(SOFABoot)或者 @SpringService 注解(SpringBoot,建设中):
@Service
@SofaService(uniqueId = "facadeAroundHandler")
public class FacadeAroundHandler implements AnnotionService {
private final static Logger LOG = LoggerConst.MY_LOGGER;
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("开始执行")
joinPoint.proceed();
log.info("执行完成")
}
}
步骤 3:
在模块里使用 @Aspect 注解实现一个 Aspect,SOFABoot 通过 @SofaReference 注入基座上的 FacadeAroundHandler。
注意:这里不要声明成一个 Bean,不要加 @Component 或者 @Service 注解,主需要 @Aspect 注解。
//注意,这里不必申明成一个bean,不要加@Component或者@Service
@Aspect
public class FacadeAroundAspect {
@SofaReference(uniqueId = "facadeAroundHandler")
private AnnotionService facadeAroundHandler;
@Pointcut("@annotation(com.alipay.linglongmng.presentation.mvc.interceptor.FacadeAround)")
public void facadeAroundPointcut() {
}
@Around("facadeAroundPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
return facadeAroundHandler.doAround(joinPoint);
}
}
步骤 4:
使用 @Configuration 注解写个 Configuration 配置类,把模块需要的 aspectj 对象都声明成 Spring Bean。
注意:这个 Configuration 类需要对模块可见,相关 Spring Jar 依赖需要以
@Configuration
public class MngAspectConfiguration {
@Bean
public FacadeAroundAspect facadeAroundAspect() {
return new FacadeAroundAspect();
}
@Bean
public EnvRouteAspect envRouteAspect() {
return new EnvRouteAspect();
}
@Bean
public FacadeAroundAspect facadeAroundAspect() {
return new FacadeAroundAspect();
}
}
步骤 5:
模块代码中显示的依赖步骤 4 创建的 Configuration 配置类 MngAspectConfiguration。
@SpringBootApplication
@ImportResource("classpath*:META-INF/spring/*.xml")
@ImportAutoConfiguration(value = {MngAspectConfiguration.class})
public class ModuleBootstrapApplication {
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(ModuleBootstrapApplication.class)
.web(WebApplicationType.NONE);
builder.build().run(args);
}
}
7 - 复用基座数据源
建议
强烈建议使用本文档方式,在模块中尽可能复用基座数据源,否则模块反复部署就会反复创建、消耗数据源连接,导致模块发布运维会变慢,同时也会额外消耗内存。
SpringBoot 解法
在模块的代码中写个 MybatisConfig 类即可,这样事务模板都是复用基座的,只有 Mybatis 的 SqlSessionFactoryBean 需要新创建。
参考 demo:/sofa-serverless/samples/springboot-samples/db/mybatis/biz1
通过SpringBeanFinder.getBaseBean
获取到基座的 Bean 对象,然后注册成模块的 Bean:
@Configuration
@MapperScan(basePackages = "com.alipay.sofa.biz1.mapper", sqlSessionFactoryRef = "mysqlSqlFactory")
@EnableTransactionManagement
public class MybatisConfig {
//tips:不要初始化一个基座的DataSource,当模块被卸载的是,基座数据源会被销毁,transactionManager,transactionTemplate,mysqlSqlFactory被销毁没有问题
@Bean(name = "transactionManager")
public PlatformTransactionManager platformTransactionManager() {
return (PlatformTransactionManager) getBaseBean("transactionManager");
}
@Bean(name = "transactionTemplate")
public TransactionTemplate transactionTemplate() {
return (TransactionTemplate) getBaseBean("transactionTemplate");
}
@Bean(name = "mysqlSqlFactory")
public SqlSessionFactoryBean mysqlSqlFactory() throws IOException {
//数据源不能申明成模块spring上下文中的bean,因为模块卸载时会触发close方法
DataSource dataSource = (DataSource) getBaseBean("dataSource");
SqlSessionFactoryBean mysqlSqlFactory = new SqlSessionFactoryBean();
mysqlSqlFactory.setDataSource(dataSource);
mysqlSqlFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mappers/*.xml"));
return mysqlSqlFactory;
}
}
SOFABoot 解法
如果 SOFABoot 基座没有开启多 bundle(Package 里没有 MANIFEST.MF 文件),则解法和上文 SpringBoot 完全一致。
如果有 MANIFEST.MF 文件,需要调用BaseAppUtils.getBeanOfBundle
获取基座的 Bean,其中BASE_DAL_BUNDLE_NAME 为 MANIFEST.MF 里面的Module-Name
:
@Configuration
@MapperScan(basePackages = "com.alipay.serverless.dal.dao", sqlSessionFactoryRef = "mysqlSqlFactory")
@EnableTransactionManagement
public class MybatisConfig {
// 注意:不要初始化一个基座的 DataSource,会导致模块被热卸载的时候,基座的数据源被销毁,不符合预期。
// 但是 transactionManager,transactionTemplate,mysqlSqlFactory 这些资源被销毁没有问题
private static final String BASE_DAL_BUNDLE_NAME = "com.alipay.serverless.dal"
@Bean(name = "transactionManager")
public PlatformTransactionManager platformTransactionManager() {
return (PlatformTransactionManager) BaseAppUtils.getBeanOfBundle("transactionManager",BASE_DAL_BUNDLE_NAME);
}
@Bean(name = "transactionTemplate")
public TransactionTemplate transactionTemplate() {
return (TransactionTemplate) BaseAppUtils.getBeanOfBundle("transactionTemplate",BASE_DAL_BUNDLE_NAME);
}
@Bean(name = "mysqlSqlFactory")
public SqlSessionFactoryBean mysqlSqlFactory() throws IOException {
//数据源不能申明成模块spring上下文中的bean,因为模块卸载时会触发close方法
ZdalDataSource dataSource = (ZdalDataSource) BaseAppUtils.getBeanOfBundle("dataSource",BASE_DAL_BUNDLE_NAME);
SqlSessionFactoryBean mysqlSqlFactory = new SqlSessionFactoryBean();
mysqlSqlFactory.setDataSource(dataSource);
mysqlSqlFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
return mysqlSqlFactory;
}
}
8 - 静态合并部署
介绍
SOFAArk 提供了静态合并部署能力,在开发阶段,应用可以将其他应用构建成的 Biz 包(模块应用),并被最终的 Base 包(基座应用) 加载。 用户可以把 Biz 包统一放置在某个目录中,然后通过启动参数告知基座扫描这个目录,以此完成静态合并部署(详情见下描述)。如此,开发不需要考虑相互之间依赖冲突问题,Biz 之间则通过 @SofaService 和 @SofaReference 发布/引用 JVM 服务(SOFABoot,SpringBoot 还在建设中 )进行交互。
步骤 1:模块应用打包成 Ark Biz
如果开发者希望自己应用的 Ark Biz 包能够被其他应用直接当成 Jar 包依赖,进而运行在同一个 SOFAArk 容器之上,那么就需要打包发布 Ark Biz 包,详见 Ark Biz 介绍。 Ark Biz 包使用 Maven 插件 sofa-ark-maven-plugin 打包生成。
<build>
<plugin>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-maven-plugin</artifactId>
<version>${sofa.ark.version}</version>
<executions>
<execution>
<id>default-cli</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</build>
步骤 2:将上述 jar 包移动到指定目录。
把需要部署的 biz jar 都移动到指定目录,如:/home/sofa-ark/biz/
mv /path/to/your/biz/jar /home/sofa-ark/biz/
步骤 3:启动基座,并通过 -D 参指定 biz 目录
java -jar -Dcom.alipay.sofa.ark.static.biz.dir=/home/sofa-ark/biz/ sofa-ark-base.jar
步骤 4:验证 Ark Biz(模块)启动
在基座启动成功后,可以通过 telnet 启动 SOFAArk 客户端交互界面:
telnet localhost 1234
然后执行如下命令查看模块列表:
biz -a
此时应当可以看到 Master Biz(基座)和所有静态合并部署的 Ark Biz(模块)。
上述操作可以通过 SOFAArk 静态合并部署样例
体验。
9 - 模块中官方支持的中间件客户端
在 SOFAServerless 模块中,官方目前支持并兼容常见的中间件客户端。
注意,这里 “已经支持” 需要在基座 POM
中引入相关客户端依赖(强烈建议使用 SpringBoot Starter 方式引入相关依赖),同时在模块 POM 中也引入相关依赖并设置 *
中间件客户端 | 版本号 | 备注 |
---|---|---|
JDK | 8.x 17.x | 已经支持 |
SpringBoot | >= 2.3.0 或 3.x | 已经支持 JDK17 + SpringBoot3.x 基座和模块完整使用样例可参见此处 |
SpringBoot Cloud | >= 2.7.x | 已经支持 基座和模块完整使用样例可参见此处 |
SOFABoot | >= 3.9.0 或 4.x | 已经支持 |
JMX | N/A | 已经支持 需要给基座加 -Dspring.jmx.default-domain=${spring.application.name} 启动参数 |
log4j2 | 任意 | 已经支持。在基座和模块引入 log4j2,并额外引入依赖: <dependency> <groupId>com.alipay.sofa.serverless</groupId> <artifactId>sofa-serverless-adapter-log4j2</artifactId> <version>${最新版 SOFAServerless 版本}</version> <scope>provided</scope> <!– 模块需要 provided –> </dependency> 基座和模块完整使用样例参见此处 |
slf4j-api | 1.x 且 >= 1.7 | 已经支持 |
tomcat | 7.x、8.x、9.x、10.x 及以上均可 | 已经支持 基座和模块完整使用样例可参见此处 |
netty | 4.x | 已经支持 基座和模块完整使用样例可参见此处 |
sofarpc | >= 5.8.6 | 已经支持 |
dubbo | 3.x | 已经支持 基座和模块完整使用样例及注意事项可参见此处 |
grpc | 1.x 且 >= 1.42 | 已经支持 基座和模块完整使用样例及注意事项可参见此处 |
protobuf-java | 3.x 且 >= 3.17 | 已经支持 基座和模块完整使用样例及注意事项可参见此处 |
apollo | 1.x 且 >= 1.6.0 | 已经支持 基座和模块完整使用样例及注意事项可参见此处 |
nacos | 2.1.x | 已经支持 基座和模块完整使用样例及注意事项可参见此处 |
kafka-client | >= 2.8.0 或 >= 3.4.0 | 已经支持 基座和模块完整使用样例可参见此处 |
rocketmq | 4.x 且 >= 4.3.0 | 已经支持 基座和模块完整使用样例可参见此处 |
jedis | 3.x | 已经支持 基座和模块完整使用样例可参见此处 |
xxl-job | 2.x 且 >= 2.1.0 | 已经支持 需要在模块里声明为 compile 依赖独立使用 |
mybatis | >= 2.2.2 或 >= 3.5.12 | 已经支持 基座和模块完整使用样例可参见此处 |
druid | 1.x | 已经支持 基座和模块完整使用样例可参见此处 |
mysql-connector-java | 8.x | 已经支持 基座和模块完整使用样例可参见此处 |
postgresql | 42.x 且 >= 42.3.8 | 已经支持 |
mongodb | 4.6.1 | 已经支持 基座和模块完整使用样例可参见此处 |
hibernate | 5.x 且 >= 5.6.15 | 已经支持 |
j2cache | 任意 | 已经支持 需要在模块里声明为 compile 依赖独立使用 |
opentracing | 0.x 且 >= 0.32.0 | 已经支持 |
elasticsearch | 7.x 且 >= 7.6.2 | 已经支持 |
jaspyt | 1.x 且 >= 1.9.3 | 已经支持 |
OKHttp | - | 已经支持 需要放在基座里,请使用模块自动瘦身能力 |
io.kubernetes:client | 10.x 且 >= 10.0.0 | 已经支持 |
net.java.dev.jna | 5.x 且 >= 5.12.1 | 已经支持 |
prometheus | - | 待验证支持 |