批量处理策略

批量处理策略

 

为了帮助设计和开发人员设计和实现批量系统,应该以示例结构图表和代码框架的形式,为他们提供基本的批量应用构建块和模式。在开始设计一个批量作业时,应该把业务逻辑分解成一系列的步骤,这些步骤可以使用下列标准构建块来实现:

1、  转换程序:对于每一种外系统提供或产生的文件类型,需要创建转换程序,将提供的交易记录转换成处理需要的标准格式。这种类型的批量应用,可以部分或全部地由转换工具模块组成(见:基本批量服务)

2、  验证程序:验证程序确保所有输入/输出记录正确和一致。典型的验证基于文件头和尾、检查计数和校验算法以及记录级别的交叉检查

3、  抽取程序:从数据库或文件读取记录集,基于预定义的规则选择程序,并将记录写到输出文件

4、  抽取/更新程序:从数据库或文件读取记录集,基于预定义的规则选择程序,根据从每次输入记录中找到的数据,更新数据库或输出文件

5、  处理/更新程序:对经过抽取或验证的输入交易进行处理,通常涉及读取数据库以获取处理需要的数据,可能会更新数据库并为输出处理创建记录

6、  输出/格式化程序:读取输入文件,根据标准的格式,重新组织每条记录数据的结构,然后打印输出到文件,或者传给另一个程序或系统。

此外,利用前面提到的这些构建块,还不能搭建提供给业务逻辑的基础应用外壳。

除了这些主要的构建块,应用可能会用到一个或多个标准的公共步骤,比如:

1、  排序:读取输入文件,根据记录的某个排序字段,将重新排序的记录写到生成的输出文件。排序通常通过标准的系统工具程序执行

2、  拆分:读取单个输入文件,基于某个字段的值,将每条记录写到若干个输出文件中的一个。拆分可以由带参数的标准系统工具程序来裁剪或执行

3、  合并:读取多个输入文件,组合输入文件中的数据,输出到生成的单个文件中。合并可以由带参数的标准系统工具程序来裁剪或执行

 

另外,批量应用可以根据输入源划分为:

1、  数据库驱动型应用:以从数据库获取的记录行或值驱动

2、  文件驱动型应用:以从文件获取的记录或值驱动

3、  消息驱动型应用:以从消息队列获取的消息驱动

 

任何批量系统的基础,是处理策略,影响策略选择的因素包括:估算的批量系统数据量、与联机或其他批量系统的并发性、可用的批量窗口期(batch windows)(随着越来越多的企业希望提升到7X24运行,已经空不出明显的批量窗口期了)。

 

批量的典型处理选择是:

1、  在离线期批量窗口时间里的常规处理

2、  批量/联机并发处理

3、  同时并行处理多个不同的批量运行或作业

4、  分块(即同时处理相同作业的多个实例)

5、  上述情况的组合

 

上面列表的顺序,将影响实现的复杂性,在批量窗口期的处理最简单,而分块实现起来最复杂。

 

商业调度程序可能支持部分或全部的选项。

 

这一节,将详细讨论这些处理选择。有个重要问题需要注意,批量处理采用的提交和锁定策略,依赖于执行的处理类型;而且一般来说,联机的锁策略也应该使用相同的原则。

 

策略可以只使用通常的数据库锁,或者在架构中实现另外的自定义锁服务。锁服务将跟踪数据库锁(比如通过在一个专门的数据库表保存必要的信息),提供或拒绝对应用程序请求数据库操作的许可。还应该在架构中实现重试逻辑,避免万一在锁定状态下,取消批量作业。

 

1       批量窗口期的常规处理

对於单独批量窗口期的简单批量处理的运行,它要更新的数据,不会被联机用户,或其他批量处理,不会有并发问题,可以在批量运行结束时,再做单次提交。

往往越健壮的方式,就越适合。要记住,批量系统不管在复杂性方面,还是在要处理的数据量方面,都有随时间增长的趋势。如果系统没有适当的锁策略,而是依然指望单点提交,批量系统修改起来将很痛苦。所以,即使是最简单的批量系统,也跟下面关于更复杂的情况提到的一样,要为重启-恢复的选择,考虑提交逻辑。

2       批量/联机并发处理

批量应用处理的数据,可能同时被联机用户更新,不应锁定任何数据(不过是数据库里的还是文件里的),可能仅仅几秒钟里,联机用户就会需要这些数据;而且,每隔几个交易,就应该提交更新到数据库中。这将尽可能的减少对其他处理不可用的数据部分,缩短这些数据的不可用时间段。

 

另一个减少物理锁的选择,是实现一个逻辑的行级锁,不论是采用乐观锁模式或悲观锁模式。

1、  乐观锁假定记录竞争的可能性比较低。它通常意味着,在每个被批量和联机处理并发访问的数据库表中,增加一个时间戳列。当一个程序在处理时,获取到一行记录,它同时也取到了时间戳。然后,当这个程序尝试更新这行被处理的数据时,这个更新将在WHERE子句使用原来的时间戳。如果时间戳不匹配,说明有另一个程序已经在它获取记录和尝试更新期间,更新了这同一行记录,因而这次更新不会被执行。

2、  悲观锁假定记录竞争的可能性非常高,因而在获取数据时,需要物理锁或逻辑锁。一种逻辑悲观锁是,在数据库表使用专门一列作为锁。当一个程序为了更新获取这行记录时,会在锁列设置一个标帜,由于存在这个标帜,其他程序在尝试获取这条记录时逻辑上会失败。当这个设置标帜的程序更新这条记录时,它同时会清除这个标帜,让其他程序能够获取到这行记录。请注意,必须维护初始查询和标帜设置之间的数据完整性,比如通过使用数据库锁(例如SELECT FOR UPDATE)。同时也要注意,这种方式与物理锁定有着同样的缺点,除非建立一种超时机制,假如用户去吃午饭了,那么被锁定记录能够自动解锁,这样管理起来会更稍微容易些。

这些模式不一定适合批量处理,但是在批量和联机的并发处理时可能用到(比如,在数据库不支持行锁的情况下)。一般来说,乐观锁更适合联机应用,而悲观锁更适合批量应用。无论何时使用逻辑锁,都必须用相同的方式,通过逻辑锁保护到所有应用访问的数据记录上。

要注意,这两个解决方案都只定位于对单条记录的锁定,我们经常需要锁定逻辑相关的一组记录。对于物理锁,你必须非常小心的管理,以避免潜在的死锁;对于逻辑锁,最好是建立一个逻辑锁管理器,它了解你要保护的逻辑记录组,可以确保锁的清晰一致,不会死锁。这个逻辑锁管理器,通常使用自己的表来实现锁管理、竞争报告、超时机制等。

3       并行处理

并行处理允许多个批量作业并行运行,以最大地减少整个批量处理消耗的时间。如果作业之间不共享相同的文件、数据表或索引空间,这不会有什么问题;如果有,那么应该使用数据分块来实现这个服务。另一个办法是,构建一个架构模块,使用一张控制表来维护依赖关系。控制表的数据行里,应该包含每个共享资源,以及它是否在被某个应用使用。批量框架或并行作业的应用,应该从这张表读取信息,再决定它是否能够访问自己需要的资源。

 

如果数据访问没有问题,可以通过使用另外的线程来实现并行处理。在主机环境,为了确保让所有的进程都有足够的CPU时间,使用了传统的并行作业类。不管怎样,这种方案足够健壮,能够保证所有在运行进程的时间片。

 

并行处理的其他关键问题还包括,负载均衡和公共系统资源的可用性(如文件、数据库缓冲池)等。还要注意,控制表本身也很容易变成临界资源。

4       分块

利用分块(partitioning),可以让大规模批量应用的多个版本并发运行。这样做的目的,是减少处理长批量作业花费的时间。那些输入文件可以拆分或(且)主要的数据库表可以分块,应用可以在不同的数据集上运行的长批量作业,就可以成功的进行分块处理。

 

另外,必须设计好让已分块的处理,只能处理分配给他们的数据集。分块架构必须与数据库设计和数据库分区策略紧密关联。请注意,数据库分区并不必然意味着数据库的物理分区,尽管多数情况下这样做是明智之举。下图阐明了分块方式:

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

4.1     分块方式

下面列出了可能的分块方式。选择分区方式,必须视情况而定。

4.1.1  整理和偶数拆散数据集

这种方式包括拆散输入记录集,分成偶数块(如10),每部分块正好是整个记录集的1/10。然后,每一块由一个批量/抽取程序的实例来处理

 

要使用这种方式,需要对记录集进行拆分处理。拆分的结果,是一个从低到高的限定记录位置的数字序列,批量/抽取程序用它作为输入,以限制只对自己单独的那部分数据进行处理

 

由于需要计算和决定记录集的每块分区的边界,这个处理可能开销很大。

 

4.1.2  按关键列拆散

根据关键列,如地区代码,来拆散输入记录集,一个批量实例分配一个键值的数据。要达到这个目的,列值可以:

1、  通过分区表分配给批量实例(稍后详述)

2、  通过分块部分的值分配给批量实例(如:分块0000-09991000-1999等等)

对于选择1,将意味着,增加新的值时,要手动配置批量/抽取,以确保这个新值添加给某个特定的实例。

对于选择2,它将确保所有的值都会被批量作业的实例覆盖到。但是,某个实例处理数据的数量,依赖于列值的分布(例如,在0000-0999范围内,可能分布有大量的数据,而1000-1999范围内却很少)。这种方式,应该在设计数据范围时就想到分块。

这两者选择,都不能实现对批量实例的数据平均分布,不能使用动态配置批量实例数量。

 

4.1.3  按视图拆散

这种方式,根本上就是按关键列拆散,只不过是在数据库层面。它涉及拆散记录集到视图,这些视图由每个批量应用的实例在处理过程中使用,通过分组数据完成拆分。

 

用这种方式,将针对特定的视图(而不是主表)配置每个批量应用实例。同样,在增加新的数据值是,必须增加一个视图来包含新增加的数据组。对实例数目的变更,同样会导致视图的变更,没有任何动态配置能力。

4.1.4  增加处理指示符

在输入表中增加一个新的列,作为指示符。作为一个处理步骤,所有的指示符将标记为“未处理”。在批量应用抓取数据期间,只读取那些标记为“未处理”的记录,一旦读取(通过锁定),则标记为“处理中”。当这些记录处理完成后,更新指示符为“成功”或“出错”。由于这个附加列保证了一条记录只会被处理一次,多实例批量应用可以不用做修改就启动。

 

使用这种方式,这张表的I/O对动态地增长。如果是更新类的批量应用,由于总是会发生写入操作,这种方式的影响会减少。

4.1.5  抽取表到普通文件

这种方式涉及从表到文件的抽取,然后,把这个文件拆分成多个段,作为批量实例的输入使用。

 

使用这种方式,抽取表到文件并作拆分的额外开销,可能抵消多个分块带来的好处。通过修改文件拆分脚本,可以实现动态配置。

4.1.6  使用哈希列

这种方案,要在数据库表上增加一个哈希列(键/索引),用来获取对应记录。在哈希列上有一个指示器,它决定由哪个批量应用实例,处理这条特定记录。例如,假如启动了3批量实例,那么指示符‘A’将标志这一行由实例1处理,而指示符‘B’标志该行由实例2处理,如此等等。

 

因而获取记录的过程,将增加一个WHERE子句,以通过特定的指示符选择所标志的全部行。插入这张表时,也要增加这个标示列,可以默认为某个实例(如‘A’)。

 

可以用一个简单批量程序来更新这个指示符,比如在不同的实例之间从新分布负载。当增加了足够多数目的新记录行时,就可以运行这个批量(除批量窗口期外的其他任何时候),把这些新行从新分发给其他实例。

 

要增加新的批量应用实例,只需要运行上述这个批量,从新分发标识符给那些新的实例。

4.2     数据库和应用设计原则

要支持运行在用关键列方式分区的数据库表上的多分块应用的架构,应该包含一个中央分块仓库,以保存分块参数。这样做提供灵活性,保证可维护性。这个仓库通常有单表构成,即所谓分区表。

 

保存在分区表中的信息将是静态的,通过应用有DBA来维护。这张表应该由包含多分块应用的每个分块的信息的行组成,它应用有这些列:程序ID码、分块数(分块的逻辑ID)、这个分块的数据库关键列最低值、该分块的数据库关键列最高值。

 

在启动程序时,框架(小任务处理控制)应该把程序ID和分块数传给它,这些变量用于读取分区表,决定这个程序应该处理哪些范围的数据(如果使用关键列方式)。此外,整个处理过程都必须使用这个分块数:

1、  为了正确地进行合并处理,要在输出文件中/数据库更新时添加分块数

2、  报告正常的处理批量日志中,和执行期间发生的任何错误给框架的错误处理器。

4.3     尽量减少死锁

当应用并行或分块运行时,数据库资源产生竞争,可能发生死锁。作为数据库设计的一部分,尽可能地消除潜在的竞争条件,对于数据库设计团队来说,非常关键。

 

同样,要确保数据库索引表为防止死锁和关注性能而设计。

 

死锁或热点,经常发生在管理或框架表,比如日志表、控制表。实现这些表时,也要考虑到这一点。为了鉴别架构中的可能瓶颈,真实的压力测试至关重要。

 

为了将数据冲突的影响减小到最低,框架应该提供这样一些服务,如:当连接到数据库或碰到死锁时,定期地等待并重试。这意味着需要一种内建机制,针对特定的数据库返回码做出反应,而不是抛出即时异常进行处理,等待一段预先确定的时间,再重试数据库操作。

4.4     参数的传递和验证

对应用开发者,分块架构应该相对地透明。以分块模式运行应用,框架应该执行所有与之相关的任务,包括:

1、  在应用启动前获取分块参数

2、  在应用启动前验证分块参数

3、  在启动时传递参数给应用

 

验证应该包括检查和确保:

1、  应用有足够的分块,能覆盖整个数据范围

2、  分块之间没有缺失

 

如果数据库已经分区,可能需要一下附加的验证,以确保单个分块不会横跨数据库分区。

 

框架同样应该考虑分块的合并,关键问题包括:

1、  所有的分块是否必须在进入到下一个作业步骤之前完成?

2、  如果某个分块取消了,会发生什么?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章