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

2024-05-05 02:04

本文主要是介绍【业务场景】京东实际场景,频繁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 {// 这个静态列表将导致内存持续增长,可能引起频繁的GCprivate 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了,堆内存的占用也是持续飙高,这时候抓一个堆的快照观察一下:

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

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

这篇关于【业务场景】京东实际场景,频繁GC引起的CPU飙高问题的解决的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/960593

相关文章

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

Java中读取YAML文件配置信息常见问题及解决方法

《Java中读取YAML文件配置信息常见问题及解决方法》:本文主要介绍Java中读取YAML文件配置信息常见问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录1 使用Spring Boot的@ConfigurationProperties2. 使用@Valu

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

SQL Server配置管理器无法打开的四种解决方法

《SQLServer配置管理器无法打开的四种解决方法》本文总结了SQLServer配置管理器无法打开的四种解决方法,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录方法一:桌面图标进入方法二:运行窗口进入检查版本号对照表php方法三:查找文件路径方法四:检查 S

MyBatis-Plus 中 nested() 与 and() 方法详解(最佳实践场景)

《MyBatis-Plus中nested()与and()方法详解(最佳实践场景)》在MyBatis-Plus的条件构造器中,nested()和and()都是用于构建复杂查询条件的关键方法,但... 目录MyBATis-Plus 中nested()与and()方法详解一、核心区别对比二、方法详解1.and()

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

解读GC日志中的各项指标用法

《解读GC日志中的各项指标用法》:本文主要介绍GC日志中的各项指标用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、基础 GC 日志格式(以 G1 为例)1. Minor GC 日志2. Full GC 日志二、关键指标解析1. GC 类型与触发原因2. 堆

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Python中re模块结合正则表达式的实际应用案例

《Python中re模块结合正则表达式的实际应用案例》Python中的re模块是用于处理正则表达式的强大工具,正则表达式是一种用来匹配字符串的模式,它可以在文本中搜索和匹配特定的字符串模式,这篇文章主... 目录前言re模块常用函数一、查看文本中是否包含 A 或 B 字符串二、替换多个关键词为统一格式三、提