1. Spring Batch 简介

企业领域中的许多应用程序需要批量处理才能在关键任务环境中执行业务操作。这些业务运营包括:

  • 无需用户交互即可最有效地处理大量信息的自动化,复杂处理。这些操作通常包括 time-based events(例如 month-end 计算,通知或通信)。

  • 定期复制业务规则的应用在非常大的数据集中重复处理(例如,保险利益确定或费率调整)。

  • 集成从内部和外部系统接收的信息,这些信息通常需要以 transactional 方式格式化,验证和处理 record 系统。批处理用于每天为企业处理数十亿的 transactions。

Spring Batch 是一个轻量级,全面的批量 framework,旨在开发对企业系统日常运营至关重要的强大批量应用程序。 Spring Batch 建立在人们所期望的 Spring Framework 的特性(生产力,POJO-based 开发方法和一般易用性)的基础上,同时使开发人员可以在必要时轻松访问和利用更高级的企业服务。 Spring Batch 不是一个调度 framework。有许多好的企业调度程序(例如 Quartz,Tivoli,Control-M,etc.)在商业和开源空间都可用。它旨在与调度程序一起使用,而不是替换调度程序。

Spring Batch 提供了可重复使用的函数,这些函数对于处理大量记录至关重要,包括 logging/tracing,transaction management,job 处理统计信息,job restart,skip 和 resource management。它还提供了更高级的技术服务和 features,通过优化和分区技术实现了极高 high-volume 和高性能的批处理作业。 Spring Batch 可用于两种简单的用例(例如将文件读入数据库或运行存储过程)以及复杂的大量用例(例如在数据库之间移动大量数据,转换它等等)上)。 High-volume 批处理作业可以高度可扩展的方式利用 framework 来处理大量信息。

1.1. 背景

虽然开源软件项目和相关社区更加关注 web-based 和 microservices-based 架构框架,但是尽管需要在企业 IT 环境中持续处理此类处理,但仍然缺乏对可重用 architecture 框架的关注以适应 Java-based 批处理需求。缺乏标准的,可重复使用的批处理 architecture 导致了 client 企业 IT 功能中开发的许多 one-off,in-house 解决方案的激增。

SpringSource(现为 Pivotal)和埃森哲合作改变了这一点。埃森哲在实施批量体系结构方面的行业和技术经验,SpringSource 的深度技术经验以及 Spring 经过验证的编程模式共同创建了一个自然而强大的合作伙伴关系,旨在创建旨在填补企业 Java 重要空白的 high-quality,market-relevant 软件。两家公司都与一些通过开发 Spring-based batch architecture 解决方案解决类似问题的客户合作。这提供了一些有用的额外细节和 real-life 约束,有助于确保解决方案可以应用于 clients 提出的 real-world 问题。

埃森哲为 Spring Batch 项目贡献了以前专有的批处理架构框架,以及提供支持,增强功能和现有 feature 集的提交者资源。埃森哲的贡献是基于在过去几代平台的 building 批量架构中的数十年经验:COBOL/Mainframe,C /Unix,现在 Java/anywhere。

埃森哲与 SpringSource 之间的合作旨在促进软件处理方法,框架和工具的标准化,这些方法,框架和工具可以在创建批处理应用程序时由企业用户持续使用。希望为其企业 IT 环境提供标准的,经过验证的解决方案的公司和政府机构可以从 Spring Batch 中受益。

1.2. 使用场景

一般的典型批处理程序:

  • 从数据库,文件或队列中读取大量记录。

  • 以某种方式处理数据。

  • 以修改的形式写回数据。

Spring Batch 自动执行此基本批处理迭代,提供了将类似 transactions 作为一个集处理的功能,通常在离线环境中,无需任何用户交互。批处理作业是大多数 IT 项目的一部分,Spring Batch 是唯一提供强大的 enterprise-scale 解决方案的开源 framework。

业务场景

  • 定期提交批处理 process

  • 并发批处理:_并行处理 job

  • 分阶段,企业 message-driven 处理

  • 大规模并行批处理

  • 失败后手动或预定重启

  • 依赖步骤的顺序处理(使用 extensions toworkflow-driven 批处理)

  • 部分处理:跳过记录(对于 example,回滚时)

  • Whole-batch transaction,适用于批量较小或存在的情况 procedures/scripts

技术目标

  • 批处理开发人员使用 Spring 编程 model:专注于业务逻辑,让 framework 负责基础架构。

  • 清楚地分离基础架构,批处理执行环境和批处理应用程序之间的关注点。

  • 提供 common 核心执行服务作为所有项目都可以实现的接口。

  • 提供可以“开箱即用”使用的核心执行接口的简单和默认 implementation。

  • 通过在所有层中利用 spring framework,轻松配置,自定义和扩展服务。

  • 所有现有核心服务都应易于更换或扩展,而不会对基础架构层产生任何影响。

  • 提供一个简单的部署 model,architecture JAR 与使用 Maven 构建的 application 完全分开。

1.3. Spring Batch Architecture

Spring Batch 的设计具有可扩展性和多样化的最终用户组。下图显示了支持 end-user 开发人员的可扩展性和易用性的分层 architecture。

图 1. Spring Batch Layered Architecture

这种分层的 architecture 突出了三个主要的 high-level 组件:Application,Core 和 Infrastructure。 application 包含开发人员使用 Spring Batch 编写的所有批处理作业和自定义 code。 Batch Core 包含启动和控制批处理 job 所需的核心运行时 classes。它包括JobLauncherJobStep的 implementations。 Application 和 Core 都构建在 common 基础架构之上。此基础结构包含 common readers 和 writers 和服务(例如RetryTemplate),它们由 application developers(readers 和_write 使用,例如ItemReaderItemWriter)以及核心 framework 本身(重试,这是它自己的 library)。

1.4. 一般批次原则和指南

构建批处理解决方案时,应考虑以下 key 原则,指南和一般注意事项。

  • 请记住,批 architecture 通常会影响 on-line architecture,反之亦然。尽可能使用 common building 块来设计体系结构和环境。

  • 尽可能简化并避免在单批应用程序中构建复杂的逻辑结构。

  • 保持数据的处理和存储物理上紧密相连(换句话说,将数据保存在处理过程中)。

  • 最大限度地减少系统资源的使用,尤其是 I/O。在内部 memory 中执行尽可能多的操作。

  • 查看 application I/O(分析 SQL statements)以确保避免不必要的物理 I/O。特别是,需要寻找以下四个 common 缺陷:

    • 当数据可以被读取一次并缓存或保存在工作存储中时,读取每个 transaction 的数据。

    • 重新读取 transaction 的数据,其中数据在之前的同一 transaction 中被读取。

    • 导致不必要的 table 或索引扫描。

    • 未在 SQL 语句的 WHERE 子句中指定 key 值。

  • 不要在批处理中运行两次 run。例如,如果您需要数据汇总以用于报告目的,则应该(如果可能)在最初处理数据时递增存储的总计,因此您的报告 application 不必重新处理相同的数据。

  • 在批处理 application 的开头分配足够的 memory,以避免 process 期间的 time-consuming 重新分配。

  • 总是假设数据完整性最差。 插入适当的检查和 record 验证以保持数据完整性。

  • 尽可能实现内部验证的校验和。对于 example,flat files 应该有一个预告片 record,告诉文件中的记录总数和 key 字段的集合。

  • 在具有真实数据量的 production-like 环境中尽可能早地计划和执行压力测试。

  • 在大批量系统中,备份可能具有挑战性,特别是如果系统在 24-7 基础上与 on-line 并行运行。数据库备份通常在 on-line 设计中得到很好的处理,但文件备份应该被视为同样重要。如果系统依赖于 flat files,则文件备份过程不仅应该存在并记录在案,而且应该定期进行测试。

1.5. 批处理策略

为了帮助设计和实现批处理系统,应该以 sample 结构图和 code shell 的形式向设计人员和程序员提供基本的批处理 application building 块和模式。在开始设计批处理 job 时,应将业务逻辑分解为一系列步骤,这些步骤可以使用以下标准 building 块来实现:

  • 转换 Applications:对于由外部系统提供或生成的每种类型的文件,必须创建转换应用程序,以将提供的 transaction 记录转换为处理所需的标准格式。这种类型的批处理应用程序可以部分或全部由翻译实用程序模块组成(请参阅基本批处理服务)。

  • 验证 Applications:验证 applications 确保所有 input/output 记录都正确且一致。验证通常基于文件 headers 和预告片,校验和和验证算法,以及 record level cross-checks。

  • 提取 Applications:一个 application,它从数据库或输入文件中读取一组记录,根据预定义的规则选择记录,并将记录写入输出文件。

  • Extract/Update Applications:一个 application,它从数据库或输入文件中读取记录,并对由每个输入 record 中的数据驱动的数据库或输出文件进行更改。

  • 处理和更新 Applications:一个 application,用于从提取或验证 application 对输入 transactions 执行处理。处理通常涉及读取数据库以获取处理所需的数据,可能更新数据库和创建记录以进行输出处理。

  • Output/Format Applications:Applications 读取输入文件,根据标准格式从此 record 重构数据,并生成用于打印或传输到另一个程序或系统的输出文件。

此外,应该为无法使用前面提到的 building 块构建的业务逻辑提供基本的 application shell。

除了主 building 块之外,每个 application 都可以使用一个或多个标准实用程序步骤,例如:

  • 排序:一个程序,它读取输入文件并根据记录中的排序 key 字段生成一个输出文件,其中记录已经 re-sequenced。排序通常由标准系统实用程序执行。

  • 拆分:一个程序,它读取单个输入文件,并根据字段 value 将每个 record 写入多个输出 files 之一。拆分可以由 parameter-driven 标准系统实用程序定制或执行。

  • 合并:一个程序,它从多个输入 files 读取记录,并生成一个输出文件,其中包含来自输入 files 的组合数据。合并可以由 parameter-driven 标准系统实用程序定制或执行。

批量应用程序还可以按其输入源进行分类:

  • Database-driven applications 由从数据库中检索的行或值驱动。

  • File-driven applications 由从文件中检索的记录或值驱动。

  • Message-driven applications 由从消息队列中检索的消息驱动。

任何批处理系统的基础都是处理策略。影响策略选择的因素包括:估计的批处理系统容量,与 on-line 系统或其他批处理系统的并发性,可用批处理 windows。 (需要注意的是,随着越来越多的企业想要全天候运行并且全天候运行,清除批量 windows 正在消失)。

批处理的典型处理选项是(增加 order 实现复杂度):

  • 在 off-line 模式下批处理窗口期间的正常处理。

  • 并发批处理或 on-line 处理。

  • 在同一 time 处并行处理许多不同的批处理运行或作业。

  • 分区(在同一 time 处理同一 job 的许多实例)。

  • 上述选项的组合。

商业调度程序可以支持部分或全部这些选项。

以下部分更详细地讨论了这些处理选项。重要的是要注意,根据经验,批处理采用的提交和锁定策略取决于执行的处理类型,并且 on-line 锁定策略也应该使用相同的原则。因此,在设计整体 architecture 时,批处理 architecture 不能仅仅是事后的想法。

锁定策略可以是仅使用普通数据库锁定,也可以在 architecture 中实现其他自定义锁定服务。锁定服务将跟踪数据库锁定(例如,通过将必要信息存储在专用 db-table 中)并对请求数据库操作的 application 程序给予或拒绝权限。此 architecture 也可以实现重试逻辑,以避免在发生锁定情况时中止批处理 job。

1.批处理窗口中的正常处理对于简单的批处理进程在单独的批处理窗口中运行_11用户或其他批处理过程不需要更新的数据,并发不是问题,可以在最后完成单个提交批运行。

在大多数情况下,更强大的方法更合适。请记住,批量系统在复杂性和处理的数据量方面都有随着时间的推移而增长的趋势。如果没有锁定策略并且系统仍然依赖于单个提交点,则修改批处理程序可能会很痛苦。因此,即使使用最简单的批处理系统,也需要考虑 restart-recovery 选项的提交逻辑以及本节后面描述的更复杂情况的信息。

2.并发批处理或 on-line 处理批处理应用程序处理数据可以由 on-line 用户同时更新,不应该锁定 on-line 用户可能需要的任何数据(在数据库中或 files 中)超过几秒钟。此外,应在每几个 transactions 结束时将更新提交到数据库。这最小化了其他进程不可用的数据部分以及数据不可用的已用时间。

最小化物理锁定的另一个选项是使用乐观锁定 Pattern 或悲观锁定 Pattern 实现逻辑 row-level 锁定。

  • 乐观锁定假设 record 争用的可能性很小。它通常意味着在批处理和 on-line 处理同时使用的每个数据库 table 中插入时间戳列。当 application 获取一行进行处理时,它还会获取时间戳。当 application 然后尝试更新已处理的行时,更新将使用 WHERE 子句中的原始时间戳。如果时间戳匹配,则更新数据和时间戳。如果时间戳不匹配,则表示另一个 application 在 fetch 和更新尝试之间更新了同一行。因此,无法执行更新。

  • 悲观锁定是任何锁定策略,假设 record 争用的可能性很高,因此需要在检索 time 时获得物理锁或逻辑锁。一种悲观的逻辑锁定在数据库 table 中使用专用的 lock-column。当 application 检索更新行时,它会在锁定列中设置 flag。在 flag 到位的情况下,尝试检索同一行的其他 applications 在逻辑上失败。当设置 flag 的 application 更新行时,它也会清除 flag,使其他 applications 可以检索该行。请注意,必须在初始提取和 flag 设置之间保持数据的完整性,例如使用数据库锁(例如SELECT FOR UPDATE)。另请注意,此方法的缺点与物理锁定相同,除了管理 building 机制,如果用户在 record 被锁定时去吃午餐时释放锁定,则更容易管理。

这些模式不一定适合批处理,但它们可能用于并发批处理和 on-line 处理(例如在数据库不支持 row-level 锁定的情况下)。作为一般规则,乐观锁定更适合 on-line applications,而悲观锁定更适合批处理应用。每当使用逻辑锁定时,必须对访问受逻辑锁保护的数据实体的所有 applications 使用相同的 scheme。

请注意,这两种解决方案仅解决锁定单个 record 的问题。通常,我们可能需要锁定逻辑相关的组记录。使用物理锁,您必须非常仔细地管理这些,以避免潜在的死锁。对于逻辑锁,通常最好 build 一个逻辑锁 manager,它理解你想要保护的逻辑 record 组,并且可以确保锁是连贯的 non-deadlocking。此逻辑锁 manager 通常使用自己的表来进行锁定管理,争用报告,time-out 机制和其他问题。

3.Parallel Processing Parallel 处理允许多个批处理运行或作业在 parallel 中运行,以最小化批处理总时间 time。这不是 long 的问题,因为作业不共享相同的 files,db-tables 或索引空格。如果他们这样做,则应使用分区数据实现此服务。另一种选择是 build architecture 模块,通过使用 control table 来维护相互依赖性。控件 table 应该包含每个共享资源的一行,以及它是否正由 application 使用。然后,parallel job 中的批处理 architecture 或 application 将从该 table 中检索信息,以确定它是否可以访问它所需的资源。

如果数据访问不是问题,可以通过在 parallel 中使用 process 的其他线程来实现 parallel 处理。在大型机环境中,传统上使用 parallel job classes,在 order 中确保所有进程都有足够的 CPU time。无论如何,该解决方案必须足够强大,以确保所有 running 进程的 time slice。

parallel 处理中的其他 key 问题包括负载平衡和一般系统资源(如 files,数据库缓冲池等)的可用性。另请注意,控件 table 本身很容易成为关键资源。

4.分区使用分区允许多个版本的大批量应用程序同时运行。这样做的目的是减少 process long 批处理作业所需的已用 time 时间。可以成功分区的进程是那些可以分割输入文件的进程 and/or 分区的主数据库表,以允许 application 对不同的数据集进行运行。

此外,必须将分区的进程设计为仅处理其分配的数据集。分区 architecture 必须与数据库设计和数据库分区策略紧密相关。请注意,数据库分区并不一定意味着数据库的物理分区,尽管在大多数情况下这是可取的。下图说明了分区方法:

图 2.分区 Process

architecture 应该足够灵活,以允许动态配置分区数。应考虑自动和用户控制的 configuration。自动 configuration 可以基于诸如输入文件大小和输入记录的数量之类的参数。

4.1 分区方法选择分区方法必须在 case-by-case 基础上完成。以下列表描述了一些可能的分区方法:

  1. Record Set 的固定和偶数 Break-Up

这涉及将输入 record 集合分成偶数个部分(对于 example,10,其中每个部分都具有整个 record 集合的 1/10th)。然后,每个部分由 batch/extract application 的一个实例处理。

在 order 中使用此方法,需要预处理来分割记录集。此拆分的结果将是一个下限和上限放置编号,可用作 order 中 batch/extract application 的输入,以将其处理限制为仅其部分。

预处理可能是一个很大的开销,因为它必须计算和确定 record 集的每个部分的边界。

  1. 由 Key 列打开

这涉及拆分 key 列设置的输入 record,例如 location code,并将每个 key 的数据分配给批处理实例。在 order 中实现此目的,列值可以是:

  • 通过分区 table 分配给批处理实例(本节稍后介绍)。

  • 通过 value 的一部分(例如 0000-0999,1000 - 1999 等)分配给批处理实例。

在选项 1 下,添加新值意味着手动重新配置 batch/extract 以确保将新的 value 添加到特定实例。

在选项 2 下,这可确保通过批处理 job 的实例覆盖所有值。但是,一个实例处理的值的数量取决于列值的分布(0000-0999 范围中可能有大量位置,1000-1999 范围内可能很少)。在此选项下,数据范围应设计为考虑分区。

在这两个选项下,无法实现记录到批处理实例的最佳均匀分布。没有动态配置使用的批处理实例数。

  1. 按意见分解

这种方法基本上是由 key 列分解但在数据库 level 上。它涉及将记录集分解为视图。这些视图在处理期间由批处理 application 的每个实例使用。分解是通过对数据进行分组来完成的。

使用此选项,必须将批处理 application 的每个实例配置为命中特定视图(而不是 master table)。此外,通过添加新数据值,这个新的数据组必须包含在视图中。没有动态配置功能,因为实例数量的更改会导致视图发生更改。

  1. 添加处理指标

这涉及向输入 table 添加新列,该列充当指示符。作为预处理 step,所有指标都标记为 non-processed。在批处理 application 的 record fetch 阶段,读取记录的条件是 record 被标记为 non-processed,一旦读取它们(带锁定),它们就被标记为正在处理中。完成该 record 后,指示符将更新为完成或错误。批处理 application 的许多实例可以在没有更改的情况下启动,因为附加列确保 record 仅处理一次。在“完成时,指标被标记为完成”的订单上的一两句话。)

使用此选项,table 上的 I/O 会动态增加。在更新批处理应用程序的情况下,这种影响会减少,因为无论如何都必须进行写入。

  1. 将 Table 解压缩到平面文件

这涉及将 table 提取到文件中。然后,可以将此文件拆分为多个段,并将其用作批处理实例的输入。

使用此选项,将 table 提取到文件中并将其拆分的额外开销可能会抵消 multi-partitioning 的影响。可以通过更改文件拆分脚本来实现动态配置。

  1. 使用哈希列

此 scheme 涉及向用于检索驱动程序 record 的数据库表添加哈希列(key/index)。此哈希列具有一个指示器,用于确定批处理 application 的哪个实例处理此特定行。例如,如果要启动三个批处理实例,则指示符“A”标记由实例 1 处理的行,指示符“B”标记由实例 2 处理的行,以及指示符“C” '标记了实例 3 处理的行。

然后,用于检索记录的过程将有一个额外的WHERE子句来选择由特定指示符标记的所有行。此 table 中的插入将涉及添加标记字段,该字段将默认为其中一个实例(例如“A”)。

将使用简单的批处理 application 来更新指示符,例如在不同实例之间重新分配负载。当添加了足够多的新行时,此批处理可以 run(任何时候,批处理窗口除外)将新行重新分配给其他实例。

批处理 application 的其他实例仅需要按前面段落中的描述运行批处理应用程序,以重新分配指示符以使用新的实例数。

4.2 数据库和 Application 设计原则

支持使用 key 列方法对分区数据库表运行的 multi-partitioned applications 的 architecture 应该包含一个用于存储分区参数的中央分区 repository。这提供了灵活性并确保了可维护性。 repository 通常由一个 table 组成,称为 partition table。

存储在分区 table 中的信息是静态的,通常应由 DBA 维护。 table 应该包含 multi-partitioned application 的每个分区的一行信息。 table 应包含 Program ID Code,Partition Number(分区的逻辑 ID),此分区的 db key 列的 Low Value 以及此分区的 db key 列的 High Value。

在程序 start-up 上,程序id和分区号应该从 architecture 传递给 application(具体来说,从 Control Processing Tasklet)。如果使用 key 列方法,则这些变量用于读取 order 中的 partition table 以确定 application 对 process 的数据范围。此外,在整个处理过程中必须使用分区号:

  • 在 order 中添加输出 files/database 更新,以使 merge process 正常工作。

  • 向批量 log 报告正常处理,并向 architecture 错误处理程序报告任何错误。

4.3 尽量减少僵局

当 applications run 在 parallel 中或被分区时,可能会发生数据库资源争用和死锁。作为数据库设计的一部分,数据库设计团队尽可能地消除潜在的争用情况至关重要。

此外,开发人员必须确保数据库索引表的设计时考虑到死锁和 performance。

管理或 architecture 表中经常会出现死锁或热点,例如 log 表,控制表和锁定表。还应考虑到这些问题的影响。现实的压力测试对于确定 architecture 中可能存在的瓶颈至关重要。

为了最大限度地减少冲突对数据的影响,architecture 在附加到数据库或遇到死锁时应提供 wait-and-retry 间隔等服务。这意味着 built-in 机制对某些数据库 return 代码作出反应,而不是发出立即错误,等待预定量的 time 并重试数据库操作。

4.4 参数传递和验证

分区 architecture 应该对 application 开发人员相对透明。 architecture 应执行与以分区模式运行 application 相关的所有任务,包括:

  • 在 application start-up 之前检索分区参数。

  • 在 application start-up 之前验证分区参数。

  • 将参数传递给 start-up 处的 application。

验证应包括检查以确保:

  • application 有足够的分区来覆盖整个数据范围。

  • 分区之间没有间隙。

如果数据库已分区,则可能需要进行一些额外的验证,以确保单个分区不跨越数据库分区。

此外,architecture 应考虑分区的合并。 关键问题包括:

  • 在进入下一个 job step 之前,是否必须完成所有分区?

  • 如果其中一个分区中止会发生什么?