[JAVA][新项目建设性能问题]大业务数据导入性能优化

71 阅读3分钟

背景:

别问为什么要导入,能不能线上对接,能不能走中台

x企y企的腐朽远超你想象, 各级不决策, 不推进, 京官也管不动省分

就等着厂商推动揽功劳, 外行还喜欢指导内行建设信息化系统

  • 文件大小会超过sheet上限, 大的文件小半个GB, 几百万数据
  • 复杂电磁环境: 指甲方内部使用的VPN还会经常坏导致断传
  • 建设是仓促的, 人是到处借的, 需求是一直变的, 排期是倒排的, 加上厂商b公司性价比招人完全没有建设能力了, 老员工一点点跑, 外包一点点招, 项目经理也是飘在天上只会要时间的

问题:

最初是10万数据就要导入库25分钟, 堪忧

解决:

考虑极限场景, 拟定目标为10分钟100万条, 此性能下至少可以满足业务流程无阻塞, 可以要求客户妥协为后续再排期优化文件上传异步入库逻辑

检查代码:

  • 至少这个开发还知道分批次入库.....
  • 哦不对, 是被测试测卡死后他优化过了.......
  • 没了

优化执行顺序如下:

  1. 同步读取Excel是不能动的, 底层io只能是同步的. 故满足一个设定的批次读取量就start一个线程去批次入库, CompletableFuture.runAsync, 最后CompletableFuture.allOf等待全部完成

    • 此时性能优化为10万数据18分钟, 前期日志观察效率较高, 中后期开始变慢, 线程变为就那么几个线程了, 未能充分利用多核心
  2. 检查JVM参数,为-Xmx8g , 改用G1, 设置大MaxNewSize, 观察gc耗时

    • 这里事实上思路是有问题的, 没有先去处理线程未充分调动的问题, gc相对只是小问题,不优化都行
  3. 对CompletableFuture的方法传入线程池, 时间提升至5分钟50万, 达到预期目标

    • 为什么要加入线程池参数?
      • 答:默认是forkJoin的, 那么并发度和核心数一样, 是面向处理多线程非资源竞争关系的阻塞任务的.
      • 而实际场景是不一致的, 是大量时间在数据库io的, 且实践中forkjoin的完成是不均匀的任务最后会堆在几个线程上.
    • 线程池参数为core: (完成io读excel+业务处理+入库耗时)/(完成io读excel耗时)*生产环境核心数量(4C8G) ≈ 80
    • 线程池使用无界队列, 不拒绝
    • 线程池一般不需要minIdle太多
    • 不同业务间应拆分微服务, 不然各业务对于线程池的需求都不一样, 性能会波动. 前期系统架构钟拆分了微服务专门处理, 但是问题是......见背景
  4. 微调每次批次量,线程池大小

    • 微调依据: 观察入库日志的打印的活跃程度, 如果发现了几百毫秒的假死, 或者观察GC发现了入库少且期间频繁回收, 则说明入库速度跟不上读取线程, 堵车在排队等入库了, 空吃JVM,添乱gc

    • 这个其实就你懂的感觉, 文字不好描述, 你懂

    • 最终效果为3分半50万

加深规范的理解:

  1. 就研发能力上, 应早期识别此项目对于性能的需求, 针对性培训研发, 并对敏感需求进行标记
  2. 对内而言, 再穷不能穷评审.jpg
  3. 需求阶段应就数据量和客户立字据, 出现性能问题都是可以扯皮要延期的, 至少吵不输
  4. 同第2点, 如果你的排期压着我要产出, 那就要立字句, 因什么原因, 暂时舍弃xxx设计及评审, 可能存在性能问题, 请客户决断是否给足时间