別拿計劃任務不當乾糧,當心分分鐘幹掉你的系統,想看看怎麼樣狗帶最慘,請參考下面的手冊框架
許多計劃任務是用於統計或者批處理的,常常須要遍歷某個列表網站
好比:spa
//查找全部將要過時的用戶,逐個發送郵件設計
Iterable<UserEntity> users = userDAO.findExpireUser();
for (UserEntity user : users) {日誌
//發送邏輯
Thread.sleep(5000);
}
}code
上面的這個例子裏,coder可能認爲同時過時的用戶可能很少,不過你忽略的一個狀況,若是系統裏的用戶是同一天批量建立的,他們中的大多數會在同一天過時,因而就可能出現幾萬,幾十萬用戶都須要發郵件的壯觀場面。。。。上面這個例子裏,一次來了兩百萬。orm
正確作法:進程
取數據要分頁取,分頁處理,處理了當即釋放,除非你很是肯定不會增加的列表,都不準用listAll,getAll,由於生產環境的行爲永遠出乎你的意料。ip
Iterable<UserEntity> users = userDAO.findExpireUserbyPage(i,pagesize);
for (UserEntity user : users) {內存
//發送邏輯
Thread.sleep(5000);
}
}
從團隊方面來說,凡是DAO裏面有listAll,getAll,無條件search的都是問題高發區,團隊應該限制對這些方法的調用。
仍是上面的例子,每次發送郵件等待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(page,pageSize);
For(UserEntity user in users){
List<Log> logs=getLoginLogForToday(user.username);
System.out.println(「username:」+user.username+」 logintimes:」+logs.size);
}
上面的需求、設計、代碼看起來簡潔有效,可是你確定就上當了,這個實現是個坑,由於用戶不會都登陸,設想一個網站有1000萬用戶,天天登陸的用戶可能就100w人次,按照上面的實現,必定會循環1000萬次,可是若是反過來,從日誌入手,就只用循環100w次
正確作法:
找到循環最少的路徑,不要被語言表面的意思所幹擾
好比每分鐘咱們要把登陸日誌取出來,分析下有沒有異常狀況,好比有沒有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)
。。。分析邏輯
}
}
。。。。。
For(analyzer in AnalyerList){
Analyzer.process()
}
乍一看很順眼,每一個analyzer幹本身的事情,可是具體執行起來,會屢次查詢和循環日誌,考慮下假若有100個分析器,就會遍歷日誌100次,加上執行process自己須要時間,很是可能形成這分鐘的尚未執行完,下次的分析又開始了,一波波最終疊加成海嘯,讓應用歇菜。
正確作法:
日誌分析,若是至少要遍歷一遍,那麼最好也就只遍歷一遍,寫個遍歷器,負責逐條遍歷,把每條交給各個處理器處理,一次遍歷解決全部問題。
List<log> logs=getLog(Now,lastCheckTime)
For(log in logs){
For(analyzer in AnalyerList){
if(analyzer.canProcess(log))
analyzer.process(log)
}
}
可能你會問,若是每一個處理器處理的日誌類型不一樣怎麼辦呢,好比有的處理器處理登陸,有的處理建立用戶,有的要處理全部日誌,其實只要有處理全部日誌的狀況,其餘的處理就能夠一塊兒作了,反正都要循環一次,就不要再浪費cpu循環屢次了,對開發團隊來講,日誌處理應該有統一的框架,有統一的人審覈,處理相似數據,執行頻率相同的要歸併,不然各自爲政就會出現上面的狀況。