模块研发
- 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 文件内容到您的项目中。
步骤二
在模块打包插件中,引入上述配置文件:
步骤三
打包构建出模块 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 排包效率会更高:
在 sofa-ark-maven-plugin 中指定排包
通过使用 **excludeGroupIds、excludeGroupIds **能够排除大量基座上已有的公共依赖:
3 - 模块与模块、模块与基座通信
基座与模块之间、模块与模块之间存在 spring 上下文隔离,互相的 bean 不会冲突、不可见。然而很多应用场景比如中台模式、独立模块模式等存在基座调用模块、模块调用基座、模块与模块互相调用的场景。 当前支持3种方式调用,@AutowiredFromBiz, @AutowiredFromBase, SpringServiceFinder 方法调用,注意三个方式使用的情况不同。
Spring 环境
基座调用模块
只能使用 SpringServiceFinder
模块调用基座
方式一:注解 @AutowiredFromBase
方式二:编程API SpringServiceFinder
模块调用模块
参考模块调用基座,注解使用 @AutowiredFromBiz 和 编程API支持 SpringServiceFinder。
方式一:注解 @AutowiredFromBiz
方式二:编程API SpringServiceFinder
SOFABoot 环境
4 - 模块本地开发
Arkctl 工具安装
方法一:
- 本地安装 go 环境,go 依赖版本在 1.21 以上。
- 执行 go install
todo 独立的 arkctl go 仓库
命令,安装 arkctl 工具。
方法二:
- 在 二进制列表 中下载对应的二进制并加入到本地 path 中。
本地快速部署
你可以使用 arkctl 工具快速地进行模块的构建和部署,提高本地调试和研发效率。
场景 1:模块 jar 包构建 + 部署到本地运行的基座中。
准备:
- 在本地启动一个基座。
- 打开一个模块项目仓库。
执行命令:
命令执行完成后即部署成功,用户可以进行相关的模块功能调试验证。
场景 2:部署一个本地构建好的 jar 包到本地运行的基座中。
准备:
- 在本地启动一个基座。
- 准备一个构建好的 jar 包。
执行命令:
命令执行完成后即部署成功,用户可以进行相关的模块功能调试验证。
场景 3: 模块 jar 包构建 + 部署到远程运行的 k8s 基座中。
准备:
- 在远程已经运行起来的基座 pod。
- 打开一个模块项目仓库。
- 本地需要有具备 exec 权限的 k8s 证书以及 kubectl 命令行工具。
执行命令:
命令执行完成后即部署成功,用户可以进行相关的模块功能调试验证。
场景 4: 在多模块的 Maven 项目中,在 Root 构建并部署子模块的 jar 包。
准备:
- 在本地启动一个基座。
- 打开一个多模块 Maven 项目仓库。
执行命令:
命令执行完成后即部署成功,用户可以进行相关的模块功能调试验证。
场景 5: 查询当前基座中已经部署的模块。
准备:
- 在本地启动一个基座。
执行命令:
场景 6: 查询远程 k8s 环境基座中已经部署的模块。
准备:
- 在远程 k8s 环境启动一个基座。
- 确保本地有 kube 证书以及有关权限。
执行命令:
5 - 模块测试
本地调试
您可以在本地或远程先启动基座,然后使用客户端 Arklet 暴露的 HTTP 接口在本地或远程部署模块,并且可以给模块代码打断点实现模块的本地或远程 Debug。
Arklet HTTP 接口主要提供了以下能力:
- 部署和卸载模块。
- 查询所有已部署的模块信息。
- 查询各项系统和业务指标。
部署模块
请求体样例:
部署成功返回结果样例:
部署失败返回结果样例:
卸载模块
请求体样例:
卸载成功返回结果样例:
卸载失败返回结果样例:
查询模块
请求体样例:
返回结果样例:
获取帮助
Arklet 暴露的所有对外 HTTP 接口,可以查看 Arklet 接口帮助:
请求体样例:
返回结果样例:
本地构建如何不改变模块版本号
添加以下 maven profile,本地构建模块使用命令 mvn clean package -Plocal
单元测试
模块里支持使用标准 JUnit4 和 TestNG 编写和执行单元测试。
6 - 复用基座拦截器
诉求
基座中会定义很多 Aspect 切面(Spring 拦截器),你可能希望复用到模块中,但是模块和基座的 Spring 上下文是隔离的,就导致 Aspect 切面不会在模块中生效。
解法
为原有的切面类创建一个代理对象,让模块能调用到这个代理对象,然后模块通过 AutoConfiguration 注解初始化出这个代理对象。完整步骤和示例代码如下:
步骤 1:
基座代码定义一个接口,定义切面的执行方法。这个接口需要对模块可见(在模块里引用相关依赖):
步骤 2:
在基座中编写切面的具体实现,这个实现类需要加上 @SofaService 注解(SOFABoot)或者 @SpringService 注解(SpringBoot,建设中):
步骤 3:
在模块里使用 @Aspect 注解实现一个 Aspect,SOFABoot 通过 @SofaReference 注入基座上的 FacadeAroundHandler。
注意:这里不要声明成一个 Bean,不要加 @Component 或者 @Service 注解,主需要 @Aspect 注解。
步骤 4:
使用 @Configuration 注解写个 Configuration 配置类,把模块需要的 aspectj 对象都声明成 Spring Bean。
注意:这个 Configuration 类需要对模块可见,相关 Spring Jar 依赖需要以
步骤 5:
模块代码中显示的依赖步骤 4 创建的 Configuration 配置类 MngAspectConfiguration。
7 - 复用基座数据源
建议
强烈建议使用本文档方式,在模块中尽可能复用基座数据源,否则模块反复部署就会反复创建、消耗数据源连接,导致模块发布运维会变慢,同时也会额外消耗内存。
SpringBoot 解法
在模块的代码中写个 MybatisConfig 类即可,这样事务模板都是复用基座的,只有 Mybatis 的 SqlSessionFactoryBean 需要新创建。
参考 demo:/sofa-serverless/samples/springboot-samples/db/mybatis/biz1
通过SpringBeanFinder.getBaseBean
获取到基座的 Bean 对象,然后注册成模块的 Bean:
SOFABoot 解法
如果 SOFABoot 基座没有开启多 bundle(Package 里没有 MANIFEST.MF 文件),则解法和上文 SpringBoot 完全一致。
如果有 MANIFEST.MF 文件,需要调用BaseAppUtils.getBeanOfBundle
获取基座的 Bean,其中BASE_DAL_BUNDLE_NAME 为 MANIFEST.MF 里面的Module-Name
:
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 打包生成。
步骤 2:将上述 jar 包移动到指定目录。
把需要部署的 biz jar 都移动到指定目录,如:/home/sofa-ark/biz/
步骤 3:启动基座,并通过 -D 参指定 biz 目录
步骤 4:验证 Ark Biz(模块)启动
在基座启动成功后,可以通过 telnet 启动 SOFAArk 客户端交互界面:
然后执行如下命令查看模块列表:
此时应当可以看到 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 | - | 待验证支持 |