要想讓計劃任務「坑」死你的系統,你必定要這樣寫

別拿計劃任務不當乾糧,當心分分鐘幹掉你的系統,想看看怎麼樣狗帶最慘,請參考下面的手冊框架

獲取大量的數據逐條處理

許多計劃任務是用於統計或者批處理的,常常須要遍歷某個列表網站

好比:spa

//查找全部將要過時的用戶,逐個發送郵件設計

       Iterable<UserEntity> users = userDAO.findExpireUser();
       for (UserEntity user : users) {日誌

      //發送邏輯
                Thread.sleep(5000);
           }
       }code

上面的這個例子裏,coder可能認爲同時過時的用戶可能很少,不過你忽略的一個狀況,若是系統裏的用戶是同一天批量建立的,他們中的大多數會在同一天過時,因而就可能出現幾萬,幾十萬用戶都須要發郵件的壯觀場面。。。。上面這個例子裏,一次來了兩百萬。orm

正確作法:進程

取數據要分頁取,分頁處理,處理了當即釋放,除非你很是肯定不會增加的列表,都不準用listAllgetAll,由於生產環境的行爲永遠出乎你的意料。ip

Iterable<UserEntity> users = userDAO.findExpireUserbyPage(i,pagesize);
       for (UserEntity user : users) {內存

       //發送邏輯
                Thread.sleep(5000);
           }
       }

從團隊方面來說,凡是DAO裏面有listAllgetAll,無條件search的都是問題高發區,團隊應該限制對這些方法的調用。

 

 

在循環裏Sleep一下子

仍是上面的例子,每次發送郵件等待5秒,oh my god,雙殺來了。假若有200w用戶同時過時,還不算髮送邏輯自己的時間,那是一五二十,二五一十,對不起個人手指頭不夠數,大概是115.7天的時間,關鍵是在這大概4個月的時間裏,users這個列表妥妥的沒法被回收,輕鬆吃掉一兩個G內存呢,想一想有點小激動呢,我親愛的OutOfMemory可等不了4個月,分分鐘在來的路上呢。

 

正確作法:

在循環裏千萬不要sleep或者調用太費時的任務,好比上面的邏輯,應該把郵件放到發送列表,而後馬上返回,這些耗時的操做,好比發送郵件這些事情,應該交給另外的進程去完成。術業有專攻,不要越俎代庖

 

Iterable<UserEntity> users = userDAO.findExpireUserbyPage(i,pagesize);
       for (UserEntity user : users) {

       //發送邏輯
                MailBox.put(new ExpireMail(user.mail))
           }
       }

被直覺帶節奏

產品:請寫一個schedule統計出全部用戶天天的登陸次數

程序猿的設計:分頁獲取全部用戶,查找該頁用戶今天的登陸日誌條數

UserEntity users=GetAllUser(pagepageSize);

For(UserEntity user in users){

List<Log>  logs=getLoginLogForToday(user.username);

System.out.println(「username:」+user.username+」 logintimes:」+logs.size);

}

上面的需求、設計、代碼看起來簡潔有效,可是你確定就上當了,這個實現是個坑,由於用戶不會都登陸,設想一個網站有1000萬用戶,天天登陸的用戶可能就100w人次,按照上面的實現,必定會循環1000萬次,可是若是反過來,從日誌入手,就只用循環100w

 

正確作法:

找到循環最少的路徑,不要被語言表面的意思所幹擾

 

1次循環就能完成?不,讓我多來幾回

好比每分鐘咱們要把登陸日誌取出來,分析下有沒有異常狀況,好比有沒有ip重複登陸啦,有沒有應用訪問量超標啊,有沒有人在危險的地區登陸啊,而後把危險狀況產生預警,通知管理員。因而產生了這樣的代碼

IpLoginAnalyzer implements Analyzer{

Public void process(){

List<log> logs=getLog(Now,lastCheckTime)

。。。分析邏輯

}

}

AppVisitBarrierAnalyzer implements Analyzer{

Public void process(){

List<log> logs=getLog(Now,lastCheckTime)

。。。分析邏輯

}

}

DangerLocationAnalyzer implements Analyzer{

Public void process(){

List<log> logs=getLog(Now,lastCheckTime)

。。。分析邏輯

}

}

。。。。。

Foranalyzer in AnalyerList{

Analyzer.process()

}

 

乍一看很順眼,每一個analyzer幹本身的事情,可是具體執行起來,會屢次查詢和循環日誌,考慮下假若有100個分析器,就會遍歷日誌100次,加上執行process自己須要時間,很是可能形成這分鐘的尚未執行完,下次的分析又開始了,一波波最終疊加成海嘯,讓應用歇菜。

 

正確作法:

日誌分析,若是至少要遍歷一遍,那麼最好也就只遍歷一遍,寫個遍歷器,負責逐條遍歷,把每條交給各個處理器處理,一次遍歷解決全部問題。

List<log> logs=getLog(Now,lastCheckTime)

For(log in logs){

Foranalyzer in AnalyerList{

if(analyzer.canProcess(log))

analyzer.process(log)

}

}

 

可能你會問,若是每一個處理器處理的日誌類型不一樣怎麼辦呢,好比有的處理器處理登陸,有的處理建立用戶,有的要處理全部日誌,其實只要有處理全部日誌的狀況,其餘的處理就能夠一塊兒作了,反正都要循環一次,就不要再浪費cpu循環屢次了,對開發團隊來講,日誌處理應該有統一的框架,有統一的人審覈,處理相似數據,執行頻率相同的要歸併,不然各自爲政就會出現上面的狀況。

相關文章
相關標籤/搜索