导入导出框架AGEIPort(GEI)正式开源
Github:https://github.com/alibaba/AGEIPort
AGEIPort是什么?
AGEIPort 是数字供应链孵化并在阿里巴巴集团内广泛使用的一套性能卓越、稳定可靠、功能丰富、易于扩展、生态完整的数据导入导出方案,致力于帮助开发者在toB复杂业务场景下能够快速交付高性能、体验优、易维护的数据导入导出功能,如用户页面上的Excel/CSV数据文件上传和下载。
目前在阿里巴巴集团内部已有盒马、菜鸟、本地生活、阿里健康、钉钉、淘系等部门有较多使用,并成为多个技术组件的基础底座,经历多次618和双11大促考验,稳定导入导出数据300~400亿条/月。
AGEIPort解决的痛点?
如果你的业务系统的数据导入导出功能有如下痛点,AGEIPort将是你不二选择:
- 用户经常使用Excel批量进行数据导入或导出,每次操作少则几千、几万条,多则几十万、上百万条。系统处理速度慢,经常被用户吐槽。
- 页面上的异步操作任务进度是mock的,99%进度的任务像薛定谔的猫,下一刻可能执行结束,也可能需要等待十几分钟,总之,没有人确定它什么时候能执行结束。
- 读写文件的工具类和数据处理的接口/抽象类在一个个应用写了一遍又一遍,封装了一层又一层,但总不能满足更复杂的业务场景。当这些工具类和抽象类被足够多的类引用,修改公共类牵一发而动全身。聪明的程序员行走于刀尖之上灵巧的复制出一个新的,改改再用,但应用被改的遍地荆棘,稍有不慎就会刺伤自己。
- 导入导出相关的代码,过于具体但不够复用,过于抽象但不易理解和维护,当接口或抽象类的入参和返回值都定义为足够通用的JSON对象或Map,新来的接手人就知道自己中了大奖
- 不同应用代码编写方式大相径庭,到一个新的应用里编写相同功能的代码时,都需要拥抱变化,同时增加对这个世界新的认知。
- 日常业务开发中,常常被性能、样式处理、数据读写与映射、交互等业务无关的问题困扰,希望自己能专注于业务开发解决业务问题,让框架帮忙解决非业务问题。
- 面向ToB复杂业务场景,需要做一个PaaS/SaaS型产品,代码要有足够的扩展和可配置能力,同一份代码满足各种场景的定制化需求。
- 依赖某些中心化的中间件实现导入导出功能,每次大促即使已经填报要做保障,仍会受到共用资源带来的影响,当业务规模突增,导入导出的性能仍受限于中间件的处理性能。
- 依赖某些控制台、Nacos配置 来实现导入导出任务的新增和修改,每当你要回滚就开始手忙脚乱,恨不得再也不用不能支持回滚的控制台。
AGEIPort的优势?
基于事件驱动架构设计整体框架,并遵循先进的设计理念:
- 透明化的集群/单机执行、串行/并行执行,可以大幅提升数据处理性能,开发者只需关注业务逻辑处理。
- 支持实时任务进度计算和反馈,避免MOCK数据处理进度,提升用户体验。
- 面向toB复杂业务场景,从多种方式(声明定义、动态定义)、多种维度(配置、插件、策略、SPI)可以满足各种场景的个性需求,可作为平台化、PaaS/SaaS型产品的基础底座。
- 沉淀多种组件,多种场景、多种功能开箱即用。
- 秉承GitOps设计理念,将相关的不可变基础设施封装在应用Git仓库内部,可以使交付物更快、更稳定和更安全的发布和回滚。
- 去中心化架构,业务应用自组集群资源隔离,保证业务功能有较高的隔离性、可伸缩性和可用性。
- 标准化任务处流程和代码编写,定义出一个数据处理任务的流程与用户需实现的接口,接口间职责分离,标准化用户导入导出代码的编写,提高代码的可维护性。
- 明确业务领域对象,通过设计泛型接口,明确导入导出代码中的领域模型,可以避免业务代码中大量使用Map、JSON传参,提高代码的可维护性。
- 记录业务代码执行过程,辅助支持业务代码性能优化
AGEIPort的架构设计
AGEIPort的架构和功能设计如图所示:
事件驱动
AGEIPort基于事件驱动架构设计整体框架以保证任务性能和纯异步执行,解耦处理逻辑以提高扩展性。
当以单机模式运行主任务时,从应用集群中随机选择一个节点执行此主任务和主任务拆解之后的子任务。
当以集群模式运行主任务时,从应用集群中随机选择一个节点作为此主任务的Master节点,主任务会分解为子任务并分发给应用集群里的其他节点,其他节点被看做此主任务的Slave节点。
框架内置LocalEventBus(单机EventBus,处理单机任务)和RemoteEventBus(分布式Eventbus,处理多机任务),EventBus会根据当前上下文路由,将Event投递给Master节点的Listener,Monitor负责计算任务进度并判断当前状态、发出下一个Event或不做任何操作。
事件驱动流程示例如图:
1)一个子任务执行失败,一个子任务正常执行,主任务执行失败
2)全部子任务正常执行完后,主任务进行合并
并发和异步
并发和异步主要用来解决导入导出功能的性能、体验和开发效率问题
当以单机模式运行主任务时,从应用中随机选择一个节点在子任务线程池线程池中并行执行子任务
当以多机模式运行主任务时,从应用中选择多个Slave节点在每个Slave的子任务线程池中并行执行子任务
框架使用Reactor线程模型,当任务在Master节点执行时任务先丢给背压队列,随后从背压队列取出任务交由主任务线程池执行,主任务分片为子任务后进行任务分发,子任务分发到Slave节点在Slave节点的子任务线程池中执行。
另外,框架的并发和异步对开发者是透明化的,开发者无需关注性能问题,只需要实现框架的回调接口来一页页处理或返回数据(导出文件就是实现一个分页查询),框架会并行在多个线程里同时调用开发者实现的回调接口。
导入核心接口:
BizImportResult write(BizUser user, QUERY query, List data) throws BizException;
导出核心接口:
List queryData(BizUser user, QUERY query, BizExportPage bizExportPage) throws BizException;
去中心化
去中心化设计主要是用来提高导入导出功能的可用性和弹性,使导入导出随业务规模伸缩集群,以提高性能,保证应用之间资源隔离,提高导入导出功能的稳定性。
主要方案是通过去中心化设计让业务应用节点自组集群,使应用无需依赖外部系统,应用集群内部相互通信、进行任务调度、分发与执行。
最终效果是业务应用中引入一个Jar包后就能自动组建一个集群,集群内运行导入导出任务不依赖外部中间件或服务,实现一个导入导出任务执行的完整闭环;集群内的节点能够相互注册、订阅和通信(保证实现集群内主子任务调度和主子任务进度同步);集群中不存在Master节点,每个节点关系平等,只要有一个节点可用,功能整体可用。
方案使用SideCar机制,引入一套服务发现和通信机制,使应用可以自组集群并相互通信,从而在应用集群内部完成分片任务调度和分片任务进度上报。执行导入导出任务时,应用的任意一个节点均可同时作为Master节点和Slave节点,以起到去中心化的效果,对于某一个导入导出任务集群中存在一个唯一的Master节点负责此任务的处理流程。
这种去中心化的自组集群进行任务分发和执行的方式,一方面,保证了导入导出集群可以随业务应用规模弹性伸缩,提高了应用集群资源利用率,另一方面,应用之间集群相互隔离,且不依赖外部系统,提高了应用的SLA;最后,任意节点均可作为Master的设计提高了应用整体的可用性,只有当集群节点全部宕机时,导入导出任务才不可用。
GitOps
GitOps使用Git来管理基础设施和应用配置的一种开发运营实践。GitOps是一种面向云原生的持续交付方式,核心思想是将应用系统的声明性不可变基础设施和应用程序存放在Git版本库中,使用 Git 作为声明基础设施和应用的单一可信源。主要优势是:开发效率更高、更高的发布的可控性、可靠性、稳定性、一致性、便捷性、有利于发布自动化、有利于持续交付、面向云原生。
试想,回滚只需要重新发布或回滚Git仓库上一个版本的代码,而非到各种控制台修改配置考虑配置管理的完整性,是不是轻松多了呢?
框架支持GitOps的交付方式,以提高开发、交付效率和导入导出功能SLA。GitOps意味着导入导出任务运行所需的基础设施和用户开发的导入导出业务代码 都会被存储在用户应用的Git仓库中,可以通过Git进行任意的审查和版本控制,并且能够通过流水线自动化的部署。
非GitOps导入导出功能开发、发布、回滚流程
GitOps导入导出功能开发、发布、回滚流程
标准化流程和面向领域模型编码
相信各位见识过使用大量Map或者JSON作为函数入参的“威力”,它们可以轻松的消灭你的领域模型,也能让新来的开发者苦不堪言、小心翼翼。
使用泛型接口而非其他框架使用Map/JSON接口,主要作用是使接口能够明确领域对象,开发者可以直接面向业务领域模型编码,最终提高代码的可读性和可维护性。
泛型 |
含义 |
QUERY |
代表参数对象(QueryObject,QO),对应用户自定义参数 |
VIEW |
代表视图对象(ViewObject,VO),对应导入文件中的一行数据 |
DATA |
代表领域对象(DomainObject,DO),对应导入业务逻辑中的业务对象 |
框架提供了标准的任务处理流程,意味着开发者需要实现标准的接口完成导入导出功能。主要作用是结构化开发者导入导出编码,提高代码可读性和可维护性,保证所有开发者的编码结构和逻辑是一致的,是能够被框架语义所约束的,这样开发者在不同应用或团队中开发导入导出功能时就能避免上下文切换。
另外,标准化任务处理流程,可以将CPU密集型和IO密集型流程区分,以便框架做透明化的性能提升。