SpringBatch介绍
在企业领域,有很多应用和系统需要在生产环境中使用批处理来执行大量的业务操作.批处理业务需要自动地对海量数据信息进行各种复杂的业务逻辑处理,同时具备极高的效率,不需要人工干预.执行这种操作通常根据时间事件(如月末统计,通知或信件),或者定期处理那些业务规则超级复杂,数据量非常庞大的业务,(如保险赔款确定,利率调整),也可能是从内部/外部系统抓取到的各种数据,通常需要格式化、数据校验、并通过事务的方式处理到自己的数据库中.企业中每天通过批处理执行的事务多达数十亿.
SpringBatch是一个轻量级的综合性批处理框架,可用于开发企业信息系统中那些至关重要的数据批量处理业务. Spring Batch基于POJO和Spring框架,相当容易上手使用,让开发者很容易地访问和利用企业级服务.Spring Batch不是调度(scheduling)框架.因为已经有很多非常好的企业级调度框架,包括商业性质的和开源的,例如Quartz, Tivoli, Control-M等.它是为了与调度程序一起协作完成任务而设计的,而不是用来取代调度框架的.
SpringBatch提供了大量的,可重用的功能,这些功能对大数据处理来说是必不可少的,包括 日志/跟踪(tracing),事务管理,任务处理(processing)统计,任务重启, 忽略(skip),和资源管理等功能。 此外还提供了许多高级服务和特性,使之能够通过优化(optimization ) 和分片技术(partitioningtechniques)来高效地执行超大型数据集的批处理任务。
SpringBatch是一个具有高可扩展性的框架,简单的批处理,或者复杂的大数据批处理作业都可以通过Spring Batch框架来实现。
背景
在开源项目及其相关社区把大部分注意力集中在基于web和SOA基于消息机制的框架中时,基于java的批处理框架却无人问津,尽管在企业IT环境中一直都有这种批处理的需求。但因为缺乏一个标准的、可重用的批处理框架导致在企业客户的IT系统中存在着很多一次编写,一次使用的版本,以及很多不同的内部解决方案。
SpringSource和Accenture(埃森哲)致力于通过合作来改善这种状况。埃森哲在实现批处理架构上有着丰富的产业实践经验,SpringSource有深入的技术开发积累,背靠Spring框架提供的编程模型,意味着两者能够结合成为默契且强大的合作伙伴,创造出高质量的、市场认可的企业级java解决方案,填补这一重要的行业空白。两家公司目前也正着力于开发基于spring的批处理解决方案,为许多客户解决类似的问题。这同时提供了一些有用的额外的细节和以及真实环境的约束,有助于确保解决方案能够被客户用于解决实际的问题。基于这些原因,SpringSource和埃森哲一起合作开发Spring Batch。
埃森哲已经贡献了先前自己的批处理体系结构框架,这个框架基于数十年宝贵的经验并基于最新的软件平台(如COBOL/Mainframe, C++/Unix 及现在非常流行的Java平台)来构建SpringBatch项目, Spring Batch未来将会由开源社区提交者来驱动项目的开发,增强,以及未来的路线图。
埃森哲咨询公司与SpringSource合作的目标是促进软件处理方法、框架和工具的标准化改进,并在创建批处理应用时能够持续影响企业用户。企业和政府机构希望为他们提供标准的、经验证过的解决方案,而他们的企业系统也将受益于SpringBatch。使用场景
典型的批处理程序通常是从数据库、文件或队列中读取大量数据,然后通过某些方法处理数据,最后将处理好格式的数据写回库中。通常SpringBatch工作在离线模式下,不需要用户干预、就能自动进行基本的批处理迭代,进行类似事务方式的处理。批处理是大多数IT项目的一个组成部分,而Spring Batch是唯一能够提供健壮的企业级扩展性的批处理开源框架。
业务场景
技术目标
SpringBatch架构
SpringBatch 设计时充分考虑了可扩展性和各类终端用户。下图显示了SpringBatch的架构层次示意图,这种架构层次为终端用户开发者提供了很好的扩展性与易用性.
图1.1: Spring Batch 分层架构
SpringBatch 架构主要分为三类高级组件: 应用层(Application), 核心层(Core) 和基础架构层(Infrastructure)。
应用层(Application)包括开发人员用Spring batch编写的所有批处理作业和自定义代码。
Batch核心(Batch Core) 包含加载和控制批处理作业所必需的核心类。包括 JobLauncher, Job, 和 Step 的实现.
应用层(Application) 与 核心等(Core)都构建在通用基础架构层之上. 基础架构包括通用的 readers(ItemReader) 和 writers(ItemWriter), 以及services (如重试模块 RetryTemplate), 可以被应用层和核心层所使用.
通用批处理的指导原则
下面是一些关键的指导原则,在构建批处理解决方案可以参考:
批处理策略
为了辅助批处理系统的设计和实现、应该通过结构示意图和代码实例的形式为设计师和程序员提供基础的批处理程序构建模块和以及处理模式.在设计批处理Job时,应该将业务逻辑分解成一系列的步骤,使每个步骤都可以利用以下的标准构建模块来实现:
因为业务逻辑不能用上面介绍的这些标准模块来完成,所以还需要另外提供一个基本的程序外壳(application shell).除了这些主要的模块,每个应用还可以使用一到多个标准的实用程序环节(standard utility steps),如:
所有批处理系统的基础都是处理策略.影响策略选择的因素包括: 预估的批处理系统容量, 在线并发或与另一个批处理系统的并发量, 可用的批处理时间窗口(随着越来越多的企业想要全天候(7x24小时)运转,所以基本上没有明确的批处理窗口).
典型的批处理选项包括:
上面列表中的顺序代表了批处理实现复杂性的排序,在同一个批处理窗口的处理最简单,而分区实现最复杂.
商业调度器可能支持上面的部分/或所有类型.
下面的部分将详细讨论这些处理选项.需要特别注意的是, 批处理所采用的提交和锁定策略将依赖于处理执行的类型,作为最佳批处理策略实践,在线锁策略应该使用相同的原则.因此,在设计批处理整体架构时不能简单地拍脑袋决定(译注:即需要详细的论证和分析).
锁策略可以只使用普通的数据库锁,也可以在架构中实现自定义的锁服务.锁服务将跟踪数据库锁定(例如在一个专用的数据库表(db-table)中存储必要的信息),然后在应用程序请求数据库操作时授予权限或拒绝.重试逻辑也可以通过这种架构实现,以避免批处理作业因为资源锁定的情况而失败.
1. 在一个批处理窗口中的常规处理 对于运行在一个单独批处理窗口中的简单批处理,更新的数据对在线用户或其他批处理来说并没有实时性要求,也没有并发问题,在批处理运行完成后执行单次提交即可.
大多数情况下,一种更健壮的方法会更合适.要记住的一件事是,批处理系统会随着时间的流逝而增长,包括复杂度和需要处理的数据量.如果没有合适的锁定策略,系统仍然依赖于一个单一的提交点,则修改批处理程序会是一件痛苦的事情.因此,即使是最简单的批处理系统,也应该为重启-恢复(restart-recovery)选项考虑提交逻辑,更不用说下面涉及到的那些更复杂情况下的信息.
2. 并发批处理/在线处理 批处理程序处理的数据如果会同时被在线用户更新,就不应该锁定在线用户需要的所有任何数据(不管是数据库还是文件),即使只需要锁定几秒钟的时间.还应该每处理一批事务就提交一次数据库.这减少了其他程序不可用的数据,也压缩了数据不可用的时间.
减少物理锁的另一个选择是实现一个行级的逻辑锁,通过使用乐观锁模式或悲观锁模式.
这些模式并不一定适用于批处理,但他们可以被用在并发批处理和在线处理的情况下(例如,数据库不支持行级锁).作为一般规则,乐观锁更适合于在线应用,而悲观锁更适合于批处理应用.只要使用了逻辑锁,那么所有访问逻辑锁保护的数据的程序都必须采用同样的方案
请注意,这两种解决方案都只锁定(address locking)单条记录.但很多情况下我们需要锁定一组相关的记录.如果使用物理锁,你必须非常小心地管理这些以避免潜在的死锁.如果使用逻辑锁,通常最好的解决办法是创建一个逻辑锁管理器,使管理器能理解你想要保护的逻辑记录分组(groups),并确保连贯和没有死锁(non-deadlocking).这种逻辑锁管理器通常使用其私有的表来进行锁管理、争用报告、超时机制等等.
3. 并行处理 并行处理允许多个批处理运行(run,名词,大意为运行中的程序)/任务(job)同时并行地运行,以使批处理总运行时间降到最低.如果多个任务不使用同一个文件、数表、索引空间时这并不算什么问题.如果确实存在共享和竞争,那么这个服务就应该使用分区数据来实现.另一种选择是使用控制表来构建一个架构模块以维护他们之间的相互依赖关系.控制表应该为每个共享资源分配一行记录,不管这些资源是否被某个程序所使用.执行并行作业的批处理架构或程序随后将查询这个控制表,以确定是否可以访问所需的资源.
如果解决了数据访问的问题,并行处理就可以通过使用额外的线程来并行实现.在传统的大型主机环境中,并行作业类上通常被用来确保所有进程都有充足的CPU时间.无论如何,解决方案必须足够强劲,以确保所有正在运行的进程都有足够的时间片.并行处理的其他关键问题还包括负载平衡以及一般系统资源的可用性(如文件、数据库缓冲池等).还要注意控制表自身也可能很容易变成一个至关重要的资源(即可能发生严重竞争?).
4. 分区(Partitioning)分区技术允许多版本的大型批处理程序并发地(concurrently)运行. 这样做的目的是减少超长批处理作业过程所需的时间. 可以成功分区的过程主要是那些可以拆分的输入文件 和/或 主要的数据库表被分区以允许程序使用不同的数据来运行.
此外,被分区的过程必须设计为只处理分配给他的数据集. 分区架构与数据库设计和数据库分区策略是密切相关的. 请注意,数据库分区并不一定指数据库需要在物理上实现分区,尽管在大多数情况下这是明智的.下面的图片展示了分区的方法:
系统架构应该足够灵活,以允许动态配置分区的数量. 自动控制和用户配置都应该纳入考虑范围. 自动配置可以根据参数来决定,例如输入文件大小 和/或 输入记录的数量.
4.1分区方法 下面列出了一些可能的分区方法. 选择哪种分区方法要根据具体情况来决定.
1.使用固定值来分解记录集
这涉及到将输入的记录集合分解成偶数个部分(例如10份,这样每部分是整个数据集的十分之一). 每个部分稍后由一个批处理/提取程序实例来处理.
为了使用这种方法,需要在预处理时将记录集拆分. 拆分的结果有一个最大值和最小值位置, 这两个值可以用作限制每个 批处理/提取程序处理部分的输入.
预处理可能是一个很大的开销,因为它必须计算并确定的每部分数据集的边界.
2.根据关键列(Key Column)分解
这涉及到将输入记录按照某个关键列来分解,比如定位码(location code),并将每个键分配给一个批处理实例.为了达到这个目标,也可以使用列值.
3.根据分区表决定分配给哪一个批处理实例(详情见下文).
4.根据值的一部分决定分配给哪个批处理实例的值(例如值 0000-0999、1000-1999 等)
在使用第1种方法时, 新值的添加将意味着需要手动重新配置批处理/提取程序,以确保新值被添加到某个特定的实例.
在使用第2种方法时,将确保所有的值都会被某个批处理作业实例处理到. 然而,一个实例处理的值的数量依赖于列值的分布(即可能存在大量的值分布在0000-0999范围内,而在1000-1999范围内的值却很少).如果使用这种方法,设计时应该考虑到数据范围的切分.
在这两种方法中,并不能将指定给批处理实例的记录实现最佳均匀分布. 批处理实例的数量并不能动态配置.
5.根据视图来分解
这种方法基本上是根据键列来分解,但不同的是在数据库级进行分解.它涉及到将记录集分解成视图.这些视图将被批处理程序的各个实例在处理时使用. 分解将通过数据分组来完成.
使用这个方法时,批处理的每个实例都必须为其配置一个特定的视图(而非主表).当然,对于新添加的数据,这个新的数据分组必须被包含在某个视图中.也没有自动配置功能,实例数量的变化将导致视图需要进行相应的改变.
6.附加的处理指示器
这涉及到输入表一个附加的新列,它充当一个指示器. 在预处理阶段,所有指示器都被标志为未处理. 在批处理程序获取记录阶段,只会读取被标记为未处理的记录,一旦他们被读取(并加锁),它们就被标记为正在处理状态. 当记录处理完成,指示器将被更新为完成或错误.批处理程序的多个实例不需要改变就可以开始,因为附加列确保每条纪录只被处理一次.
使用该选项时,表上的I/O会动态地增长.在批量更新的程序中,这种影响被降低了,因为写操作是必定要进行的.
7.将表提取到平面文件
这包括将表中的数据提取到一个文件中. 然后可以将这个文件拆分成多个部分,作为批处理实例的输入.
使用这个选项时,将数据提取到文件中,并将文件拆分的额外开销,有可能抵消多分区处理(multi-partitioning)的效果.可以通过改变文件分割脚本来实现动态配置.
8.使用哈希列(Hashing Column)
这个计划需要在数据库表中增加一个哈希列(key/index)来检索驱动(driver)记录. 这个哈希列将有一个指示器来确定将由批处理程序的哪个实例处理某个特定的行.例如,如果启动了三个批处理实例,那么“A”指示器将标记某行由实例1来处理,“B”将标记着将由实例2来处理,以此类推.
稍后用于检索记录的过程(procedure,程序)将有一个额外的WHERE子句来选择以一个特定指标标记的所有行. 这个表的insert需要附加的标记字段,默认值将是其中的某一个实例(例如“A”).
一个简单的批处理程序将被用来更新不同实例之间的重新分配负载的指标.当添加足够多的新行时,这个批处理会被运行(在任何时间,除了在批处理窗口中)以将新行分配给其他实例.
批处理应用程序的其他实例只需要像上面这样的批处理程序运行着以重新分配指标,以决定新实例的数量.
数据库和应用程序设计原则
如果一个支持多分区(multi-partitioned)的应用程序架构,基于数据库采用关键列(key column)分区方法拆成的多个表,则应该包含一个中心分区仓库来存储分区参数.这种方式提供了灵活性,并保证了可维护性.这个中心仓库通常只由单个表组成,叫做分区表.
存储在分区表中的信息应该是是静态的,并且只能由DBA维护.每个多分区程序对应的单个分区有一行记录,组成这个表.这个表应该包含这些列: 程序ID编号,分区编号(分区的逻辑ID),一个分区对应的关键列(keycolumn)的最小值,分区对应的关键列的最大值.
在程序启动时,应用程序架构(Control Processing Tasklet,控制处理微线程)应该将程序id和分区号传递给该程序.这些变量被用于读取分区表,来确定应用程序应该处理的数据范围(如果使用关键列的话).另外分区号必须在整个处理过程中用来:
死锁或热点往往发生在管理或架构表上,如日志表、控制表, 锁表(lock tables).这些影响也应该纳入考虑.为了确定架构可能的瓶颈,一个真实的压力测试是至关重要的.
要最小化数据冲突的影响,架构应该提供一些服务,如附加到数据库或遇到死锁时的 等待-重试(wait-and-retry)间隔时间.这意味着要有一个内置的机制来处理数据库返回码,而不是立即引发错误处理,需要等待一个预定的时间并重试执行数据库操作.
4.4参数传递和校验
对程序开发人员来说,分区架构应该相对透明.框架以分区模式运行时应该执行的相关任务包括:
体系架构应该考虑整合分区(partitions).包括以下关键问题: