数据批处理加速(GC收集器->安全点->线程池)

这篇具有很好参考价值的文章主要介绍了数据批处理加速(GC收集器->安全点->线程池)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、引言

       有个数据镜像系统每天0点会进行批量处理生成日表,随着数据量不断增大,处理时间从之前的一个小时,目前每天需要接近两小时,一旦继续延迟会影响BI、库存等很多任务。领导让博主进行优化提速。

        博主从GC收集器、可数循环的安全点放置、CPU与线程搭配等多个方向进行分析实践。

二、提速方向

首先看一下代码

List<A> as = mapper.get(queryDTO, i);
                if (CollectionUtils.isEmpty(as )) {
                    break;
                }
                /**------------**/
                CountDownLatch latch;
                if (as .size() < paramConfig.getInsertBatchQuantity()) {
                    latch = new CountDownLatch(1);
                } else if (as .size() % paramConfig.getInsertBatchQuantity() == 0) {
                    latch = new CountDownLatch(assetCollects.size() / paramConfig.getInsertBatchQuantity());
                } else {
                    latch = new CountDownLatch(as .size() / paramConfig.getInsertBatchQuantity() + 1);
                }
                Lists.partition(as , paramConfig.getInsertBatchQuantity()).forEach(a -> {
                    Runnable runnable = () -> {
                        try {
                            log.info("线程" + Thread.currentThread().getName() + "开始执行");
                            mapper.batchSave(a, tableName);
                            log.info("线程" + Thread.currentThread().getName() + "执行完成");
                        } catch (Exception e) {
                            log.error("数据镜像执行异常", e);
                        }
                        finally {
                            latch.countDown();
                        }
                    };
                    dbInsertExecutorService.execute(runnable);
                });

                latch.await();

        就是查数据再并发插入日表,原来的并发是根据查询数量/插入数量得出的,插入时需要使用同步器CountDownLatch等待插入都完成。

1、GC

        一开始不想动代码,试着从数据查询语句和gc看看能不能加速,查询的sql也不复杂没什么好说的。

        收集频率在60左右,minor gc在30ms左右,major gc在200ms左右,其实也能理解,毕竟数据在不断存取,很快就会失去引用链,所以回收密集也很正常。

        数据批处理加速(GC收集器->安全点->线程池)

         那么有没有可能减少gc stop the world 的时间呢,看一下jvm的配置

javaagent:/app/skywalking/agent/skywalking-agent.jar -jar -Djava.security.egd=file:/dev/./urandom -server -Xms3998m -Xmx3998m -XX:MetaspaceSize=200m -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=3 -XX:GCLogFileS
ize=10m -Xmn1999m -Xloggc:/dev/shm/gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:SurvivorRatio=8 -XX:-UseAdaptiveSizePolicy -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs/ /app/code/app.jar

        垃圾收集器没有特殊设置,那就是jdk8默认的UseParallelGC,这对收集器在收集过程中是需要stop the world的,目前能进行并发收集的用的比较多的是CMS、G1,分别是通过增量更新和原始快照的方式解决三色图标记问题,从而实现并发收集。

        和运维部门协调修改jvm配置,使用G1看看处理提速情况,结果卡在了第一步,运维说所有系统jvm配置在生成发布镜像的时候都是拉取同一个Linux挂载路径下的文件,改动会影响其他系统,博主对于K8S的配置文件和发布镜像机制不是很了解,没法反驳,之前也有过类似的时候,这就是吃了没文化的亏。之前还有很多其他东西研究,现在有精力研究容器了,博主暗戳戳的买了《深入理解K8S》,准备后面找回所有的场子。

2、安全点

         收集器走不通,看看其他方案,在《深入理解java虚拟机》中,作者提到过在可数循环当中jvm不会插入安全点,如果循环体执行的又是比较耗时的操作,就会导致gc收集的过程中需要等待线程进入安全点过久

       数据批处理加速(GC收集器->安全点->线程池)

        那么功能符不符合这种情况呢 ,可以看出循环切分是可数循环,而循环体是开启一个线程插入数据库,那么网络io+数据库io也算是比普通的代码计算久的,那就改写一下试试看,但是最终结果提速只有几十秒,这个也被pass。

3、CPU&线程

        以前的开发人员是使用线程池开启多线程,然后又使用了同步器阻塞这个过程,为什么不去掉同步器呢,如果是害怕队列中的数据可能因为宕机之类的原因丢掉,那么宕机本来就需要清空表之后重新插入的设计又是为什么呢

        这里看一下线程池的配置,用ThreadPoolExecutor生成自定义线程池,按照使用规范设置有界队列,队列数量10个,问题出在拒绝策略,默认的拒绝策略是丢弃并且抛出异常,猜想以前的开发人员对于线程池的拒绝策略不是太了解,没有想过配置其他策略。

new ThreadPoolExecutor(poolProperties.getCorePoolSize(), poolProperties.getMaxPoolSize(),
                poolProperties.getKeepAliveSeconds(), TimeUnit.SECONDS, new ArrayBlockingQueue<>(poolProperties.getMaxQueueSize()), factory);
    

         博主把线程池的拒绝策略设置成了主线程执行,这也是博主在开发时常用的,感觉没有哪个业务是能把消息数据丢掉的。

new ThreadPoolExecutor(poolProperties.getCorePoolSize(), poolProperties.getMaxPoolSize(),
                poolProperties.getKeepAliveSeconds(), TimeUnit.SECONDS, new ArrayBlockingQueue<>(poolProperties.getMaxQueueSize()), factory, new ThreadPoolExecutor.CallerRunsPolicy());
    

        然后就是把同步器给去掉

//                CountDownLatch latch;
//                if (as.size() < paramConfig.getInsertBatchQuantity()) {
//                    latch = new CountDownLatch(1);
//                } else if (as.size() % paramConfig.getInsertBatchQuantity() == 0) {
//                    latch = new CountDownLatch(as.size() / paramConfig.getInsertBatchQuantity());
//                } else {
//                    latch = new CountDownLatch(as.size() / paramConfig.getInsertBatchQuantity() + 1);
//                }
                Lists.partition(as, paramConfig.getInsertBatchQuantity()).forEach(a-> {
                    Runnable runnable = () -> {
                        try {
                            log.info("线程" + Thread.currentThread().getName() + "开始执行");
                            assetService.batchSave(a, tableName);
                            log.info("线程" + Thread.currentThread().getName() + "执行完成");
                        } catch (Exception e) {
                            log.error("数据镜像执行异常", e);
                        }
//                        finally {
//                            latch.countDown();
//                        }
                    };
                    dbInsertExecutorService.execute(runnable);
                });

//                latch.await();

        线程池调整到了15个,结果速度反而还降了一些,思考了一下,这种属于io密集型(网络io+数据库io),线程没有什么计算,所以cpu只是进行了一个切分提交,然后就是等待io了,上下文切换再加上数据库主键自增锁可能反而是耗时的。

        把线程和队列都调到了6,速度上来了,测试环境提升百分之十几

测试环境优化之前耗时

数据批处理加速(GC收集器->安全点->线程池)

优化之后

数据批处理加速(GC收集器->安全点->线程池)

        再看一下线上

       线上优化之前

数据批处理加速(GC收集器->安全点->线程池)

 线上优化之后

数据批处理加速(GC收集器->安全点->线程池)

         可以看出线上提速在百分之五十几

 三、总结

         为了进行优化提速,博主从gc收集器->可数循环的安全点问题->线程池调整,多维度分析,希望同学们更多的看到其中的原理,比如安全点问题在一些高精度ms级别提速优化的可能性,收集器涉及的jvm知识可以用于许多优化和排查问题,建议大家看看《深入理解java虚拟机》,会有很多收获。文章来源地址https://www.toymoban.com/news/detail-489232.html

到了这里,关于数据批处理加速(GC收集器->安全点->线程池)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 垃圾收集器

    目录 1.垃圾判断 1.1 引用计数法: 1.2 可达性分析: 1.3 常见的GC Roots有: 2.垃圾回收方式:垃圾收集器 2.1 CMS收集器: 2.2 G1收集器: 1.1 引用计数法:         对象有一个引用计数器,引用一次计数器就加1,计数器为0就是没有引用,但存在循环引用问题 1.2 可达性分析:

    2024年02月11日
    浏览(38)
  • JVM 垃圾收集器

    重点:CMS,G1,ZGC 主要垃圾收集器如下,图中标出了它们的工作区域、垃圾收集算法,以及配合关系。 Serial 收集器 Serial 收集器是最基础、历史最悠久的收集器。 如同它的名字(串行),它是一个单线程工作的收集器,使用一个处理器或一条收集线程去完成垃圾收集工作。

    2024年02月10日
    浏览(46)
  • JVM:垃圾收集器(7种)

    如果两个收集器之间存在连线,就说明它们可以搭配使用。它们说在的区域则表示这个收集器属于新生代收集器还是老年代收集器。其中Serial(串行)、Parallel(并行)         Serial收集器是最基础、历史最悠久的收集器,是HotSpot虚拟机新生代收集器的唯一选择。这个收集器

    2024年01月20日
    浏览(48)
  • 【JVM】JVM垃圾收集器

    垃圾收集器是负责 执行垃圾回收的组件 ,它们用于 管理Java程序运行时的内存分配和释放 。垃圾收集器的主要任务是 自动回收不再使用的内存对象 ,并将 内存空间重新回收 以供程序继续使用。 Serial和Serial Old串行垃圾收集器,是指 使用单线程进行垃圾回收 ,堆内存较小,

    2024年02月13日
    浏览(41)
  • SAP 详细解析成本收集器

    成本收集器作为成本对象,主要应用于按期间进行成本核算的情况,在这种情况下会把产品创建为成本收集器,实际成本的收集和差异的结算全部按照成本收集器进行处理,财务的成本分析也针对成本收集器进行。   成本收集器是按期间核算,生产订单可以按期间也可以完全

    2024年02月16日
    浏览(53)
  • 深入理解JVM垃圾收集器

    相关系列 深入理解JVM垃圾收集算法-CSDN博客 目前市面常见的垃圾收集器有Serial、ParNew、Parallel、CMS、Serial Old、Parallel Old、G1、ZGC以及有二种不常见的Epsilon、Shenandoah的,从上图可以看到有连线的的垃圾收集器是可以组合使用,是年轻代+老年代。为什么会出现这么多的垃圾收集

    2024年04月09日
    浏览(49)
  • JVM ZGC垃圾收集器

    ZGC(“Z”并非什么专业名词的缩写,这款收集器的名字就叫作Z Garbage Collector)是一款在JDK 11中新加入的具有实验性质[1]的低延迟垃圾收集器,是由Oracle公司研发的。 ZGC收集器 是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术

    2024年02月10日
    浏览(47)
  • JVM的故事——垃圾收集器

    新生代收集器,最基础的收集器,单线程。进行垃圾收集时必须暂停其他所有工作线程,stop the world 新生代收集器,实质上是serial收集器的多线程版本。除了serial,只有它能和CMS收集器(老年代收集器)配合工作。随着处理器核心数越来越多,parnew比serial有着更好的性能。(但如

    2024年02月10日
    浏览(49)
  • 08-JVM垃圾收集器详解

    上一篇:07-垃圾收集算法详解 如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。 虽然我们对各个收集器进行比较,但并非为了挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做

    2024年02月09日
    浏览(47)
  • JVM--- 垃圾收集器详细整理

    目录 一、垃圾收集需要考虑的三个事情: 二、垃圾回收针对的区域 三、如何判断对象已死         1.引用计数算法:         2.可达性分析算法 四、引用 五、生存还是死亡? 六、回收方法区 七、垃圾收集算法         1.分代收集理论 2.标记-清除算法         3.标记

    2024年02月19日
    浏览(71)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包