Spring Batch 批量处理策略

为了帮助设计和实现批量处理系统,基本的批量应用是经过块和模式来构建的,同时也应该可以为程序开发人员和设计人员提供结构的样例和基础的批量处理程序。算法

当你开始设计一个批量做业任务的时候,商业逻辑应该被拆分一系列的步骤,而这些步骤又是能够经过下面的标准构件块来实现的:数据库

  • 转换应用程序(Conversion Applications):针对每个从外部系统导出或者提供的各类类型的文件,咱们都须要建立一个转换应用程序来说这些类型的文件和数据转换为处理所须要的标准格式。这个类型的批量应用程序能够是正规转换工具模块中的一部分,也能够是整个的转换工具模块(请查看:基本的批量服务(Basic Batch Services))。
  • 校验应用程序(Validation Applications):校验应用程序可以保证全部的输入和输出记录都是正确和一致的。校验一般是基于头和尾进行校验的,校验码和校验算法一般是针对记录的交叉验证。
  • 提取应用(Extract Applications): 这个应用程序一般被用来从数据库或者文本文件中读取一系列的记录,并对记录的选择一般是基于预先肯定的规则,而后将这些记录输出到输出文件中。
  • 提取/更新应用(Extract/Update Applications):这个应用程序一般被用来从数据库或者文本文件中读取记录,并将每一条读取的输入记录更新到数据库或者输出数据库中。
  • 处理和更新应用(Processing and Updating Applications):这种程序对从提取或验证程序 传过来的输入事务记录进行处理。这处理一般包括有读取数据库而且得到须要处理的数据,为输出处理更新数据库或建立记录。
  • 输出和格式化应用(Output/Format Applications):一个应用经过读取一个输入文件,对输入文件的结构从新格式化为须要的标准格式,而后建立一个打印的输出文件,或将数据传输到其余的程序或者系统中。

更多的,一个基本的应用外壳应该也可以被针对商业逻辑来提供,这个外壳一般不能经过上面介绍的这些标准模块来完成。架构

另外的一个主要的构建块,每个引用一般可使用下面的一个或者多个标准工具步骤,例如:并发

  • 分类(Sort)- 一个程序能够读取输入文件后生成一个输出文件,在这个输出文件中能够对记录进行从新排序,从新排序的是根据给定记录的关键字段进行从新排序的。分类一般使用标准的系统工具来执行。
  • 拆分(Split)- 一个程序能够读取输入文件后,根据须要的字段值,将输入的文件拆分为多个文件进行输出。拆分一般使用标准的系统工具来执行。
  • 合并(Merge)- 一个程序能够读取多个输入文件,而后将多个输入文件进行合并处理后生成为一个单一的输出文件。合并能够自定义或者由参数驱动的(parameter-driven)系统实用程序来执行.

批量处理应用程序能够经过下面的输入数据类型来进行分类:app

  • 数据库驱动应用程序(Database-driven applications)能够经过从数据库中得到的行或值来进行驱动。
  • 文件驱动应用程序(File-driven applications) 能够经过从文件中得到的数据来进行驱动。
  • 消息驱动应用程序(Message-driven applications) 能够经过从消息队列中得到的数据来进行驱动。

全部批量处理系统的处理基础都是策略(strategy)。对处理策略进行选择产生影响的因素包括有:预估批量处理须要处理的数据量,在线并发量,和另一个批量处理系统的在线并发量,可用的批量处理时间窗口(不少企业都但愿系统是可以不间断运行的,基本上来讲批量处理可能没有处理时间窗口)。框架

针对批量处理的标准处理选项包括有:数据库设计

  • 在一个批处理窗口中执行常规离线批处理
  • 并发批量 / 在线处理
  • 并发处理不少不一样的批量处理或者有不少批量做业在同一时间运行
  • 分区(Partitioning),就是在同一时间有不少示例在运行相同的批量做业
  • 混合上面的一些需求

上面列表中的顺序表明了批处理实现复杂性的排序,在同一个批处理窗口的处理最简单,而分区实现最复杂。工具

上面的一些选项或者全部选项可以被商业的任务调度所支持。性能

在下面的部分,咱们将会针对上面的处理选项来对细节进行更多的说明。须要特别注意的是,批量处理程序使用提交和锁定策略将会根据批量处理的不一样而有所不一样。做为最佳实践,在线锁策略应该使用相同的原则。所以,在设计批处理总体架构时不能简单地拍脑壳决定,须要进行详细的分析和论证。测试

锁定策略能够仅仅使用常见的数据库锁或者你也能够在系统架构中使用其余的自定义锁定服务。这个锁服务将会跟踪数据库的锁(例如在一个专用的数据库表(db-table)中存储必要的信息),而后在应用程序请求数据库操做时授予权限或拒绝。重试逻辑应该也须要在系统架构中实现,以免批量做业中的因资源锁定而致使批量任务被终止。

批量处理做业窗口中的常规处理

针对运行在一个单独批处理窗口中的简单批量处理,更新的数据对在线用户或其余批处理来讲并无实时性要求,也没有并发问题,在批处理运行完成后执行单次提交便可。

大多数状况下,一种更健壮的方法会更合适.要记住的是,批处理系统会随着时间的流逝而增加,包括复杂度和须要处理的数据量。若是没有合适的锁定策略,系统仍然依赖于一个单一的提交点,则修改批处理程序会是一件痛苦的事情。 所以,即便是最简单的批处理系统,也应该为重启-恢复(restart-recovery)选项考虑提交逻辑。针对下面的状况,批量处理就更加复杂了。

并发批量 / 在线处理

批处理程序处理的数据若是会同时被在线用户实时更新,就不该该锁定在线用户须要的全部任何数据(无论是数据库仍是文件),即便只须要锁定几秒钟的时间。

还应该每处理一批事务就提交一次数据库。这减小了其余程序不可用的数据数据量,也压缩了数据不可用的时间。

另外一个可使用的方案就是使用逻辑行基本的锁定实现来替代物理锁定。经过使用乐观锁(Optimistic Locking )或悲观锁(Pessimistic Locking)模式。

  • 乐观锁假设记录争用的可能性很低。这一般意味着并发批处理和在线处理所使用的每一个数据表中都有一个时间戳列。当程序读取一行进行处理时,同时也得到对应的时间戳。当程序处理完该行之后尝试更新时,在 update 操做的 WHERE 子句中使用原来的时间戳做为条件.若是时间戳相匹配,则数据和时间戳都更新成功。若是时间戳不匹配,这代表在本程序上次获取和这次更新这段时间内已经有另外一个程序修改了同一条记录,所以更新不会被执行。
  • 悲观锁定策略假设记录争用的可能性很高,所以在检索时须要得到一个物理锁或逻辑锁。有一种悲观逻辑锁在数据表中使用一个专用的 lock-column 列。当程序想要为更新目的而获取一行时,它在 lock column 上设置一个标志。若是为某一行设置了标志位,其余程序在试图获取同一行时将会逻辑上获取失败。当设置标志的程序更新该行时,它也同时清除标志位,容许其余程序获取该行。请注意,在初步获取和初次设置标志位这段时间内必须维护数据的完整性,好比使用数据库锁(例如,SELECT FOR UPDATE)。还请注意,这种方法和物理锁都有相同的缺点,除了它在构建一个超时机制时比较容易管理。好比记录而用户去吃午饭了,则超时时间到了之后锁会被自动释放。

这些模式并不必定适用于批处理,但他们能够被用在并发批处理和在线处理的状况下(例如,数据库不支持行级锁)。做为通常规则,乐观锁更适合于在线应用,而悲观锁更适合于批处理应用。只要使用了逻辑锁,那么全部访问逻辑锁保护的数据的程序都必须采用一样的方案。

请注意:这两种解决方案都只锁定(address locking)单条记录。但不少状况下咱们须要锁定一组相关的记录。若是使用物理锁,你必须很是当心地管理这些以免潜在的死锁。若是使用逻辑锁,一般最好的解决办法是建立一个逻辑锁管理器,使管理器能理解你想要保护的逻辑记录分组(groups),并确保连贯和没有死锁(non-deadlocking)。这种逻辑锁管理器一般使用其私有的表来进行锁管理、争用报告、超时机制 等等。

并行处理

并行处理容许多个批量处理运行(run)/任务(job)同时并行地运行。以使批量处理总运行时间降到最低。若是多个任务不使用相同的文件、数据表、索引空间时,批量处理这些不算什么问题。若是确实存在共享和竞争,那么这个服务就应该使用分区数据来实现。另外一种选择是使用控制表来构建一个架构模块以维护他们之间的相互依赖关系。控制表应该为每一个共享资源分配一行记录,无论这些资源是否被某个程序所使用。执行并行做业的批处理架构或程序随后将查询这个控制表,以肯定是否能够访问所需的资源。

若是解决了数据访问的问题,并行处理就能够经过使用额外的线程来并行实现。在传统的大型主机环境中,并行做业类上一般被用来确保全部进程都有充足的 CPU 时间。不管如何,解决方案必须足够强劲,以确保全部正在运行的进程都有足够的运行处理时间。

并行处理的其余关键问题还包括负载平衡以及通常系统资源的可用性(如文件、数据库缓冲池等)。请注意,控制表自己也可能很容易变成一个相当重要的资源(有可能发生严重竞争)。

分区

分区技术容许多版本的大型批处理程序并发地(concurrently)运行。这样作的目的是减小超长批处理做业过程所需的时间。

能够成功分区的过程主要是那些能够拆分的输入文件 和/或 主要的数据库表被分区以容许程序使用不一样的数据来运行。

此外,被分区的过程必须设计为只处理分配给他的数据集。分区架构与数据库设计和数据库分区策略是密切相关的。请注意,数据库分区并不必定指数据库须要在物理上实现分区,尽管在大多数状况下这是明智的。

下面的图片展现了分区的方法:

上图: 分区处理

系统架构应该足够灵活,以容许动态配置分区的数量。自动控制和用户配置都应该归入考虑范围。自动配置能够根据参数来决定,例如输入文件大小 和/或 输入记录的数量。

分区方案

面列出了一些可能的分区方案,至于具体选择哪一种分区方案,要根据具体状况来肯定:

固定和均衡拆分记录集

这涉及到将输入的记录集合分解成均衡的部分(例如,拆分为 10 份,这样每部分是整个数据集的十分之一)。每一个拆分的部分稍后由一个批处理/提取程序实例来处理。

为了使用这种方案,须要在预处理时候就将记录集进行拆分。拆分的结果有一个最大值和最小值的位置,这两个值能够用做限制每一个 批处理/提取程序处理部分的输入。

预处理可能有一个很大的开销,由于它必须计算并肯定的每部分数据集的边界。

经过关键字段(Key Column)拆分

这涉及到将输入记录按照某个关键字段来拆分,好比一个地区代码(location code),并将每一个键分配给一个批处理实例。为了达到这个目标,也可使用列值。

经过分区表来指派给一个批量处理实例

请查看下面的详细说明。

在使用这种方法时, 新值的添加将意味着须要手动从新配置批处理/提取程序,以确保新值被添加到某个特定的实例。

经过数据的部分值指派给一个批量处理实例

例如,值 0000-0999, 1000 - 1999, 等。

使用这种方法的时候,将确保全部的值都会被某个批处理做业实例处理到。然而,一个实例处理的值的数量依赖于列值的分布(便可能存在大量的值分布在0000-0999范围内,而在1000-1999范围内的值却不多)。若是使用这种方法,设计时应该考虑到数据范围的切分。

使用 经过分区表来指派 和 经过数据的部分值, 在这两种方法中,并不能将指定给批处理实例的记录实现最佳均匀分布。批处理实例的数量并不能动态配置。

经过视图(Views)

这种方法基本上是根据键列来分解,但不一样的是在数据库级进行分解。它涉及到将记录集分解成视图。这些视图将被批处理程序的各个实例在处理时使用。分解将经过数据分组来完成。

使用这个方法时,批处理的每一个实例都必须为其配置一个特定的视图(而非主表)。固然,对于新添加的数据,这个新的数据分组必须被包含在某个视图中。也没有自动配置功能,实例数量的变化将致使视图须要进行相应的改变。

附加的处理识别器

这涉及到输入表一个附加的新列,它充当一个指示器。在预处理阶段,全部指示器都被标志为未处理。在批处理程序获取记录阶段,只会读取被标记为未处理的记录,一旦他们被读取(并加锁),它们就被标记为正在处理状态。当记录处理完成,指示器将被更新为完成或错误。批处理程序的多个实例不须要改变就能够开始,由于附加列确保每条纪录只被处理一次。

使用该选项时,表上的I/O会动态地增加。在批量更新的程序中,这种影响被下降了,由于写操做是一定要进行的。

提取表到无格式文件

这包括将表中的数据提取到一个文件中。而后能够将这个文件拆分红多个部分,做为批处理实例的输入。

使用这个选项时,将数据提取到文件中,并将文件拆分的额外开销,有可能抵消多分区处理(multi-partitioning)的效果。能够经过改变文件分割脚原本实现动态配置。

With this option, the additional overhead of extracting the table into a file, and splitting it, may cancel out the effect of multi-partitioning. Dynamic configuration can be achieved via changing the file splitting script.

使用哈希列(Hashing Column)

这个计划须要在数据库表中增长一个哈希列(key/index)来检索驱动(driver)记录。这个哈希列将有一个指示器来肯定将由批处理程序的哪一个实例处理某个特定的行。例如,若是启动了三个批处理实例,那么 “A” 指示器将标记某行由实例 1 来处理,“B”将标记着将由实例 2 来处理,以此类推。

稍后用于检索记录的过程(procedure)程序,将有一个额外的 WHERE 子句来选择以一个特定指标标记的全部行。这个表的插入(insert)须要附加的标记字段,默认值将是其中的某一个实例(例如“A”)。

一个简单的批处理程序将被用来更新不一样实例之间的从新分配负载的指标。当添加足够多的新行时,这个批处理会被运行(在任什么时候间,除了在批处理窗口中)。

批处理应用程序的其余实例只须要像上面这样的批处理程序运行着以从新分配指标,以决定新实例的数量。

数据库和应用设计原则

若是一个支持多分区(multi-partitioned)的应用程序架构,基于数据库采用关键列(key column)分区方法拆成的多个表,则应该包含一个中心分区仓库来存储分区参数。这种方式提供了灵活性,并保证了可维护性。这个中心仓库一般只由单个表组成,叫作分区表。

存储在分区表中的信息应该是是静态的,而且只能由 DBA 维护。每一个多分区程序对应的单个分区有一行记录,组成这个表。这个表应该包含这些列:程序 ID 编号,分区编号(分区的逻辑ID),一个分区对应的关键列(key column)的最小值,分区对应的关键列的最大值。

在程序启动时,应用程序架构(Control Processing Tasklet, 控制处理微线程)应该将程序 id 和分区号传递给该程序。这些变量被用于读取分区表,来肯定应用程序应该处理的数据范围(若是使用关键列的话)。另外分区号必须在整个处理过程当中用来:

  • 为了使合并程序正常工做,须要将分区号添加到输出文件/数据库更新
  • 向框架的错误处理程序报告正常处理批处理日志和执行期间发生的全部错误

死锁最小化

当程序并行或分区运行时,会致使数据库资源的争用,还可能会发生死锁(Deadlocks)。其中的关键是数据库设计团队在进行数据库设计时必须考虑尽量消除潜在的竞争状况。

还要确保设计数据库表的索引时考虑到性能以及死锁预防。

死锁或热点每每发生在管理或架构表上,如日志表、控制表、锁表(lock tables)。这些影响也应该归入考虑。为了肯定架构可能的瓶颈,一个真实的压力测试是相当重要的。

要最小化数据冲突的影响,架构应该提供一些服务,如附加到数据库或遇到死锁时的 等待-重试(wait-and-retry)间隔时间。这意味着要有一个内置的机制来处理数据库返回码,而不是当即引起错误处理,须要等待一个预约的时间并重试执行数据库操做。

参数处理和校验

对程序开发人员来讲,分区架构应该相对透明。框架以分区模式运行时应该执行的相关任务包括:

  • 在程序启动以前获取分区参数
  • 在程序启动以前验证分区参数
  • 在启动时将参数传递给应用程序

验证(validation)要包含必要的检查,以确保:

  • 应用程序已经足够涵盖整个数据的分区
  • 在各个分区之间没有遗漏断代(gaps)

若是数据库是分区的,可能须要一些额外的验证来保证单个分区不会跨越数据库的片区。

体系架构应该考虑整合分区(partitions).包括如下关键问题:

  • 在进入下一个任务步骤以前是否全部的分区都必须完成?
  • 若是一个分区 Job 停止了要怎么处理?

https://www.cwiki.us/display/SpringBatchZH/Batch+Processing+Strategies

相关文章
相关标签/搜索