SOFA-Ark 动态部署
SOFAArk介绍https://www.sofastack.tech/projects/sofa-boot/sofa-ark-readme/ SOFASTACK 引用文章
SOFAArk基本介绍还是引用官网的文章,概括的比较全面了,后面就是我自己基于SOFAArk动态部署的源码分析~
SOFAArk 是一款基于 Java 实现的轻量级类隔离容器,主要提供类隔离和应用(模块)合并部署能力,由蚂蚁金服公司开源贡献;
场景
包冲突
日常使用 Java 开发,常常会遇到包依赖冲突的问题,尤其当应用变得臃肿庞大,包冲突的问题也会变得更加棘手,导致各种各样的报错,例如 LinkageError
, NoSuchMethodError
等;实际开发中,可以采用多种方法来解决包冲突问题,比较常见的是类似 Spring Boot 的做法,统一管理应用所有依赖包的版本,保证这些三方包不存在依赖冲突;这种做法只能有效避免包冲突问题,不能根本上解决包冲突的问题;如果某个应用的确需要在运行时使用两个相互冲突的包,例如 protobuf2
和 protobuf3
,那么类似 Spring Boot 的做法依然解决不了问题。
为了彻底解决包冲突的问题,需要借助类隔离机制,使用不同的 ClassLoader
加载不同版本的三方依赖,进而隔离包冲突问题;OSGI
作为业内最出名的类隔离框架,自然是可以被用于解决上述包冲突问题,但是 OSGI
框架太过臃肿,功能繁杂;为了解决包冲突问题,引入 OSGI
框架,有牛刀杀鸡之嫌,且反而使工程变得更加复杂,不利于开发;
SOFAArk 采用轻量级的类隔离方案来解决日常经常遇到的包冲突问题,在蚂蚁金服内部服务于整个 SOFABoot 技术体系,弥补 Spring Boot 没有的类隔离能力。SOFAArk 提出了一种特殊的包结构 – Ark Plugin,在遇到包冲突时,用户可以使用 Maven 插件将若干冲突包打包成 Plugin,运行时由独立的 PluginClassLoader 加载,从而解决包冲突。
假设如下场景,如果工程需要引入两个三方包:A 和 B,但是 A 需要依赖版本号为 0.1 的 C 包,而恰好 B 需要依赖版本号为 0.2 的 C 包,且 C 包的这两个版本无法兼容:
此时,即可使用 SOFAArk 解决该依赖冲突问题;只需要把 A 和版本为 0.1 的 C 包一起打包成一个 Ark 插件,然后让应用工程引入该插件依赖即可;
合并部署
复杂项目通常需要跨团队协作开发,各自负责不同的组件,而众所周知,协调跨团队合作开发会遇到不少问题;比如各自技术栈不统一导致的依赖冲突,又比如往同一个 Git 仓库提交代码常常导致 merge 冲突。因此,如果能让每个团队将负责的功能组件当成一个个单独的应用开发,运行时合并部署,通过统一的编程界面交互,那么将极大的提升开发效率及应用可扩展性。SOFAArk 提出了一种特殊的包结构 – Ark Biz,用户可以使用 Maven 插件将应用打包成 Biz,允许多 Biz 在 SOFAArk 容器之上合并部署,并通过统一的编程界面交互。
静态合并部署
SOFAArk 提供了静态合并部署能力,在开发阶段,应用可以将其他应用打成的 Biz 包通过 Maven 依赖的方式引入,而当自身被打成可执行 Fat Jar 时,可以将其他应用 Biz 包一并打入,启动时,则会根据优先级依次启动各应用。每个 Biz 使用独立的 BizClassLoader 加载,不需要考虑相互依赖冲突问题,Biz 之间则通过 SofaService/SofaReference
JVM 服务进行交互。
动态合并部署
动态合并部署区别于静态合并部署最大的一点是,运行时通过 API 或者配置中心(Zookeeper)来控制 Biz 的部署和卸载。动态合并部署的设计理念图如下:
无论是静态还是动态合并部署都会有宿主应用(master biz)的概念, 如果 Ark 包只打包了一个 Biz,则该 Biz 默认成为宿主应用;如果 Ark 包打包了多个 Biz 包,需要配置指定宿主应用。宿主应用不允许被卸载,一般而言,宿主应用会作为流量入口的中台系统,具体的服务实现会放在不同的动态 Biz 中,供宿主应用调用。宿主应用可以使用 SOFAArk 提供的客户端 API 实现动态应用的部署和卸载。除了 API, SOFAArk 提供了 Config Plugin,用于对接配置中心(目前支持 Zookeeper),运行时接受动态配置;Config Plugin 会解析下发的配置,控制动态应用的部署和卸载。
原理
SOFAArk 包含三个概念,Ark Container
, Ark Plugin
和 Ark Biz
; 运行时逻辑结构图如下:
在介绍这三个概念之前,先介绍上述 Ark
包概念;Ark
包是满足特定目录格式要求的可运行 Fat Jar
,使用官方提供的 Maven
插件 sofa-ark-maven-plugin
可以将单个或多个应用打包成标准格式的 Ark
包;使用 java -jar
命令即可在 SOFAArk 容器之上启动所有应用;Ark
包通常包含 Ark Container
、Ark Plugin
和 Ark Biz
;以下我们针对这三个概念简单做下名词解释:
Ark Container
: SOFAArk 容器,负责Ark
包启动运行时的管理;Ark Plugin
和Ark Biz
运行在 SOFAArk 容器之上;容器具备管理插件和应用的功能;容器启动成功后,会自动解析 classpath 包含的Ark Plugin
和Ark Biz
依赖,完成隔离加载并按优先级依次启动之;Ark Plugin
: Ark 插件,满足特定目录格式要求的Fat Jar
,使用官方提供的Maven
插件sofa-ark-plugin-maven-plugin
可以将一个或多个普通的Java jar
打包成一个标准格式的Ark Plugin
;Ark Plugin
会包含一份配置文件,通常包括插件类导入导出配置、资源导入导出配置、插件启动优先级等;运行时,SOFAArk 容器会使用独立的PluginClassLoader
加载插件,并根据插件配置构建类加载索引表、资源加载索引表,使插件和插件之间、插件和应用之间相互隔离;Ark Biz
: Ark 应用模块,满足特定目录格式要求的Fat Jar
,使用官方提供的Maven
插件sofa-ark-maven-plugin
可以将工程应用打包成一个标准格式的Ark Biz
;Ark Biz
是工程应用以及其依赖包的组织单元,包含应用启动所需的所有依赖和配置;一个 Ark 包中可以包含多个Ark Biz
包,按优先级依次启动,Biz 之间通过 JVM 服务交互;
运行 Ark
包,Ark Container
优先启动,容器自动解析 Ark
包中含有的 Ark Plugin
和 Ark Biz
,并读取他们的配置信息,构建类和资源的加载索引表;然后使用独立的 ClassLoader
加载并按优先级配置依次启动;需要指出的是,Ark Plugin
优先 Ark Biz
被加载启动;Ark Plugin
之间是双向类索引关系,即可以相互委托对方加载所需的类和资源;Ark Plugin
和 Ark Biz
是单向类索引关系,即只允许 Ark Biz
索引 Ark Plugin
加载的类和资源,反之则不允许。
感觉我写文档的能力有待提升,下面的图 看得懂的人不需要看,看不懂的感觉看了也不懂......
源代码包:
com.alipay.sofa.ark.api.ArkClient
com.alipay.sofa.ark.event.ArkEvent
com.alipay.sofa.spi.service.event.EventAdminService
主要在以上几个类中
安装操作 installOperation
卸载操作 uninstallOperation
卸载操作比较简单
发送事件通知
eventAdminService.sendEvent(new BeforeBizStopEvent(this));
从concurrentHashMap缓存中删除对应版本
bizCache.remove(bizVersion);
环绕事件通知
eventAdminService.sendEvent(new BeforeBizRecycleEvent(this));
删除工作目录
bizTempWorkDir.delete();
清除classloader 缓存
((AbstractClasspathClassLoader) classLoader).clearCache();
停止后发送事件通知
eventAdminService.sendEvent(new AfterBizStopEvent(this));
检查操作 checkOperation
查看当前所有版本的biz,其实就是从缓存中把biz及其所有版本获取到
选择操作 switchOperation
从cache中获取当前biz不同版本的集合,然后根据版本获取当前biz
ConcurrentHashMap<String, Biz> bizCache = bizRegistration.get(bizName);
if (bizCache != null) {
return bizCache.get(bizVersion);
}
如果biz状态不是active
eventAdminService.sendEvent(new BeforeBizSwitchEvent(biz));
bizManagerService.activeBiz(bizName, bizVersion);
eventAdminService.sendEvent(new AfterBizSwitchEvent(biz));
核心bizManagerService.activeBiz
其实就是将state设置为active,这样就可以switch对应的biz了