百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

不想CRUD干到老,就来看看这篇OOM排查的实战案例

toyiye 2024-06-21 12:18 7 浏览 0 评论



一、经历概要

程序里有个跑数据的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背后原因的过程,后边单独写。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码