1 - 1.1 简介与适用场景
什么是 Koupleless
Koupleless 是一种模块化的 Serverless 技术解决方案,它能让普通应用以比较低的代价演进为 Serverless 研发模式,让代码与资源解耦,轻松独立维护,与此同时支持秒级构建部署、合并部署、动态伸缩等能力为用户提供极致的研发运维体验,最终帮助企业实现降本增效。
随着各行各业的信息化数字化转型,企业面临越来越多的研发效率、协作效率、资源成本和服务治理痛点,接下来带领大家逐一体验这些痛点,以及它们在 Koupleless 中是如何被解决的。
适用场景
痛点 1:应用构建发布慢或者 SDK 升级繁琐
传统应用镜像化构建一般要 3 - 5 分钟,单机代码发布到启动完成也要 3 - 5 分钟,开发者每次验证代码修改或上线代码修改,都需要经历数次 6 - 10 分钟的构建发布等待,严重影响开发效率。此外,每次 SDK 升级(比如中间件框架、rpc、logging、json 等),都需要修改所有应用代码并重新构建发布,对开发者也造成了不必要的打扰。
通过使用 Koupleless 通用基座与配套工具,您可以低成本的将应用切分为 “基座” 与 “模块”,其中基座沉淀了公司或者某个业务部门的公共 SDK,基座升级可以由专人负责,对业务开发者无感,业务开发者只需要编写模块。在我们目前支持的 Java 技术栈中,模块就是一个 SpringBoot 应用代码包(FatJar),只不过 SpringBoot 框架本身和其他的企业公共依赖在运行时会让基座提前加载预热,模块每次发布都会找一台预热 SpringBoot 的基座进行热部署,整个过程类似 AppEngine,能够帮用户实现应用 10 秒级构建发布和公共 SDK 升级无感。
痛点 2:长尾应用资源与维护成本高
在企业中,80% 的应用只服务了不到 20% 的流量,同时伴随着业务的变化,企业存在大量的长尾应用,这些长尾应用 CPU 使用率长期不到 10%,造成了极大的资源浪费。
通过使用 Koupleless 合并部署与配套工具,您可以低成本的实现多个应用的合并部署,从而解决企业应用过度拆分和低流量业务带来的资源浪费,节约成本。
这里 “业务A 应用1” 在 Koupleless 术语中叫 “模块”。多个模块应用可以使用 SOFAArk 技术合并到同一个基座。基座可以是完全空的 SpringBoot应用(Java 技术栈),也可以下沉一些公共 SDK 到基座,模块应用每次发布会重启基座机器。在这种方式下,模块应用最大程度复用了基座的内存(Metaspace 和 Heap),构建产物大小也能从数百 MB 瘦身到几十 MB 甚至更激进,CPU 使用率也得到了有效提升。
痛点 3:企业研发协作效率低
在企业中,一些应用需要多人开发协作。在传统研发模式下,每一个人的代码变更都需要发布整个应用,这就导致应用需要以赶火车式的方式进行研发迭代,大家需要统一一个时间窗口做迭代开发,统一的时间点做发布上线,因此存在大量的需求上线相互等待、环境机器抢占、迭代冲突等情况。
通过使用 Koupleless,您可以方便的将应用拆分为一个基座与多个功能模块,一个功能模块就是一组代码文件。不同的功能模块可以同时进行迭代开发和发布运维,模块间互不感知互不影响,这样就消除了传统应用迭代赶火车式的相互等待,每个模块拥有自己的独立迭代,需求交付效率因此得到了极大提升。如果您对模块额外启用了热部署方式(也可以每次发布模块重启整个基座),那么模块的单次构建+发布也会从普通应用的 6 - 10 分钟减少到十秒级。
痛点 4:难以沉淀业务资产提高中台效率
在一些中大型企业中,会沉淀各种业务中台应用。中台一般封装了业务的公共 API 实现,和 SPI 定义。其中 SPI 定义允许中台上的插件去实现各自的业务逻辑,流量进入中台应用后,会调用对应的 SPI 实现组件去完成相应的业务逻辑。中台应用内的组件,业务逻辑一般不复杂,如果拆出去部署为独立应用会带来高昂的资源成本和运维成本,而且构建发布速度很慢,严重加剧研发负担影响研发效率。
通过使用 Koupleless,您可以方便的将中台应用拆分一个基座和多个功能模块。基座可以沉淀比较厚的业务依赖、公共逻辑、API 实现、SPI 定义等(即所谓的业务资产),提供给上面的模块使用。模块使用基座的能力可以是对象之间或 Bean 之间的直接调用,代码几乎不用改造。而且多个模块间可以同时进行迭代开发和发布运维,互不感知互不影响,协作交付效率得到了极大提升。此外对于比较简单的模块还可以开启热部署,单次构建+发布也会从普通应用的 6 - 10 分钟减少到 30 秒内。
痛点 5:微服务演进成本高
企业里不同业务有不同的发展阶段,因此应用也拥有自己的生命周期。
初创期:一个初创的应用一般会先采用单体架构。
↓
增长期:随着业务增长,应用开发者也随之增加。此时您可能不确定业务的未来前景,也不希望过早把业务拆分成多个应用以避免不必要的维护、治理和资源成本,那么您可以用 Koupleless 低成本地将应用拆分为一个基座和多个功能模块,不同功能模块之间可以并行研发运维独立迭代,从而提高应用在此阶段的研发协作和需求交付效率。
↓
成熟期:随着业务进一步增长,您可以使用 Koupleless 低成本地将部分或全部功能模块拆分成独立应用去研发运维。
↓
长尾期:部分业务在经历增长期或者成熟期后,也可能慢慢步入到低活状态或者长尾状态,此时您可以用 Koupleless 低成本地将这些应用一键改回模块,合并部署到一起实现降本增效。
可以看到 Koupleless 支持企业应用低成本地在初创期、增长期、成熟期、长尾期之间平滑过渡甚至来回切换,从而轻松让应用架构与业务发展保持同步。
应用生命周期演进
2 - 1.2 行业背景
应用研发与微服务存在的问题
应用架构从单体应用发展到微服务,结合软件工程从瀑布模式到当前的 DevOps 模式的发展,解决了可扩展、分布式、分工协作等问题,为企业提供较好的敏捷性与执行效率,带来了明显的价值。但该模式发展至今,虽然解决了一些问题,也有微服务的一些问题慢慢暴露出来,在当前已经得到持续关注:
基础设施复杂
认知负载高
当前业务要完成一个需求,背后实际上有非常多的依赖、组件和平台在提供各种各样的能力,只要这些业务以下的某一个组件出现异常被业务感知到,都会对业务研发人员带来较大认知负担和对应恢复的时间成本。
基础设施复杂、异常种类繁多
运维负担重
业务包含的各个依赖也会不断迭代升级,例如框架、中间件、各种 sdk 等,在遇到
- 重要功能版本发布
- 修复紧急 bug
- 遇到重大安全漏洞
等情况时,这些依赖的新版本就需要业务尽可能快的完成升级,这造成了两方面的问题:
对于业务研发人员
这些依赖的升级如果只是一次两次那么就不算是问题,但是一个业务应用背后依赖的框架、中间件与各类 sdk 是很多的,每一个依赖发布这些升级都需要业务同学来操作,这么多个依赖的话长期上就会对业务研发同学来说是不小的运维负担。另外这里也需要注意到业务公共层对业务开发者来说也是不小的负担。
对于基础设施人员
类似的对于各个依赖的开发人员自身,每发布一个这样的新版本,需要尽可能快的让使用的业务应用完成升级。但是业务研发人员更关注业务需求交付,想要推动业务研发人员快速完成升级是不太现实的,特别是在研发人员较多的企业里。
启动慢
每个业务应用启动过程都需要涉及较多过程,造成一个功能验证需要花费较长等待时间。
发布效率低
由于上面提到的启动慢、异常多的问题,在发布上线过程中需要较长时间,出现异常导致卡单需要恢复处理。发布过程中除了平台异常外,机器异常发生的概率会随着机器数量的增多而增多,假如一台机器正常完成发布(不发生异常)的概率是 99.9%,也就是一次性成功率为 99.9%,那么100台则是 90%,1000台则降低到了只有 36.7%,所以对于机器较多的应用发布上线会经常遇到卡单的问题,这些都需要研发人员介入处理,导致效率低。
协作与资源成本高
单体应用/大应用过大
多人协作阻塞
业务不断发展,应用会不断变大,这主要体现在开发人员不断增多,出现多人协作阻塞问题。
变更影响面大,风险高
业务不断发展,线上流量不断增大,机器数量也不断增多,但当前一个变更可能影响全部代码和机器流量,变更影响面大风险高。
小应用过多
在微服务发展过程中,随着时间的推移,例如部分应用拆分过多、某些业务萎缩、组织架构调整等,都会出现线上小应用或者长尾应用不断积累,数量越来越多,像蚂蚁过去3年应用数量增长了 3倍。
资源成本高
这些应用每个机房都需要几台机器,但其实流量也不大,cpu 使用率很低,造成资源浪费。
长期维护成本
这些应用同样需要人员来维度,例如升级 SDK,修复安全漏洞等,长期维护成本高。
问题必然性
微服务系统是个生态,在一个公司内发展演进几年后,参考28定律,少数的大应用占有大量的流量,不可避免的会出现大应用过大和小应用过多的问题。
然而大应用多大算大,小应用多少算多,这没有定义的标准,所以这类问题造成的研发人员的痛点是隐匿的,没有痛到一定程度是较难引起公司管理层面的关注和行动。
如何合理拆分微服务
微服务如何合理拆分始终是个老大难的问题,合理拆分始终没有清晰的标准,这也是为何会存在上述的大应用过大、小应用过多问题的原因。而这些问题背后的根因是业务与组织灵活,与微服务拆分的成本高,两者的敏捷度不一致。
业务发展灵活,组织架构也在不断调整,而微服务拆分需要机器与长期维护的成本,两者的敏捷度不一致,导致容易出现未拆或过度拆分问题,从而出现大应用过大和小应用过多问题。这类问题不从根本上解决,会导致微服务应用治理过一波之后还会再次出现问题,导致研发同学始终处于低效的痛苦与治理的痛苦循环中。
不同体量企业面对的问题
行业尝试的解法
当前行业里也有很多不错的思路和项目在尝试解决这些问题,例如服务网格、应用运行时、平台工程,Spring Modulith、Google ServiceWeaver,有一定的效果,但也存在一定的局限性:
- 从业务研发人员角度看,只屏蔽部分基础设施,未屏蔽业务公共部分
- 只解决其中部分问题
- 存量应用接入改造成本高
Koupleless 的目的是为了解决这些问题而不断演进出来的一套研发框架与平台能力。
3 - 1.3 产品架构
3.1 - 1.3.1 架构原理
模块化应用架构
为了解决这些问题,我们对应用同时做了横向和纵向的拆分。首先第一步纵向拆分:把应用拆分成基座和业务两层,这两层分别对应两层的组织分工。基座小组与传统应用一样,负责机器维护、通用逻辑沉淀、业务架构治理,并为业务提供运行资源和环境。通过关注点分离的方式为业务屏蔽业务以下所有基础设施,聚焦在业务自身上。第二部我们将业务进行横向切分出多个模块,多个模块之间独立并行迭代互不影响,同时模块由于不包含基座部分,构建产物非常轻量,启动逻辑也只包含业务本身,所以启动快,具备秒级的验证能力,让模块开发得到极致的提效。
拆分之前,每个开发者可能感知从框架到中间件到业务公共部分到业务自身所有代码和逻辑,拆分后,团队的协作分工发生相应改变,研发人员分工出两种角色,基座和模块开发者,模块开发者不关心资源与容量,享受秒级部署验证能力,聚焦在业务逻辑自身上。
这里要重点看下我们是如何做这些纵向和横向切分的,切分是为了隔离,隔离是为了能够独立迭代、剥离不必要的依赖,然而如果只是隔离是没有共享相当于只是换了个部署的位置而已,很难有好的效果。所以我们除了隔离还有共享能力,所以这里需要聚焦在隔离与共享上来理解模块化架构背后的原理。
模块的定义
在这之前先看下这里的模块是什么?模块是通过原来应用减去基座部分得到的,这里的减法是通过设置模块里依赖的 scope 为 provided 实现的,
一个模块可以由这三点定义:
- SpringBoot 打包生成的 jar 包
- 一个模块: 一个 SpringContext + 一个 ClassLoader
- 热部署(升级的时候不需要启动进程)
模块的隔离与共享
模块通过 ClassLoader 隔离配置和代码,SpringContext 隔离 Bean 和服务,可以通过调用 Spring ApplicationContext 的start close 方法来动态启动和关闭服务。通过 SOFAArk 来共享模块和基座的配置和代码 Class,通过 SpringContext Manager 来共享多模块间的 Bean 和服务。
并且在 JVM 内通过
- Ark Container 提供多 ClassLoader 运行环境
- Arklet 来管理模块生命周期
- Framework Adapter 将 SpringBoot 生命周期与模块生命周期关联起来
- SOFAArk 默认委托加载机制,打通模块与基座类委托加载
- SpringContext Manager 提供 Bean 与服务发现调用机制
- 基座本质也是模块,拥有独立的 SpringContext 和 ClassLoader
但是在 Java 领域模块化技术已经发展了20年了,为什么这里的模块化技术能够在蚂蚁内部规模化落地,这里的核心原因是
基于 SOFAArk 和 SpringContext Manager 的多模块能力,提供了低成本的使用方式。
隔离方面
对于其他的模块化技术,从隔离角度来看,JPMS 和 Spring Modulith 的隔离是通过自定义的规则来做限制的,Spring Modulith 还需要在单元测试里执行 verify 来做校验,隔离能力比较弱且一定程度上是比较 tricky 的,对于存量应用使用来说也是有不小改造成本的,甚至说是存量应用无法改造。而 SOFAArk 和 OSGI 一样采用 ClassLoader 和 SpringContext 的方式进行配置与代码、bean与服务的隔离,对原生应用的启动模式完全保持一致。
共享方面
SOFAArk 的隔离方式和 OSGI 是一致的,但是在共享方面 OSGI 和 JPMS、Spring Modulith 一样都需要在源模块和目标模块间定义导入导出列表或其他配置,这造成业务使用模块需要强感知和理解多模块的技术,使用成本是比较高的,而 SOFAArk 则定义了默认的类委托加载机制,和跨模块的 Bean 和服务发现机制,让业务不用改造的情况下能够使用多模块的能力。
这里额外提下,为什么基于 SOFAArk 的多模块化技术能提供这些默认的能力,而做到低成本的使用呢?这里主要的原因是因为我们对模块做了角色的区分,区分出了基座与模块,在这个核心原因基础上也对低成本使用这块比较重视,做了重要的设计考量和取舍。具体有哪些设计和取舍,可以查看技术实现文章。
模块间通信
模块间通信主要依托 SpringContext Manager 的 Bean 与服务发现调用机制提供基础能力,
!
模块的可演进
回顾背景里提到的几大问题,可以看到通过模块化架构的隔离与共享能力,可以解决掉基础设施复杂、多人协作阻塞、资源与长期维护成本高的问题,但还有微服务拆分与业务敏捷度不一致的问题未解决。
在这里我们通过降低微服务拆分的成本来解决,那么怎么降低微服务拆分成本呢?这里主要是在单体架构和微服务架构之间增加模块化架构
- 模块不占资源所以拆分没有资源成本
- 模块不包含业务公共部分和框架、中间件部分,所以模块没有长期的 sdk 升级维护成本
- 模块自身也是 SpringBoot,我们提供工具辅助单体应用低成本拆分成模块应用
- 模块具备灵活部署能力,可以合并部署在一个 JVM 内,也可拆除独立部署,这样模块可以按需低成本演进成微服务或回退会单体应用模式
图中的箭头是双向的,如果当前微服务拆分过多,也可以将多个微服务低成本改造成模块合并部署在一个 JVM 内。所以这里的本质是通过在单体架构和微服务架构之间增加一个可以双向过渡的模块化架构,降低改造成本的同时,也让开发者可以根据业务发展按需演进或回退。这样可以把微服务的这几个问题解决掉
模块化架构的优势
模块化架构的优势主要集中在这四点:快、省、灵活部署、可演进,
与传统应用对比数据如下,可以看到在研发阶段、部署阶段、运行阶段都得到了10倍以上的提升效果。
平台架构
只有应用架构还不够,需要从研发阶段到运维阶段到运行阶段都提供完整的配套能力,才能让模块化应用架构的优势真正触达到研发人员。
在研发阶段,需要提供基座接入能力,模块创建能力,更重要的是模块的本地快速构建与联调能力;在运维阶段,提供快速的模块发布能力,在模块发布基础上提供 A/B 测试和秒级扩缩容能力;在运行阶段,提供模块的可靠性能力,模块可观测、流量精细化控制、调度和伸缩能力。
组件视图
在整个平台里,需要四个组件:
- 研发工具 Arkctl, 提供模块创建、快速联调测试等能力
- 运行组件 SOFAArk, Arklet,提供模块运维、模块生命周期管理,多模块运行环境
- 控制面组件 ModuleController
- ModuleDeployment 提供模块发布与运维能力
- ModuleScheduler 提供模块调度能力
- ModuleScaler 提供模块伸缩能力