<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          不想 CRUD 干到老,就來看看這篇 OOM 排查的實戰(zhàn)案例!

          共 1478字,需瀏覽 3分鐘

           ·

          2020-09-16 23:27

          點擊上方藍色“程序猿DD”,選擇“設(shè)為星標(biāo)”

          回復(fù)“資源”獲取獨家整理的學(xué)習(xí)資料!

          作者:三國夢回

          來源:cnblogs.com/grey-wolf/p/9179113.html

          一、經(jīng)歷概要
          程序里有個跑數(shù)據(jù)的job,這個job的主要功能是往數(shù)據(jù)庫寫假數(shù)據(jù)。
          既需要跑歷史數(shù)據(jù)(傳給job的日期是過去的時間),也需要能夠上線后,實時跑(十秒鐘觸發(fā)一次,傳入觸發(fā)時的當(dāng)前時間)。
          其中一個job比較奇葩點,要寫入的數(shù)據(jù)比較難以隨機生成,是產(chǎn)品的同事從互聯(lián)網(wǎng)上找的數(shù)據(jù),比如當(dāng)前網(wǎng)絡(luò)上的熱門話題,然后導(dǎo)入到數(shù)據(jù)庫中。所以,
          我這邊隨機的時候,不能亂造。因此我的策略是,從數(shù)據(jù)庫將已經(jīng)存在的那幾條真實數(shù)據(jù)查詢出來,然后job中根據(jù)隨機數(shù),選擇其中一條來仿造一條新的,
          隨機生成新記錄的其他字段,再寫入數(shù)據(jù)庫中。
          我單元測試一直這么跑的,沒有任何問題,直到,將定時觸發(fā)器打開,然后上線運行。悲劇來了。

          二、程序大體邏輯

          1、job接口定義:


          /**
          ?* desc:
          ?* 造數(shù)據(jù)的job,可按表來劃分。一個表一個job
          ?* @author :
          ?* creat_date: 2018/6/11 0011
          ?* creat_time: 14:46
          ?**/

          public?interface?DataProduceJob?{
          /**
          ?????* job的初始化
          ?????* @param date
          ?????*/

          void?jobInit(Date date);
          /**
          ?????* 具體的job運行細(xì)節(jié)
          ?????*/

          void?jobDetail(Integer recordNum);
          }
          job之所以分了上面兩個接口,只是因為設(shè)計失誤,完全可以融合為一個方法。jobInit的內(nèi)容,后來我改寫到j(luò)ob的afterPropertiesSet中了。


          (job實現(xiàn)了org.springframework.beans.factory.InitializingBean接口,保證初始化數(shù)據(jù)只被調(diào)用一次,所謂的初始化數(shù)據(jù)是指:讀文件,讀數(shù)據(jù)庫之類的準(zhǔn)備工作,后續(xù)的假數(shù)據(jù)都從這里面取)
          ?
          這邊是出問題的job的源碼:
          @Component
          public?class?TopicWebsiteJob?extends?BaseJob?implements?DataProduceJob?{
          @Autowired
          private?TopicWebsiteMapper topicWebsiteMapper;
          private?Date date;
          ????Random random =?new?Random();
          private?List topicWebsites;
          /**
          ?????* 當(dāng)前job執(zhí)行時的時間,會作為創(chuàng)建時間寫入數(shù)據(jù)庫表
          ?????*
          ?????*?@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));
          //設(shè)置時間
          ????????????????topicWebsite.setCreateTime(date);
          ????????????????topicWebsiteMapper.insert(topicWebsite);
          ????????????}
          ????????}
          ????}
          }

          2、job的歷史數(shù)據(jù)初始化器


          初始化器,主要是用于生成歷史數(shù)據(jù),用的是隨機生成的過去30天內(nèi)的時間,去new一個job。然后調(diào)用job的init,設(shè)置date;然后調(diào)用job的細(xì)節(jié)。上面我也說了,沒必要搞兩個,只是最初設(shè)計失誤了。


          總體邏輯,就是傳入日期,然后根據(jù)那個日期,去造假數(shù)據(jù)。
          package com..datavisual.quartz.init;
          /**
          ?* desc:
          ?* 用于造初始化數(shù)據(jù)
          ?*?@author?:
          ?* creat_date: 2018/6/11 0011
          ?* creat_time: 14:29
          ?**/

          public?interface?Initer?{
          /**
          ?????* 具體的初始化邏輯,可參考
          ?????*?@return?成功或失敗
          ?????*/

          Boolean init();
          }
          出問題的初始化器的源碼:
          /**
          ?* 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();
          //日期循環(huán),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、目前為止,運行正常?


          到目前為止,運行沒什么問題,因為我都是用單元測試的方式去調(diào)用上面的initer.init方法。真的嗎?

          4、加上定時觸發(fā)機制


          這些job,在上線后,還是需要繼續(xù)運行。具體的間隔,是每十秒觸發(fā)一次。
          code如下:
          @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比較多,說實話,也沒注意一些細(xì)節(jié),沒去查看數(shù)據(jù)庫的數(shù)據(jù)條數(shù)。我一直以為沒啥問題,直到運行了沒一會,程序假死了,卡著不動了。


          后來將堆轉(zhuǎn)儲拿出來分析,才發(fā)現(xiàn),是因為每次init被多次調(diào)用了,每次調(diào)用都會從表里面查所有數(shù)據(jù)(一直以為只有10條真實數(shù)據(jù))。然后根據(jù)這些數(shù)據(jù),去生成新的假數(shù)據(jù)。再插回表里。這時候表里的數(shù)據(jù),差不多翻倍了。
          ?
          再過10s后,再次查詢,這次查到20條,然后,又造了20條假數(shù)據(jù),寫到表里,變成了40條。再過10s后,再次查詢,這次查到40條,然后,又造了40條假數(shù)據(jù),寫到表里,變成了80條。
          ...


          然后就越來越慢,越來越卡。直到發(fā)現(xiàn)表里竟然變成了千萬條數(shù)據(jù),然后將java程序的內(nèi)存撐爆了。
          ?

          三、總結(jié)


          其實這次主要的坑,在于自己設(shè)計功力不夠,沒有考慮清楚。數(shù)據(jù)庫的數(shù)據(jù)是變化的,而我拿變化的東西作為基準(zhǔn),來生成假數(shù)據(jù),再將假數(shù)據(jù)寫入到原表,造成了
          表里數(shù)據(jù)的指數(shù)級增長,然后撐爆了內(nèi)存。
          拋開這塊不說,比較有意思的是,查找這個bug背后原因的過程,后邊單獨寫。如果喜歡我的文章,記得點贊轉(zhuǎn)發(fā)支持一波~~~

          往期推薦

          居然僅用瀏覽器,就完成了Spring Boot應(yīng)用的開發(fā)與部署!

          「贈書」賈揚清推薦,國內(nèi)首本數(shù)據(jù)競賽圖書

          使用IntelliJ IDEA查看類圖,內(nèi)容極度舒適

          Stack Overflow 2020 開發(fā)者調(diào)查中的 21 條關(guān)鍵結(jié)果

          Serverless 初體驗:快速開發(fā)與部署一個Hello World


          最后,推薦一個專注分享后端面試要點的公眾號「后端面試那些事兒」,置頂標(biāo)星。每日一篇常問的面試問題,秀的一批~掃描下方二維碼關(guān)注!
          瀏覽 69
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  高清无码高潮 | 肏屄一级片 | 黄色片视频在线 | 伊人久久五月天 | 中韩一区二区 |