Spring批次介绍

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

  • 自动、复杂地处理大量信息,无需用户交互即可最有效地处理。这些操作通常包括基于时间的事件(例如月末计算、通知或通信)。

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

  • 将从内部和外部系统接收到的信息集成到记录系统中,这些信息通常需要以事务方式进行格式化、验证和处理。批处理用于每天为企业处理数十亿笔交易。

Spring Batch 是一个轻量级、全面的批处理框架,旨在支持开发对企业系统的日常运营至关重要的健壮批处理应用程序。Spring Batch 建立在人们所期望的 Spring Framework 的特性(生产力、基于 POJO 的开发方法和一般易用性)之上,同时使开发人员在必要时可以轻松访问和利用更先进的企业服务。Spring Batch 不是调度框架。在商业和开源空间中都有许多优秀的企业调度程序(例如 Quartz、Tivoli、Control-M 等)可用。它旨在与调度程序一起工作,而不是取代调度程序。

Spring Batch 提供了在处理大量记录时必不可少的可重用功能,包括日志记录/跟踪、事务管理、作业处理统计、作业重新启动、跳过和资源管理。它还提供更先进的技术服务和功能,通过优化和分区技术实现超大容量和高性能的批处理作业。Spring Batch 既可以用于简单的用例(例如将文件读入数据库或运行存储过程),也可以用于复杂的大容量用例(例如在数据库之间移动大量数据、转换数据等)上)。大批量批处理作业可以以高度可扩展的方式利用该框架来处理大量信息。

背景

虽然开源软件项目和相关社区更加关注基于 Web 和基于微服务的架构框架,但对于满足基于 Java 的批处理需求的可重用架构框架却明显缺乏关注,尽管需要继续处理此类问题在企业 IT 环境中进行处理。由于缺乏标准的、可重用的批处理架构,导致在客户企业 IT 功能中开发的许多一次性内部解决方案激增。

SpringSource(现为 Pivotal)和埃森哲合作改变了这一点。埃森哲在实施批处理架构方面的实际行业和技术经验、SpringSource 深厚的技术经验以及 Spring 久经考验的编程模型共同构成了一种自然而强大的合作伙伴关系,旨在创建高质量、与市场相关的软件,旨在填补企业 Java 的重要空白. 两家公司都与许多客户合作,这些客户通过开发基于 Spring 的批处理架构解决方案来解决类似的问题。这提供了一些有用的额外细节和现实生活中的限制,有助于确保解决方案可以应用于客户提出的现实问题。

埃森哲为 Spring Batch 项目贡献了以前专有的批处理架构框架,以及提交者资源以推动支持、增强和现有功能集。埃森哲的贡献基于数十年来在使用最后几代平台构建批处理架构方面的经验:COBOL/Mainframe、C++/Unix,以及现在的 Java/anywhere。

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

使用场景

一个典型的批处理程序通常:

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

  • 以某种方式处理数据。

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

Spring Batch 自动化了这个基本的批处理迭代,提供了将类似事务作为一组处理的能力,通常是在离线环境中,无需任何用户交互。批处理作业是大多数 IT 项目的一部分,而 Spring Batch 是唯一提供强大的企业级解决方案的开源框架。

业务场景

  • 定期提交批处理

  • 并发批处理:一个作业的并行处理

  • 分阶段的企业消息驱动处理

  • 大规模并行批处理

  • 失败后手动或计划重启

  • 相关步骤的顺序处理(扩展工作流驱动的批处理)

  • 部分处理:跳过记录(例如,在回滚时)

  • 整批事务,适用于小批量或现有存储过程/脚本的情况

技术目标

  • 批处理开发人员使用 Spring 编程模型:专注于业务逻辑,让框架负责基础设施。

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

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

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

  • 通过在所有层中利用 spring 框架,易于配置、自定义和扩展服务。

  • 所有现有的核心服务都应该易于替换或扩展,而不会对基础设施层产生任何影响。

  • 提供一个简单的部署模型,架构 JAR 与应用程序完全分离,使用 Maven 构建。

Spring Batch 架构

Spring Batch 在设计时考虑了可扩展性和多样化的最终用户群体。下图显示了支持最终用户开发人员可扩展性和易用性的分层架构。

图 1.1:Spring Batch 分层架构
图 1. Spring Batch 分层架构

这种分层架构突出了三个主要的高级组件:应用程序、核心和基础架构。该应用程序包含开发人员使用 Spring Batch 编写的所有批处理作业和自定义代码。Batch Core 包含启动和控制批处理作业所需的核心运行时类。它包括 JobLauncherJob和的实现Step。应用程序和核心都建立在一个通用的基础设施之上。此基础架构包含常见的读取器和写入器以及服务(例如RetryTemplate),应用程序开发人员(读取器和写入器,例如ItemReaderand ItemWriter)和核心框架本身(重试,它自己的库)都使用它们。

一般批次原则和指南

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

  • 请记住,批处理架构通常会影响在线架构,反之亦然。尽可能使用通用构建块进行设计,同时考虑架构和环境。

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

  • 将数据的处理和存储物理上紧密地结合在一起(换句话说,将您的数据保存在您进行处理的地方)。

  • 最小化系统资源使用,尤其是 I/O。在内部存储器中执行尽可能多的操作。

  • 查看应用程序 I/O(分析 SQL 语句)以确保避免不必要的物理 I/O。特别是,需要寻找以下四个常见缺陷:

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

    • 为在同一事务中较早读取数据的事务重新读取数据。

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

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

  • 不要在批处理中做两次。例如,如果您需要出于报告目的进行数据汇总,您应该(如果可能)在最初处理数据时增加存储的总数,这样您的报告应用程序就不必重新处理相同的数据。

  • 在批处理应用程序开始时分配足够的内存,以避免在此过程中进行耗时的重新分配。

  • 在数据完整性方面始终假设最坏的情况。插入足够的检查和记录验证以保持数据完整性。

  • 尽可能为内部验证实施校验和。例如,平面文件应该有一个尾记录,告诉文件中的记录总数和关键字段的聚合。

  • 在具有真实数据量的类生产环境中尽早计划和执行压力测试。

  • 在大型批处理系统中,备份可能具有挑战性,尤其是当系统以 24-7 的方式同时在线运行时。数据库备份通常在联机设计中得到很好的照顾,但文件备份应该被认为同样重要。如果系统依赖于平面文件,则文件备份程序不仅应到位并记录在案,而且还应定期进行测试。

批处理策略

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

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

  • 验证应用程序:验证应用程序确保所有输入/输出记录正确且一致。验证通常基于文件头和尾、校验和和验证算法以及记录级交叉检查。

  • 提取应用程序:从数据库或输入文件中读取一组记录、根据预定义规则选择记录并将记录写入输出文件的应用程序。

  • 提取/更新应用程序:从数据库或输入文件中读取记录并根据在每个输入记录中找到的数据对数据库或输出文件进行更改的应用程序。

  • 处理和更新应用程序:对来自提取或验证应用程序的输入事务执行处理的应用程序。处理通常涉及读取数据库以获取处理所需的数据,可能会更新数据库并创建用于输出处理的记录。

  • 输出/格式化应用程序:读取输入文件、根据标准格式从该记录重组数据并生成输出文件以打印或传输到另一个程序或系统的应用程序。

此外,应该为无法使用前面提到的构建块构建的业务逻辑提供一个基本的应用程序外壳。

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

  • 排序:读取输入文件并生成输出文件的程序,其中记录已根据记录中的排序键字段重新排序。排序通常由标准系统实用程序执行。

  • 拆分:读取单个输入文件并根据字段值将每条记录写入多个输出文件之一的程序。拆分可以由参数驱动的标准系统实用程序定制或执行。

  • 合并:从多个输入文件中读取记录并使用来自输入文件的组合数据生成一个输出文件的程序。可以通过参数驱动的标准系统实用程序来定制或执行合并。

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

  • 数据库驱动的应用程序由从数据库中检索的行或值驱动。

  • 文件驱动的应用程序由从文件中检索的记录或值驱动。

  • 消息驱动的应用程序由从消息队列中检索的消息驱动。

任何批处理系统的基础都是处理策略。影响策略选择的因素包括:估计的批处理系统容量、与在线系统或其他批处理系统的并发性、可用的批处理窗口。(请注意,随着越来越多的企业希望 24x7 全天候运行,清晰的批处理窗口正在消失)。

批处理的典型处理选项是(按实现复杂度递增的顺序):

  • 离线模式下批处理窗口期间的正常处理。

  • 并发批处理或在线处理。

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

  • 分区(同时处理同一作业的多个实例)。

  • 上述选项的组合。

商业调度程序可能支持这些选项中的一些或全部。

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

锁定策略可以是仅使用普通的数据库锁定,也可以在架构中实现额外的自定义锁定服务。锁定服务将跟踪数据库锁定(例如,通过将必要的信息存储在专用的数据库表中)并授予或拒绝请求数据库操作的应用程序的权限。此架构还可以实现重试逻辑,以避免在锁定情况下中止批处理作业。

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

在大多数情况下,更稳健的方法更合适。请记住,批处理系统有随着时间的推移而增长的趋势,无论是在复杂性还是它们处理的数据量方面。如果没有锁定策略并且系统仍然依赖于单个提交点,那么修改批处理程序可能会很痛苦。因此,即使使用最简单的批处理系统,也要考虑重新启动恢复选项的提交逻辑的需求,以及本节后面描述的有关更复杂情况的信息。

2. 并发批处理或在线处理批处理应用程序处理在线用户可以同时更新的数据,不应锁定在线用户可能需要的任何数据(无论是在数据库中还是在文件中)超过一个几秒钟。此外,更新应该在每几个事务结束时提交到数据库。这最大限度地减少了其他进程不可用的数据部分以及数据不可用的经过时间。

最小化物理锁定的另一个选项是使用乐观锁定模式或悲观锁定模式实现逻辑行级锁定。

  • 乐观锁定假设记录争用的可能性很小。这通常意味着在批处理和在线处理同时使用的每个数据库表中插入一个时间戳列。当应用程序获取一行进行处理时,它也会获取时间戳。当应用程序尝试更新已处理的行时,更新使用 WHERE 子句中的原始时间戳。如果时间戳匹配,则更新数据和时间戳。如果时间戳不匹配,这表明另一个应用程序在获取和更新尝试之间更新了同一行。因此,无法执行更新。

  • 悲观锁定是任何锁定策略,它假定记录争用的可能性很高,因此需要在检索时获得物理或逻辑锁定。一种悲观逻辑锁定在数据库表中使用专用的锁定列。当应用程序检索要更新的行时,它会在锁定列中设置一个标志。使用该标志,尝试检索同一行的其他应用程序在逻辑上会失败。当设置标志的应用程序更新行时,它也会清除标志,从而使其他应用程序能够检索该行。请注意,在初始获取和设置标志之间也必须保持数据的完整性,例如通过使用数据库锁(例如SELECT FOR UPDATE)。另请注意,此方法与物理锁定具有相同的缺点,只是它更容易管理构建一个超时机制,如果用户在记录被锁定的情况下去吃午饭时释放锁。

这些模式不一定适用于批处理,但它们可能用于并发批处理和在线处理(例如在数据库不支持行级锁定的情况下)。一般来说,乐观锁更适合在线应用,而悲观锁更适合批量应用。每当使用逻辑锁时,所有访问受逻辑锁保护的数据实体的应用程序都必须使用相同的方案。

请注意,这两种解决方案都只解决了锁定单个记录的问题。通常,我们可能需要锁定一组逻辑相关的记录。使用物理锁,您必须非常小心地管理这些锁,以避免潜在的死锁。使用逻辑锁,通常最好构建一个逻辑锁管理器,该管理器了解您要保护的逻辑记录组,并且可以确保锁是连贯的和非死锁的。这个逻辑锁管理器通常使用自己的表来进行锁管理、争用报告、超时机制和其他问题。

3. 并行处理并行处理允许多个批处理运行或作业并行运行,以最大限度地减少批处理运行的总时间。只要作业不共享相同的文件、数据库表或索引空间,这不是问题。如果他们这样做,则应使用分区数据来实现此服务。另一种选择是构建一个架构模块,通过使用控制表来维护相互依赖关系。控制表应该包含每个共享资源的一行,以及它是否正在被应用程序使用。然后,批处理架构或并行作业中的应用程序将从该表中检索信息,以确定它是否可以访问所需的资源。

如果数据访问没有问题,可以通过使用额外的线程并行处理来实现并行处理。在大型机环境中,传统上使用并行作业类,以确保所有进程都有足够的 CPU 时间。无论如何,解决方案必须足够健壮,以确保所有运行进程的时间片。

并行处理中的其他关键问题包括负载平衡和通用系统资源(如文件、数据库缓冲池等)的可用性。另请注意,控制表本身很容易成为关键资源。

4. 分区使用分区允许多个版本的大批量应用程序同时运行。这样做的目的是减少处理长批处理作业所需的时间。可以成功分区的进程是可以拆分输入文件和/或对主数据库表进行分区以允许应用程序针对不同数据集运行的进程。

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

图 1.2:分区进程
图 2. 分区进程

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

4.1 分区方法 必须根据具体情况选择分区方法。以下列表描述了一些可能的分区方法:

1. 记录集的固定甚至打破

这涉及将输入记录集分成偶数个部分(例如,10 个,其中每个部分正好是整个记录集的 1/10)。然后每个部分由批处理/提取应用程序的一个实例处理。

为了使用这种方法,需要预处理来拆分记录集。此拆分的结果将是一个下限和上限放置编号,可用作批处理/提取应用程序的输入,以便将其处理限制为仅其部分。

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

2.按关键列分解

这涉及通过键列(例如位置代码)分解输入记录集,并将每个键的数据分配给批处理实例。为了实现这一点,列值可以是:

  • 由分区表分配给批处理实例(本节稍后介绍)。

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

在选项 1 下,添加新值意味着手动重新配置批处理/提取以确保将新值添加到特定实例。

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

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

3. 按视图分解

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

使用此选项,必须将批处理应用程序的每个实例配置为访问特定视图(而不是主表)。此外,随着新数据值的添加,这组新数据必须包含在视图中。没有动态配置功能,因为实例数量的变化会导致视图的变化。

4. 增加处理指标

这涉及在输入表中添加一个新列,作为一个指标。作为预处理步骤,所有指标都被标记为未处理。在批处理应用程序的记录获取阶段,在记录被标记为未处理的条件下读取记录,一旦读取(带锁),它们被标记为正在处理。当该记录完成时,指示器会更新为完成或错误。批处理应用程序的许多实例无需更改即可启动,因为附加列可确保仅处理一次记录。

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

5. 将表格提取到平面文件

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

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

6. 哈希列的使用

该方案涉及向用于检索驱动程序记录的数据库表添加哈希列(键/索引)。此散列列有一个指示符,用于确定批处理应用程序的哪个实例处理此特定行。例如,如果要启动三个批处理实例,则指示符“A”标记行由实例 1 处理,指示符“B”标记行由实例 2 处理,指示符“C” ' 标记要由实例 3 处理的行。

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

将使用一个简单的批处理应用程序来更新指标,例如在不同实例之间重新分配负载。当添加了足够多的新行时,可以运行此批处理(任何时候,除了在批处理窗口中)以将新行重新分配给其他实例。

批处理应用程序的其他实例只需要运行前面段落中描述的批处理应用程序来重新分配指标以使用新数量的实例。

4.2 数据库和应用程序设计原则

支持使用键列方法针对分区数据库表运行的多分区应用程序的体系结构应包括用于存储分区参数的中央分区存储库。这提供了灵活性并确保了可维护性。存储库通常由单个表组成,称为分区表。

存储在分区表中的信息是静态的,通常应由 DBA 维护。该表应包含多分区应用程序的每个分区的一行信息。该表应包含 Program ID Code、Partition Number(分区的逻辑 ID)、此分区的 db key 列的 Low Value 和此分区的 db key 列的 High Value 的列。

在程序启动时,程序id和分区号应该从体系结构(特别是从控制处理任务小程序)传递给应用程序。如果使用键列方法,这些变量用于读取分区表以确定应用程序要处理的数据范围。此外,必须在整个处理过程中使用分区号以:

  • 添加到输出文件/数据库更新,以使合并过程正常工作。

  • 向批处理日志报告正常处理,向架构错误处理程序报告任何错误。

4.3 最小化死锁

当应用程序并行运行或分区时,可能会发生数据库资源争用和死锁。作为数据库设计的一部分,数据库设计团队尽可能地消除潜在的争用情况是至关重要的。

此外,开发人员必须确保在设计数据库索引表时考虑到死锁预防和性能。

死锁或热点经常出现在管理表或架构表中,例如日志表、控制表和锁表。这些的影响也应该被考虑在内。现实的压力测试对于识别架构中可能存在的瓶颈至关重要。

为了最大限度地减少冲突对数据的影响,架构应在附加到数据库或遇到死锁时提供等待和重试间隔等服务。这意味着一种内置机制来响应某些数据库返回代码,而不是立即发出错误,而是等待预定的时间并重试数据库操作。

4.4 参数传递和验证

分区架构对应用程序开发人员应该是相对透明的。该架构应执行与以分区模式运行应用程序相关的所有任务,包括:

  • 在应用程序启动之前检索分区参数。

  • 在应用程序启动之前验证分区参数。

  • 在启动时将参数传递给应用程序。

验证应包括检查以确保:

  • 应用程序有足够的分区来覆盖整个数据范围。

  • 分区之间没有间隙。

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

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

  • 在进入下一个工作步骤之前必须完成所有分区吗?

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


1. see XML Configuration