本文为《剖析 | SOFAArk 实现原理》第一篇,本篇作者卫恒,SOFAArk 开源负责人。《剖析 | SOFAArk 实现原理》系列由 SOFA 团队和源码爱好者们出品,项目代号:<SOFA:ArkLab/>,文末附共建列表,欢迎领取共建~
在大型软件开发过程中,通常会推荐底层功能插件化、业务功能模块化的开发模式,以期达到低耦合、高内聚、功能复用的优点。对于模块化,从语言层面,原计划在 Java7 就有的模块化特性,终于在 Java9 里面提供了。在 Java语言级对模块化提供支持之前,业界内最知名的 Java 模块化规范当属 OSGi 了,直至到今天,OSGi 在众多企业、厂商中被广泛使用,比如我们常用的 Web 应用服务器、Eclipse 等均采用了 OSGi 规范。
蚂蚁金服内部,CE 作为使用了 10 年的"元老级"容器组件,见证了和支撑了每年的大促、新春红包等流量场景。作为中间件的常青树,CE 以足够的稳定性为业务保驾护航。CE 容器也是基于 OSGi 实现了模块化,但是由于 CE 背负了太多包袱,使得其自身变得太重,在云原生及商业化输出上逐渐失去了优势。
从 2016 年底开始,框架组内部开始在 CE 的基础上进行抽离和整合,开始拥抱新的轻量级类隔离容器框架-SOFAArk。截止 2019 年底,SOFAArk 已经在蚂蚁金服内部 Serverless 场景下落地实践,并已经有数家企业在生产环境使用 SOFAArk ,包括网易云音乐、挖财、溢米教育等。
SOFAArk 是一款基于 Java 实现的轻量级类隔离容器,主要提供类隔离和应用(模块)合并部署能力,由蚂蚁金服公司开源贡献。SOFAArk 提供了一套较为规范化的插件化、模块化的开发方案,产品能力主要包括:
SOFAArk: https://github.com/sofastack/sofa-ark
基于模块化及模块的动态能力,SOFAArk 有非常丰富的落地场景,如:通过从 classloader 层面解决 依赖冲突问题、基于 arkcontainer 的多应用合并部署,基于动态 biz 的 SOFAServerless 等等。
日常使用 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 容器之上合并部署,并通过统一的编程界面交互。
在开发阶段,应用可以将其他应用打成的 Biz 包通过 Maven 依赖的方式引入,而当自身被打成可执行 Fat Jar 时,可以将其他应用 Biz 包一并打入,启动时,则会根据优先级依次启动各应用。每个 Biz 使用独立的 BizClassLoader 加载,不需要考虑相互依赖冲突问题,Biz 之间则通过 SofaService / SofaRefernece JVM 服务进行交互。
静态合并部署是将多个应用打包在一个 ARK 可执行 JAR 包中,然后通过 java -jar 启动,这种方式可以在 ARK 容器内同时运行多个应用,对于一些资源要求不高、流量较少的应用可以使用这种方式部署以节省资源。
模块是 ark biz 在动态模型场景下的一种别名,其实质就是一个 ark biz 包。
动态模块相对于静态合并部署最大的不同点是,运行时通过 API 或者配置中心(Zookeeper)来控制 Biz 的部署和卸载。动态模块的设计理念图如下:
无论是静态合并部署还是动态模块都会有宿主应用(master biz)的概念, 如果 Ark 包只打包了一个 Biz,则该 Biz 默认成为宿主应用;如果 Ark 包打包了多个 Biz 包,需要配置指定宿主应用。宿主应用不允许被卸载,一般而言,宿主应用会作为流量入口的中台系统,具体的服务实现会放在不同的动态 Biz 中,供宿主应用调用。宿主应用可以使用 SOFAArk 提供的客户端 API 实现动态应用的部署和卸载。除了 API, SOFAArk 提供了 Config Plugin,用于对接配置中心(目前支持 Zookeeper),运行时接受动态配置;Config Plugin 会解析下发的配置,控制动态应用的部署和卸载。
SOFAArk 的模块下发是动态合并部署能力的核心基础,目前 SOFAArk 除了自身提供的 telnet 服务可用于动态管理 ark biz 之外,SOFAStack 官方社区还提供了基于 Zookeeper 做为配置中心下发的实现,通过与 SOFADashboard 的集成,构成了一条完成的模块下发链路:
更多细节可以参考: SOFADashboard-SOFAArk 管控 。
SOFADashboard 作为面向操作人员的运维管控端,可以对模块进行动态的安装、卸载、切换等运维操作,这些操作指令会从 SOFADashboard 下发“命令型”转换成“状态型”指令存储到 Zookeeper。业务的宿主应用中可以通过引入 dashboard client,来对 Zookeeper 进行监听,在状态发生变更时,通过 dashboard client 提供的 api 来执行具体的运维指令。
当然,如果用户不想依赖 Zookeeper 作为配置中心来下发指令,也可以通过自定义 Config Plugin 的方式提供其他的下发模式,比如基于 Apollo、基于 Rest 等等。
对于“千人千面”一词,相信大家都不陌生,可以说任何一个电商系统的商品页或者一个推送信息流的 APP,展示给不同用户的商品或者信息都是不同的。
这就是动态模块可行的典型场景之一,平台可以根据不同的用户、时间、事件、热点话题等等,来向用户提供不同的商品或者信息。
“源信息”对于系统来说是不可控制的,但是 计算策略 是可以的;在固有的“源信息”的基础的情况下,可以通过调整不同的计算策略来实现将最新、最热点或者最合适的信息展示给用户。
对于一些普通场景来说,可以通过准备好一个推荐策略算法库,运行时通过配置中心动态切换,来实现动态调整;当然这种在某些场景下是 OK 的,但是如果希望在此之外想去动态增加额外的推荐策略来适应当前最新的信息流呢?
作为源码解析系列的第一篇文章,本文对 SOFAArk、 SOFAArk 使用场景以及蚂蚁金服内部基于 SOFAArk 对于 Serverless 框架的的探索实践做了简单的介绍。对于其内部更多的概念及实现机制,希望可以通过此系列文章让大家对 SOFAArk 有更加深刻的认识,并且能够在实际的工作中用以解决实际的问题。同时也希望大家能够参与我们源码共建系列及社区共建。
同时,我们开启了《剖析 | SOFAArk 源码》系列,会逐步详细介绍各个部分的代码设计和实现,预计按照如下的目录进行:
如果有同学对以上某个主题特别感兴趣的,可以留言讨论,我们会适当根据大家的反馈调整文章的顺序,谢谢大家关注 SOFAStack,关注 SOFAArk,我们会一直与大家一起成长的。
直接回复【金融级分布式架构】公众号想认领的文章名称,我们将会主动联系你,确认资质后,即可加入,It's your show time!
除了源码解析,也欢迎提交 issue 和 PR:
SOFAArk: https://github.com/sofastack/sofa-ark