记录一次OOM排查经历(一)
一、经历概要
程序里有个跑数据的job,这个job的主要功能是往数据库写假数据。
既需要跑历史数据(传给job的日期是过去的时间),也需要能够上线后,实时跑(十秒钟触发一次,传入触发时的当前时间)。
其中一个job比较奇葩点,要写入的数据比较难以随机生成,是产品的同事从互联网上找的数据,比如当前网络上的热门话题,然后导入到数据库中。所以,
我这边随机的时候,不能乱造。因此我的策略是,从数据库将已经存在的那几条真实数据查询出来,然后job中根据随机数,选择其中一条来仿造一条新的,
随机生成新记录的其他字段,再写入数据库中。
我单元测试一直这么跑的,没有任何问题,直到,将定时触发器打开,然后上线运行。。。悲剧来了。
二、程序大体逻辑
1、job接口定义:
/**
* desc:
* 造数据的job,可按表来划分。一个表一个job
* @author :
* creat_date: 2018/6/11 0011
* creat_time: 14:46
**/
public interface DataProduceJob {
/**
* job的初始化
* @param date
*/
void jobInit(Date date); /**
* 具体的job运行细节
*/
void jobDetail(Integer recordNum);
}
job之所以分了上面两个接口,只是因为设计失误,完全可以融合为一个方法。jobInit的内容,后来我改写到job的afterPropertiesSet中了。
(job实现了org.springframework.beans.factory.InitializingBean接口,保证初始化数据只被调用一次,所谓的初始化数据是指:
读文件,读数据库之类的准备工作,后续的假数据都从这里面取)
这边是出问题的job的源码:
package com.ceiec.datavisual.quartz.job; import com.ceiec.common.utils.FileUtils;
import com.ceiec.common.utils.MathUtils;
import com.ceiec.datavisual.dao.GpsLocationSampleMapper;
import com.ceiec.datavisual.dao.TopicAccountMapper;
import com.ceiec.datavisual.dao.TopicMapper;
import com.ceiec.datavisual.dao.TopicWebsiteMapper;
import com.ceiec.datavisual.model.GpsLocationSample;
import com.ceiec.datavisual.model.Topic;
import com.ceiec.datavisual.model.TopicAccount;
import com.ceiec.datavisual.model.TopicWebsite;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component; import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Random; @Component
public class TopicWebsiteJob extends BaseJob implements DataProduceJob {
@Autowired
private TopicWebsiteMapper topicWebsiteMapper; private Date date; Random random = new Random(); private List<TopicWebsite> topicWebsites; /**
* 当前job执行时的时间,会作为创建时间写入数据库表
*
* @param date
*/
@Override
public void jobInit(Date date) {
this.date = date;
topicWebsites = topicWebsiteMapper.selectAll();
} @Override
public void jobDetail() {
for (TopicWebsite website : topicWebsites) {
for (int i = 0; i < 5; i++) {
TopicWebsite topicWebsite = new TopicWebsite(); topicWebsite.setWebsiteName(website.getWebsiteName());
topicWebsite.setIconUrl(website.getIconUrl());
topicWebsite.setHotValue((long) random.nextInt(6354147));
//设置时间
topicWebsite.setCreateTime(date); topicWebsiteMapper.insert(topicWebsite);
}
}
} }
2、job的历史数据初始化器
初始化器,主要是用于生成历史数据,用的是随机生成的过去30天内的时间,去new一个job。
然后调用job的init,设置date;然后调用job的细节。
上面我也说了,没必要搞两个,只是最初设计失误了。
总体逻辑,就是传入日期,然后根据那个日期,去造假数据。
package com..datavisual.quartz.init; /**
* desc:
* 用于造初始化数据
* @author :
* creat_date: 2018/6/11 0011
* creat_time: 14:29
**/
public interface Initer {
/**
* 具体的初始化逻辑,可参考
* @return 成功或失败
*/
Boolean init();
}
出问题的初始化器的源码:
package com.ceiec.datavisual.quartz.init; import com.ceiec.datavisual.quartz.job.TopicWebsiteJob;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import java.util.Date; /**
* desc:
*
* @author:
* creat_date: 2018/6/11 0011
* creat_time: 14:28
**/
@Component
public class TopicWebsiteIniter implements Initer {
@Autowired
private TopicWebsiteJob job; @Override
public Boolean init() {
DateTime now = DateTime.now();
//日期循环,30天
for (int a = -29; a < 1; a++) {
for (int b = 0; b < 24; b++) {
int minutes = (int) (Math.random() * 60);
Date date = com.ceiec.datavisual.quartz.DateUtils.getNeedTime(b, minutes, 0, a);
if (a == 0 && date.after(now.toDate())) { } else {
job.jobInit(date);
job.jobDetail(360);
}
}
} return true;
} }
3、目前为止,运行正常?
到目前为止,运行没什么问题,因为我都是用单元测试的方式去调用上面的initer.init方法。
真的吗?
4、加上定时触发机制
这些job,在上线后,还是需要继续运行。具体的间隔,是每十秒触发一次。
code如下:
package com..datavisual.quartz.schedule; import com..datavisual.quartz.job.TopicWebsiteJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import java.util.Date; @Component
public class TopicWebsiteScheduler implements DataProduceScheduler {
private static final Logger logger = LoggerFactory.getLogger(TopicWebsiteScheduler.class); @Autowired
private TopicWebsiteJob job;
@Override
@Scheduled(cron = "0/10 * * * * ?}")
public Boolean schedule() {
logger.info("start...");
job.jobInit(new Date());
job.jobDetail(1); return true;
} }
5、问题出来了
就上面的代码,上线一运行,因为job比较多,说实话,也没注意一些细节,没去查看数据库的数据条数。
我一直以为没啥问题,直到运行了没一会,程序假死了,卡着不动了。
后来将堆转储拿出来分析,才发现,是因为每次init被多次调用了,每次调用都会从表里面查所有数据(一直以为只有10条真实数据)。
然后根据这些数据,去生成新的假数据。再插回表里。这时候表里的数据,差不多翻倍了。
再过10s后,再次查询,这次查到20条,然后,又造了20条假数据,写到表里,变成了40条。
再过10s后,再次查询,这次查到40条,然后,又造了40条假数据,写到表里,变成了80条。
。。。
然后就越来越慢,越来越卡。。。直到发现表里竟然变成了千万条数据,然后将java程序的内存撑爆了。
三、总结
其实这次主要的坑,在于自己设计功力不够,没有考虑清楚。数据库的数据是变化的,而我拿变化的东西作为基准,来生成假数据,再将假数据写入到原表,造成了
表里数据的指数级增长,然后撑爆了内存。
抛开这块不说,比较有意思的是,查找这个bug背后原因的过程,后边单独写。
记录一次OOM排查经历(一)的更多相关文章
- 记录一次OOM排查经历
我是用了netty搭建了一个UDP接收日志,堆启动配置 Xmx256 Xms256 ,项目刚启动的时候,系统进程占用内存很正常,在250M左右. 长时间运行之后发现,进程占用内存不断增长,远远超过了 ...
- 【转】又一次线上 OOM 排查经过
又一次线上OOM排查经过 最近线上一个服务又出现了频繁Full GC的情况,导致提供的业务经常超时.问题出现非常不稳定,经过两周的时候,终于又捕捉到了一次Full GC,于是联系运维做Heap Dum ...
- 华为云数据库GaussDB(for Cassandra)揭秘第二期:内存异常增长的排查经历
摘要:华为云数据库GaussDB(for Cassandra) 是一款基于计算存储分离架构,兼容Cassandra生态的云原生NoSQL数据库:它依靠共享存储池实现了强一致,保证数据的安全可靠. 本文 ...
- 实战经验 | Cassandra Java堆外内存排查经历全记录
背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测.压测时候比较容易触发OOM Killer,把cassandra进程干掉.问题是8G这个规格我配置的heap(Xmx ...
- 超干货!Cassandra Java堆外内存排查经历全记录
背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测.压测时候比较容易触发OOM Killer,把cassandra进程干掉.问题是8G这个规格我配置的heap(Xmx ...
- 记录一次elasticsearch-5.6.4宕机排查经历
犯罪现场~~ es: 三节点,配置相同 内存: 248G CPU: 没注意看 磁盘: 2T data: 380G左右 indices: 近9800条 在下才疏学浅,目前跟着大佬学习,这个问题还没解决, ...
- Java 性能优化实战记录(3)--JVM OOM的分析和原因追查
前言: C/C++的程序员渴望Java的自由, Java程序员期许C/C++的约束. 其实那里都是围城, 外面的人想进来, 里面的人想出去. 背景: 作为Java程序员, 除了享受垃圾回收机制带来的便 ...
- FastDFS并发会有bug,其实我也不太信?- 一次并发问题的排查经历
前一段时间,业务部门同事反馈在一次生产服务器升级之后,POS消费上传小票业务偶现异常,上传小票业务有重试机制,有些重试三次也不会成功,他们排查了一下没有找到原因,希望架构部帮忙解决. 公司使用的是Fa ...
- SQL Server死锁排查经历 -基于SqlProfiler
提到sql server,想必最让人头疼的当属锁机制了.在默认的read committed隔离模式下,连最基本的select操作都要申请各种粒度的锁,而且在读取数据过程中会不断有锁升级.转化.在非 ...
随机推荐
- Java 注解 (Annotation)
Java 注解用于为 Java 代码提供元数据.作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的.Java 注解是从 Java5 开始添加到 Java 的. 注解语法 ...
- c/c++ 代码中使用sse指令集加速
使用SSE指令,首先要了解这一类用于进行初始化加载数据以及将暂存器的数据保存到内存相关的指令, 我们知道,大多数SSE指令是使用的xmm0到xmm8的暂存器,那么使用之前,就需要将数据从内存加载到这些 ...
- Android检查设备是否可以访问互联网,判断Internet连接,测试网络请求,解析域名
安卓SDK提供了ConnectivityManager类,那么我们就可以轻松的获取设备的网络状态以及联网方式等信息. 但是要想知道安卓设备连接的网络能不能访问到Internet,就要费一番周折了. 本 ...
- PHP安全之临时文件的安全
(一)临时文件简介临时文件,顾名思义是临时的文件,文件的生命周期短.然而,很多应用的运行都离不开临时文件,临时文件在我们电脑上无处不在,通常有以下几种形式的临时文件: 文件或图形编辑程序,所生成的中间 ...
- Android 下载zip压缩文件并解压
网上有很多介绍下载文件或者解压zip文件的文章,但是两者结合的不多,在此记录一下下载zip文件并直接解压的方法. 其实也很简单,就是把下载文件和解压zip文件结合到一起.下面即代码: URLConne ...
- Android Studio INSTALL_FAILED_UID_CHANGED的解决办法
使用Android Studio开发Android应用,把Android应用调试安装在手机上时,出现了安装失败的提示:INSTALL_FAILED_UID_CHANGED. 上网找了很多资料: 1.说 ...
- MongoDB 之 幽灵操作避免
进行静态加载数据到集合的过程中可能会出现. 假设建立一个任务(Job):在MongoDB中进行千条更新操作,开始后迅速终止任务,终止所有更新操作,但依然发现新的更新任务在不断出现,即使任务已经停止. ...
- 我的Oracle控制台创建数据库的工具
Oracle windows 11.2.0.4 在控制台cmd下的创建工具,不依赖于服务和监听 工具及下载:Oracle控制台工具 注意:其中的 “seeddatabase.gbk.7z”文件为从Or ...
- MathType怎么编辑半开半闭区间
数学中的公式有很多,涉及到各种各样的样式,这些公式都会用到不同的符号,每一个符号用在不同数学问题的公式中,都会有其特定的意义,比如括号.括号这个符号在除了能够表示优先运算之外,还可以代表区间的意思,小 ...
- jQuery easyUI的datagrid,如何在翻页以后仍能记录被选中的行
1.先给出问题解决后的代码 <%@ page language="java" import="java.util.*" pageEncoding=&quo ...