【业务场景】京东实际场景,频繁GC引起的CPU飙高问题的解决

目录

1.业务介绍

2.判断任务类型

3.CPU飙高的原因


1.业务介绍

本文的业务场景是京东零售线公开的一篇文章,文章内容详细介绍了京东零售线如何将广告相关的定时任务从半小时优化到秒级的,原文链接:

半小时到秒级,京东零售定时任务优化怎么做的?_业务 定时任务 100万-CSDN博客

原文内容虽然干货满满,但是表达的太跳跃了,读起来很难读懂。本文将基于京东零售线遇到的这一业务场景来聊一聊,在实际业务场景中性能优化的一整套打法。

业务背景:

京东零售的广告投放是分时段投放的,以半小时为一个时段,由于是分时段投放的,所以允许广告商控制哪些时段投放广告。

实现:

单条广告有以下几个核心字段:

id、startTime、endTime、记录哪些时段投放,哪些时段不投放的一个JSON、是否可被投放的状态字段。

投放的时候只投放状态为可投放的广告。这种设计需要一个定时任务来每半个小时根据JSON的内容来刷一下状态,将各广告商允许在当前时段投放的广告的状态刷为可被投放。

京东就是在这里遇见了问题,表里面有8400万条数据,一次定时任务要半小时才能跑完,刚刚跑完上一次就该跑下一次了,肯定是不行的。接下来我们就按一套思路来解决问题

2.判断任务类型

遇见一个性能问题,首先是要判断它是IO密集型任务还是CPU密集型任务。如果是IO密集型任务就要从IO优化上来思考解决方法,如果是CPU密集型任务就要从计算层面来进行优化。所谓的IO优化其实就是减少IO次数,优化IO时的数据大小等。所谓的计算层面优化就是优化算法等。

这种对数据库进行读写的定时任务很明显是IO密集型任务,所以要往IO优化的方向考虑。首先考虑是不是能减少IO次数,8400万条数据是不是都是要用到的?京东的开发人员在这一步发现其实不是所有数据都要用到,广告是分渠道的,有一个type字段用来区分这条广告投放到哪些渠道,是APP,微信小程序,PC端等等。需要进行分时段投放的其实只有其中一个渠道。加上type条件后,数据量瞬间下去四分之三。r然后给所有条件字段建一个联合索引,就搞定了。从减少IO次数,以及利用索引这种空间换时间的方法双管齐下。据京东的开发人员的说法,这一步之后这个定时任务的耗时已经从半小时降到了几分钟,已经可以用了。

3.CPU飙高的原因

本来以为上面的IO层面的优化后已经万事大吉了,但是用起来后发现定时任务执行期间,CPU的占用率会飙高,定时任务把CPU资源吃了,对请求的处理任务会受到影响,吞吐量会下跌。这本来是个IO密集型任务,不可能引起CPU飙高的,所以一定是哪里还有问题没被抓出来。这时候就需要监控一下程序了。用JDK自带的JDK监控工具jvisualVM来看看程序内部发生了些什么。JDK调试工具,作者有一篇专门的文章介绍过,可移步:

详解JAVA程序调优-CSDN博客

通过监控发现JVM在频繁GC。GC时确实会引起CPU飙高,原因如下:

1. 标记和清除过程:垃圾回收需要进行对象的标记和清除工作。标记阶段需要遍历所有对象,确定哪些对象是不可达的(即不再被程序引用),这个过程需要占用一定的CPU时间。清除阶段则需要释放这些不可达对象占用的内存空间,这同样需要CPU的参与。
2. 暂停其他线程:在进行垃圾回收时,为了保证内存安全,需要“Stop The World”,暂停其他线程的执行。所有的CPU资源都会被用来进行垃圾回收工作,从而导致CPU使用率升高。
3. 内存碎片整理:在垃圾回收过程中,可能需要对内存碎片进行整理,以提高内存的使用效率。这个过程同样需要CPU的参与。

GC本来就会引起CPU飙高,持续GC,当然就会让CPU持续飙高。持续GC很可能是new了大量对象,导致堆空间频繁到阈值从而引起GC。果然,京东的开发人员通过观察发现定时任务中有大量hu调试日志,logger在打印日志的时候是会new一个日志对象的,随便点开一个logger的info之类的方法进入底层可以看到:

private void buildLoggingEventAndAppend(String localFQCN, Marker marker, Level level, String msg, Object[] params, Throwable t) {
    LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
    le.setMarker(marker);
    this.callAppenders(le);
}

大量打印日志当然就会new出大量对象,触发GC。下面我们模拟一个过程,new大量日志对象,并结合JvisualVM来定位问题。

测试代码:

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

public class TestClass {
        // 这个静态列表将导致内存持续增长,可能引起频繁的GC
        private static List<LoggingEvent> eventList = new ArrayList<>();

        public static void main(String[] args) {
            while (true) {
                buildLoggingEventAndAppend("com.example.Logger", null, Level.INFO, "A log message", null, null);
            }
        }

        private static void buildLoggingEventAndAppend(String localFQCN, Marker marker, Level level, String msg, Object[] params, Throwable t) {
            LoggingEvent le = new LoggingEvent(localFQCN, "LoggerName", level, msg, t, params);
            le.setMarker(marker);

            // 将LoggingEvent对象添加到静态列表中,造成内存泄漏
            eventList.add(le);

            // 假设的调用方法,实际应用中应替换为正确的日志处理逻辑
            callAppenders(le);
        }

        private static void callAppenders(LoggingEvent le) {
            // 这里简化处理,实际应包含向各个Appender发送事件的逻辑
            System.out.println("Logging: " + le.getMessage());
        }

        // 为了示例简单,我们模拟一个LoggingEvent类
        static class LoggingEvent {
            private String localFQCN;
            private String loggerName;
            private Level level;
            private String message;
            private Throwable throwable;
            private Object[] parameters;
            private Marker marker;

            public LoggingEvent(String localFQCN, String loggerName, Level level, String message, Throwable throwable, Object[] parameters) {
                this.localFQCN = localFQCN;
                this.loggerName = loggerName;
                this.level = level;
                this.message = message;
                this.throwable = throwable;
                this.parameters = parameters;
            }

            public void setMarker(Marker marker) {
                this.marker = marker;
            }

            public String getMessage() {
                return message;
            }
        }

        // 简化的Level和Marker类,仅用于示例
        enum Level {DEBUG, INFO, WARN, ERROR}
        static class Marker {}
}

JvusialVM监控:

监控中已经显示频繁GC了,堆内存的占用也是持续飙高,这时候抓一个堆的快照观察一下:

堆快照显示堆内有大量日志对象:

既然找到原因了,把多余的日志打印去掉就行了,自然就解决了问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/592192.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

BUUCTF:Web 解析(一)

前言 Buuctf Web 是一个在线安全挑战平台&#xff0c;旨在提高参与者对网络安全的理解和实践能力。本文将详细介绍 Buuctf Web 的特点、挑战和机遇&#xff0c;帮助读者更好地了解这一领域。 一、Buuctf Web 的特点 多样化的挑战场景&#xff1a;Buuctf Web 提供了多种挑战场…

Redis事务,管道,发布订阅

Redis事务 redis事务本质上是一组命令的集合,按照顺序串行化执行命令而不被其他命令打断 redis事务开启后将要执行的命令放到事务队列中,提交事务后一次性顺序排他地执行所有命令 关键词:单线程,无隔离级别,不保证原子性,排他性,顺序性 要注意和mysql的acid进行区分 怎么用…

【JavaEE】多线程安全问题

文章目录 1、什么是多线程安全问题2、出现线程不安全的原因2.1 线程在系统中是随机调度&#xff0c;抢占式执行的2.2 多个线程同时修改同一个变量2.3 线程针对变量的修改操作&#xff0c;不是“原子”的2.4 内存可见性问题2.5 指令重排序 3 、如何解决线程安全问题3.1 锁操作3.…

2024年3月Scratch图形化编程等级考试(三级)真题试卷

2024年3月Scratch图形化编程等级考试&#xff08;三级&#xff09;真题试卷 选择题 第 1 题 Scratch运行程序后&#xff0c;角色一定不会说出的数字是&#xff1f;&#xff08; &#xff09; A.2 B.4 C.6 D.8 第 2 题 Scratch角色初始位置如下图所示&#xff0c;右图…

14_Scala面向对象编程_属性

属性 1.类中属性声明 // 1.给Scala声明属性&#xff1b;var name :String "zhangsan"val age :Int 302.系统默认赋值 scala由于初始化变量必须赋值&#xff0c;为了解决此问题可以采用下划线赋值&#xff0c;表示系统默认赋值 , –但是此方法局限于变量&…

ArkTS开发原生鸿蒙HarmonyOS短视频应用

HarmonyOS实战课程“2024鸿蒙零基础快速实战-仿抖音App开发&#xff08;ArkTS版&#xff09;”已经于今日上线至慕课网&#xff08;https://coding.imooc.com/class/843.html&#xff09;&#xff0c;有致力于鸿蒙生态开发的同学们可以关注一下。 课程简介 本课程以原生鸿蒙Ha…

Hibernate执行流程分析及配置文详解

目录 1、Hibernate执行流程分析及配置文件详解 1&#xff09;Configuration对象 2&#xff09;ServiceRegistry对象&#xff08;hibernate4的新特性&#xff09; 3&#xff09;SessionFactory对象 4&#xff09;Session对象 5&#xff09;Transaction对象 6&#xff09;…

缓冲流,BufferReader,BufferWriter,案例

IO流的体系 字节缓冲流的作用 提高字节流读取数据的性能 *原理&#xff1a;字节缓冲输入流自带了8Kb的缓冲池&#xff0c;字节缓冲输出流也自带了8kb的缓冲池 构造器说明public BufferedInputStream(InputStream is)把低级的字节输入流包装成一个高级的缓冲字节输入流&#…

对链表进行插入排序(详细解析)

对链表进行插入排序&#xff08;详解&#xff09; 题目&#xff1a; 对链表进行插入排序 给定单个链表的头 head &#xff0c;使用 插入排序 对链表进行排序&#xff0c;并返回 排序后链表的头 。 插入排序 算法的步骤: 插入排序是迭代的&#xff0c;每次只移动一个元素&a…

特斯拉FSD落地分析

再续前缘 媒体的神经从马斯克的湾流私人飞机起飞那一刻开始,就开始被牵动着。28/4 号的突然访华,在大多数人看来其实已经早已是计划之中,从摆在台面上的消息来看,主要目的是为了在大陆推广FSD的落地,也为8月份FSD 的正式版本做预热,和中国上海的第一次联姻造就了特斯拉m…

17 内核开发-内核内部内联汇编学习

​ 17 内核开发-内核内部内联汇编学习 课程简介&#xff1a; Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础&#xff0c;让他们能够理解和参与到Linux内核的开发过程中…

【Linux】进程exec函数族以及守护进程

一.exec函数族 1.exec函数族的应用 在shell下敲shell的命令都是在创建shell的子进程。而我们之前学的创建父进程和子进程代码内容以及通过pid与0的关系来让父子进程执行不同的代码内容都是在一个代码文件里面&#xff0c;而shell是如何做到不在一个文件里面写代码使之成为子进…

06|LangChain | 从入门到实战 -六大组件之Agent

点点赞~ 注意&#xff1a;langchain的版本迭代比较快&#xff0c;社区维护&#xff0c;代码当中或许部分方法在某个版本不再支持 01&#xff5c;LangChain | 从入门到实战-介绍 02&#xff5c;LangChain | 从入门到实战 -六大组件之Models IO 03&#xff5c;LangChain | 从入…

asp.net结课作业中遇到的问题解决2

目录 1、如何实现评论交流的界面 2、如果想要将文字添加到数据库中&#xff0c;而不是乱码&#xff0c;该怎么修改 3、如果想要添加的数据已经存在于数据库&#xff0c;就不允许添加了&#xff0c;该如何实现 4、想要实现某个模块下有好几个小的功能该如何实现 5、想要实现…

代码随想录算法训练营第25天 | 216.组合总和III、17.电话号码的字母组合

代码随想录算法训练营第25天 | 216.组合总和III、17.电话号码的字母组合 自己看到题目的第一想法看完代码随想录之后的想法 链接: 216.组合总和III 链接: 17.电话号码的字母组合 自己看到题目的第一想法 216.组合总和III&#xff1a;递归函数终止条件为搜索得到的数相加为n&…

【架构系列】RabbitMQ应用场景及在实际项目中如何搭建可靠的RabbitMQ架构体系

作者:后端小肥肠 创作不易&#xff0c;未经允许禁止转载。 1. 前言 RabbitMQ&#xff0c;作为一款高性能、可靠的消息队列软件&#xff0c;已经成为许多企业和开发团队的首选之一。它的灵活性和可扩展性使得它适用于各种应用场景&#xff0c;从简单的任务队列到复杂的分布式系统…

阿里低代码引擎学习记录

官网 一、关于设计器 1、从设计器入手进行低代码开发 设计器就是我们用拖拉拽的方法&#xff0c;配合少量代码进行页面或者应用开发的在线工具。 阿里官方提供了以下八个不同类型的设计器Demo&#xff1a; 综合场景Demo&#xff08;各项能力相对完整&#xff0c;使用Fusion…

【前端项目——分页器】手写分页器实现(JS / React)

组件介绍 用了两种方式实现&#xff0c;注释详细~ 可能代码写的不够简洁&#xff0c;见谅&#x1f641; 1. 包含内容显示的分页器 网上看了很多实现&#xff0c;很多只有分页器部分&#xff0c;没和内容显示联动。 因此我增加了模拟content的显示&#xff0c;这里模拟了32条数…

Python数据分析案例44——基于模态分解和深度学习的电负荷量预测(VMD+BiGRU+注意力)

案例背景 承接之前的案例&#xff0c;说要做模态分解加神经网络的模型的&#xff0c;前面纯神经网络的缝合模型参考数据分析案例41和数据分析案例42。 虽然我自己基于各种循环神经网络做时间序列的预测已经做烂了.....但是还是会有很多刚读研究生或者是别的领域过来的小白来问…

Monorepo(单体仓库)与MultiRepo(多仓库): Monorepo 单体仓库开发策略与实践指南

&#x1f31f; 引言 在软件开发的浩瀚宇宙里&#xff0c;选择合适的代码管理方式是构建高效开发环境的关键一步。今天&#xff0c;我们将深入探讨两大策略——Monorepo&#xff08;单体仓库&#xff09;与MultiRepo&#xff08;多仓库&#xff09;&#xff0c;并通过使用现代化…