【业务场景】京东实际场景,频繁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 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

qt5cored.dll报错怎么解决? 电脑qt5cored.dll文件丢失修复技巧

《qt5cored.dll报错怎么解决?电脑qt5cored.dll文件丢失修复技巧》在进行软件安装或运行程序时,有时会遇到由于找不到qt5core.dll,无法继续执行代码,这个问题可能是由于该文... 遇到qt5cored.dll文件错误时,可能会导致基于 Qt 开发的应用程序无法正常运行或启动。这种错

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

Python主动抛出异常的各种用法和场景分析

《Python主动抛出异常的各种用法和场景分析》在Python中,我们不仅可以捕获和处理异常,还可以主动抛出异常,也就是以类的方式自定义错误的类型和提示信息,这在编程中非常有用,下面我将详细解释主动抛... 目录一、为什么要主动抛出异常?二、基本语法:raise关键字基本示例三、raise的多种用法1. 抛

MySQL 设置AUTO_INCREMENT 无效的问题解决

《MySQL设置AUTO_INCREMENT无效的问题解决》本文主要介绍了MySQL设置AUTO_INCREMENT无效的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录快速设置mysql的auto_increment参数一、修改 AUTO_INCREMENT 的值。

关于跨域无效的问题及解决(java后端方案)

《关于跨域无效的问题及解决(java后端方案)》:本文主要介绍关于跨域无效的问题及解决(java后端方案),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录通用后端跨域方法1、@CrossOrigin 注解2、springboot2.0 实现WebMvcConfig

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Java死锁问题解决方案及示例详解

《Java死锁问题解决方案及示例详解》死锁是指两个或多个线程因争夺资源而相互等待,导致所有线程都无法继续执行的一种状态,本文给大家详细介绍了Java死锁问题解决方案详解及实践样例,需要的朋友可以参考下... 目录1、简述死锁的四个必要条件:2、死锁示例代码3、如何检测死锁?3.1 使用 jstack3.2

解决JSONField、JsonProperty不生效的问题

《解决JSONField、JsonProperty不生效的问题》:本文主要介绍解决JSONField、JsonProperty不生效的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录jsONField、JsonProperty不生效javascript问题排查总结JSONField

github打不开的问题分析及解决

《github打不开的问题分析及解决》:本文主要介绍github打不开的问题分析及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、找到github.com域名解析的ip地址二、找到github.global.ssl.fastly.net网址解析的ip地址三