近期我同事将原本一个多小时的复杂批处理任务优化成了 5分钟
让我哑然不已,于是连忙请教..............
缘起:
近期因为业务变动的原因,导致原本的批处理任务需要调整,
这一块是由我同事负责. 本来批处理需要 30分钟 加上本次新增的业务处理完需要20分钟, 所以改动完成的批处理将近一个小时
而众所周知- 批处理这一点向来都是性能杀手,有时因为一点不注意导致性能严重下滑.
但即是如此一个小时这个结果也是万万不能接受的. 那么该如何是好呢?
从 60 变成 30 分钟:
首先 当前测试数据量大概在 700万左右,并且一张表具有70多个字段(为什么会有这种表?!!(╯‵□′)╯︵┻━┻ )。
由于 当前业务逻辑中中 sql 分布状态如下:
- 全批量的 select
- 小部分insert
- 大部分的update
于是就将目光放在了 数据库之上。
insert 与 update:
因为当前的情况是 单表 ,字段多(70多个字段), 数据量大。
而提起解决方案,想必大多数人都是 四字方针 “分库,分表”。
这无疑是正确的,这确实是解决方案之一,而且近乎可以解决大部分场景,但是如同 手藓剁手,脚气砍脚 一般还没到那个地步。
并且使用此方案所付出的代价确实我们目前万万承受不起的. 因为当前表牵扯甚广, 使用此方案将直接导致 上线点延期.
所以当前方案排除.
因为数据量庞大,所以 存储过程,存储函数、内存表等方案 因为其 参数长度,内存大小 都是有限的,不能很好的减少jdbc 交互次数并且改动麻烦所以不做考虑.
那么将所有数据生成到临时表,然后使用sql统一更新如何呢?
于是我们采用了当前方案,先将数据分批 插入一个临时表, 在统一使用update 语句处理 ,将一个逻辑拆分成 好几个临时表 ,但是效果并未达到预期目标。
所以我们在此基础上,扩大了原本的线程池,并按照业务逻辑将数据分片,自行编写数据分片算法。并将其交由对应的线程处理,目前为止唯一的好消息就是 当前 不存在竞争资源 多线程的风险倒是并不高。
但是说实话: 理想是 500万条数据 十个线程平均每个线程 50万,可实际上并非如此,总有线程数据多,总有线程数据少,这是分区算法的问题,所以十个线程并没有充分利用。
至此已经执行时间为 三十分钟。
从30 变成 27 分钟:
因为将目标聚焦到 数据库之上,已经优化完 insert与update 那么接下来就是select 了, 减少查询字段,削减不需要的字段,使用索引查询,去掉一些可能导致索引不生效的判断条件
包括但是不限于:
- 去掉where 后的 or 、!=、is null等逻辑
- 减少查询字段
- 建立关键索引
- 去掉in
- 不用like
- 检查表锁
在基于当前细节优化上 将线程与分区数量扩展到 20 但是受限与 电脑性能无法达到预期结果, 可实际上这一步就算可以也不会是最终的解决方案,因为按当前生产资源来算 10个线程 要想上线起码得在减少几个
从30分钟到27分钟是耗时最长,但是收益最小,得不偿失. 但是也不算浪费时间,因为做的都是细节上的优化。
从27 变成 5 分钟:
由于发现数据库并非性能瓶颈, 那么 这么长时间消耗到哪去呢?
那么最后剩下的就是真相, 性能被消耗到业务逻辑代码中了, 于是对代码逻辑进行了反复的核查,与排查. 对代码结构进行优化,包括但不限于:
-
将continue,return 判断语句提前、
-
减少循环(实施上业务逻辑中的所有 for 几乎被优化完了,除了变量数据的总for)
-
将参数提前初始化完毕
-
…
但是以上手段,用处均不大,性能提升微乎其微.
平均每条数据处理的时间上,经过日志打印发现处理速度大概是在 266/秒 , 八万条数据近乎 5分钟, 何况全表将近500W 的存量数据
那就只能向办法,把现在的 266/秒 变成 600/秒、 800/秒 乃至 1600/秒 缩短每条数据的处理时间。
于是我们把目光放到了实体类之中, 准确来说是放到了 BigDecimal 类型的字段上, 整个表将近70个字段, 整个实体类也是将近 70 多个属性
而其中需要保证数据精确性的字段不少, 于是 放眼望去将近十几个 BigDecimal 个属性在做运算,
此时就只能从业务逻辑上出发了, 将需要高精度的数据保持 BigDecimal 类型不变,而有些数据只需要双精度,也就是只保留两位小数的数据. 于是把注意打在了这些双精度的数据上
因为JAVA中基本类型double 默认也是双精度, 于是我们做了个实验 来测试BigDecimal 所耗时间, 那就是 将原本的运算代码全部临时强转成 double类型做运算,然后在new BigDecimal 并赋值
结果是在当前场景下 一次减法运算 = 三次double强转 和一次 new BigDecimal() 对象, 于是就对实体类起了心思,
本来是准备把 一些BigDecimal 改成 double类型,但是在这过程中却发现大部分字段在当前业务代码块其实是用不上的。
于是就将原本的 70个字段,去掉冗余,将类型修改为基本类型 最后只保留了十几个字段,于是每条数据在处理的时候耗时以下就降了下来。
此时才发现原来最耗费性能的不是BigDecimal 的加减乘除, 而是每次调用公共函数初始化当前实体类, 至此由原本的 30 分钟批次变成了5分钟
由 将近 30分钟变成了 5分钟, 优化将近 6倍,说到底不过是削减了每个类创建时所耗资源,优化了原本的类对象, 由原本的 70 多个字段 筛选出必备的几个字段.
减少了每条数据初始化所耗资源,与运算时的资源消耗, 但是如果没有之前的sql 数据库优化 ,使得数据库不在是瓶颈,相信也不会有如此效果。
面对大数据的时候, 确保
- 对象最小
- 通讯次数最少,
- 线程走起,
一般都不会慢,
谨以此文- 纪念那逝去的三天