批量系统设计之禅
之前写过关于基于SpringBatch框架来实现批量的文章,有些同学反应希望写写关于批量设计的文章,毕竟笔者以前也在国有大银行写过3年的后台OLAP批量系统,在批量程序方面还是掉过很多坑,同时也学到了很多批量优化方面的经验,本篇文章就是总结了我自己批量的经验和原先行里总结的一些批量设计经验,以前我们批量都是使用大量的存储过程来实现功能,使用存储过程的问题就在于扩展性非常差,对某个数据库的依赖非常高,工作中会遇到各种因为数据库产品导致的bug,对程序员SQL要求非常高,优秀的SQL和烂SQL性能可能会差几十倍,所以目前除了一些传统公司还在使用老式的存储过程来实现批量,大部分互联网公司的批量都已经改为使用一些类似Springbatch的框架,通过这样的框架可以减少很多开发工作,而且对于一般能够写java的同学都可以去写批量了,但是做联机和做批量在设计思想上还是有很大差别,本文就给大家梳理下批量设计思路。文章开头说下批量的设计核心思想就是“减少IO”,这是所有批量系统优化性能的核心思路,做批量的同学请牢记这一条!!!
1. 批量系统使用场景
- 定期提交批处理任务
- 并发批处理:并行执行任务
- 分阶段,企业消息驱动处理
- 高并发批处理任务
- 失败后手动或定时重启
- 按顺序处理任务依赖(使用工作流驱动的批处理插件)
- 局部处理:跳过记录(例如在回滚时)
- 完整的批处理事务:因为可能有小数据量的批处理或存在存储过程/脚本
2. 批量设计原则
- 批处理架构通常会影响在线服务的架构,反之亦然。设计架构和环境时请尽可能将联机数据资源和批量数据资源分开。
- 尽可能的简化,避免在单个批处理应用中构建复杂的逻辑结构。
- 尽可能在数据存放的地方处理这些数据,反之亦然(即各自负责处理自己的数据)。
- 尽可能少的使用系统资源,尤其是I/O。尽可能多地在内存中执行大部分操作。
- 审查应用程序I/O(分析SQL语句)以避免不必要的物理I/O。特别是以下四个常见的缺陷需要避免:
- 在每个事务中都将(所有并不需要的)数据读取,并缓存起来;
- 多次读取/查询同一事务中已经读取过的数据;
- 引起不必要的表或索引扫描;
- 在SQL语句的WHERE子句中不指定过滤条件。
- 在同一个批处理不要做两次一样的事。例如,如果你需要报表的数据汇总,请在处理每一条记录时使用增量来存储,尽可能不要再去遍历一次同样的数据。
- 在批处理程序开始时就分配足够的内存,以避免运行过程中再执行耗时的内存分配。
- 总是将数据完整性假定为最坏情况。插入适当的检查和数据校验以保持数据完整性(integrity)。
- 如有可能,请为内部校验实现checksum。例如,平面文件应该有一条结尾记录,说明文件中的总记录数和关键字段的集合。
- 尽可能早地在模拟生产环境下使用真实的数据量,进行计划和执行压力测试。
- 在大型批处理系统中,备份会是一个很大的挑战,特别是 7x24小时不间断的在线服务系统。数据库备份通常在设计时就考虑好了,但是文件备份也应该提升到同养的重要程度。如果系统依赖于文本文件,文件备份程序不仅要正确设置和形成文档,还要定期进行测试。
3. 批量设计策略
为了辅助批处理系统的设计和实现、应该通过结构示意图和代码实例的形式为设计师和程序员提供基础的批处理程序构建模块和以及处理模式. 在设计批处理Job时,应该将业务逻辑分解成一系列的步骤,使每个步骤都可以利用以下的标准构建模块来实现:
- 转换程序(Conversion Applications): 由外部系统提供或需要写入到外部系统的各种类型的文件,我们都需要为其创建一个
- 转换程序, 用来将所提供的事务记录转换成符合要求的标准格式.这种类型的批处理程序可以部分或全部由转换工具模块
- 验证程序(Validation Applications): 验证程序确保所有输入/输出记录都是正确和一致的.验证通常基于文件头和结尾信息,
- 校验和(checksums)以及记录级别的交叉验证算法.
- 提取程序(Extract Applications): 这种程序从数据库或输入文件读取一堆记录,根据预定义的规则选取记录,并将选取的记录
- 写入到输出文件.
- 提取/更新程序(Extract/Update Applications): 这种程序从数据库或输入文件读取记录,并将输入的每条记录都更新到数据库或记录到输出文件.
- 处理和更新程序(Processing and Updating Applications): 这种程序对从 提取或验证程序 传过来的输入事务记录进行处理.这些处理通常包括 从数据库读取数据,有可能更新数据库,并创建输出记录.
- 输出/格式化程序(Output/Format Applications): 这种程序从输入文件中读取信息,将数据重组成为标准格式,并打印到输出文件,或者传输给另一个程序或系统.
因为业务逻辑不能用上面介绍的这些标准模块来完成, 所以还需要另外提供一个基本的程序外壳.
除了这些主要的模块,每个应用还可以使用一到多个标准的实用程序环节(standard utility steps),如:
- Sort 排序,排序程序从输入文件读取记录,并根据记录中的某个key字段重新排序,然后生成输出文件. 排序通常由标准的系统实用程序来执行.
- Split 拆分,拆分程序从单个输入文件中读取记录,根据某个字段的值,将记录写入到不同的输出文件中. 拆分可以自定义或者由参数驱动的(parameter-driven)系统实用程序来执行.
- Merge 合并,合并程序从多个输入文件读取记录,并将组合后的数据写入到单个输出文件中. 合并可以自定义或者由参数驱动的(parameter-driven)系统实用程序来执行.
批处理程序也可以根据输入来源分类:
- 数据库驱动(Database-driven)的应用程序, 由从数据库中获取的行或值驱动.
- 文件驱动(File-driven)的应用程序,是由从文件中获取的值或记录驱动的.
- 消息驱动(Message-driven)的应用程序由从消息队列中检索到的消息驱动.
所有批处理系统的基础都是处理策略.影响策略选择的因素包括: 预估的批处理系统容量, 在线并发或与另一个批处理系统的并发量, 可用的批处理时间窗口(随着越来越多的企业想要全天候(7x24小时)运转,所以基本上没有明确的批处理窗口).
典型的批处理选项包括:
- 在一个批处理窗口中执行常规离线批处理
- 并发批处理/在线处理
- 同一时刻有许多不同的批处理在并行执行
- 分区(即同一时刻,有多个实例在处理同一个job)
- 上面这些的组合
上面列表中的顺序代表了批处理实现复杂性的排序,在同一个批处理窗口的处理最简单,而分区实现最复杂,分区实现虽然并发性能高能够充分利用IO资源,但是缺点是对数据结构设计和批量设计要求很高,弄不好就会出很多多线程或资源死锁问题导致批量加工数据错误或中断,所以如果数据量不是非常巨大就不要用分区批量实现方案了,当你数据量足够大了,并且自己已经能力提高并能够充分理解批量精髓后,自然就会灵活使用批量分区实现方案了.
总结
本文主要是从各个角度讲诉了下批量设计的思路,真的只是思路,估计很多同学看完了很多条tips都不知道在说啥或者不知道如何实践,如果具体展开讲这些tips要写太多字了,本人比较懒不想写了,如果感兴趣的人比较多后续可能会对于大家感兴趣的一些tips展开讲讲后面的故事。