Apache LRU算法問題分析解決
Apache LRU算法問題分析解決
UDL(United Data Load)是公司開發用于通用數據加載工具,支持抽取設置、作業定義并提供頁面監控等功能,在某一項目中中多次出現了UDL作業調度出現問題,由于該問題隱藏得較深,分析解決花了較多的精力,現把經驗總結共享給大家。
1. 問題現象
問題一般是UDL運行3-5天才出現,出現錯誤現象是固定的,后臺拋出如下異常(紅色部分是下面分析的切入點):
1 2011-10-08 01:05:00,078 [] ERROR - 作業:DELETE_F_TABLE_ALL_WORKID運行過程中發生不可預知的錯誤,當前作業在本次調度中終止運行,發生不可預知的錯誤,這通常是基礎信息配置有誤,或運行環境異常導致,等原因,請根據錯誤提示信息,檢查錯誤原因 [URuntimeJob:runJob]
2 java.lang.IllegalStateException: Entry.next=null, data[removeIndex]=resoft.udl.schedule.JobKey@600a5e7d=false previous=resoft.udl.schedule.JobKey@74a3b4ef=false key=resoft.udl.schedule.JobKey@fb09e543 value=true size=150 maxSize=150 Please check that your keys are immutable, and that you have used synchronization properly. If so, then please report this to commons-dev@jakarta.apache.org as a bug.
3 at org.apache.commons.collections.map.LRUMap.reuseMapping(LRUMap.java:300)
4 at org.apache.commons.collections.map.LRUMap.addMapping(LRUMap.java:266)
5 at org.apache.commons.collections.map.AbstractHashedMap.put(AbstractHashedMap.java:283)
6 at resoft.udl.jetlValidate.logicValidate.validator.ValidatorFactory.setJobOver(ValidatorFactory.java:57)
7 at resoft.udl.job.URuntimeJob.runJob(URuntimeJob.java:389)
8 at resoft.udl.job.URuntimeJob.execute(URuntimeJob.java:132)
9 at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
10 at edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:987)
11 at edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:528)
12 at java.lang.Thread.run(Thread.java:595)
2 java.lang.IllegalStateException: Entry.next=null, data[removeIndex]=resoft.udl.schedule.JobKey@600a5e7d=false previous=resoft.udl.schedule.JobKey@74a3b4ef=false key=resoft.udl.schedule.JobKey@fb09e543 value=true size=150 maxSize=150 Please check that your keys are immutable, and that you have used synchronization properly. If so, then please report this to commons-dev@jakarta.apache.org as a bug.
3 at org.apache.commons.collections.map.LRUMap.reuseMapping(LRUMap.java:300)
4 at org.apache.commons.collections.map.LRUMap.addMapping(LRUMap.java:266)
5 at org.apache.commons.collections.map.AbstractHashedMap.put(AbstractHashedMap.java:283)
6 at resoft.udl.jetlValidate.logicValidate.validator.ValidatorFactory.setJobOver(ValidatorFactory.java:57)
7 at resoft.udl.job.URuntimeJob.runJob(URuntimeJob.java:389)
8 at resoft.udl.job.URuntimeJob.execute(URuntimeJob.java:132)
9 at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
10 at edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:987)
11 at edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:528)
12 at java.lang.Thread.run(Thread.java:595)
2. 問題分析
跟蹤了UDL代碼 ValidatorFactory.java中setJobOver方法
1 private static Map isJobOverMap = new LRUMap(100);
2 public static void setJobOver(String jobid, String date, boolean isTaskJobOver) {
3 isJobOverMap.put(new JobKey(jobid, date), Boolean.valueOf(isTaskJobOver));
4 }
2 public static void setJobOver(String jobid, String date, boolean isTaskJobOver) {
3 isJobOverMap.put(new JobKey(jobid, date), Boolean.valueOf(isTaskJobOver));
4 }
在LRUMap.java 調用的代碼如下(紅色為拋出異常點):
1 protected void reuseMapping(LinkEntry entry, int hashIndex, int hashCode, Object key, Object value) {
2 try {
3 int removeIndex = hashIndex(entry.hashCode, data.length);
4 HashEntry[] tmp = data; // may protect against some sync issues
5 HashEntry loop = tmp[removeIndex];
6 HashEntry previous = null;
7 while (loop != entry && loop != null) {
8 previous = loop;
9 loop = loop.next;
10 }
11 if (loop == null) {
12 throw new IllegalStateException(
13 ……
14 }
2 try {
3 int removeIndex = hashIndex(entry.hashCode, data.length);
4 HashEntry[] tmp = data; // may protect against some sync issues
5 HashEntry loop = tmp[removeIndex];
6 HashEntry previous = null;
7 while (loop != entry && loop != null) {
8 previous = loop;
9 loop = loop.next;
10 }
11 if (loop == null) {
12 throw new IllegalStateException(
13 ……
14 }
從獲取的日志結合出錯現象分析,出錯可能有如下幾個方面:
- 網絡問題 由于網絡中斷造成數據庫連接出現中斷,但該問題出現比較有規律,實際情況出現的可能性很小,初步排除;
- 數據庫連接超時時間 數據庫連接時間設置不足引起連接被注銷,通過后面設置數據庫連接超時來看,并不是該問題造成的,可以排除;
- 使用LRU算法出錯 所謂LRU是Least Recently Used最近最少使用算法,主要是防止使用內存過大,造成內存溢出的一種方法,在編程中主要用于存放使用即可拋棄的對象。UDL存放作業是否完成的狀態,有可能出現存放狀態的對象被擠出內存情況,造成獲取狀態時無法找到,這個問題可能性較大,作為后面分析的重點;
- 線程同步問題 由于Apache中LRU算法是非線程安全的,LRU中存放對象達到最大數量調用LRU算法reuse內存空間,由于非線程安全可能出現兩個進程同時操作一個內存區域造成錯誤,可能性非常大;
從更深入了解客戶現場定義的作業將近100多個,設置LRU的最大數量為150,這樣在運行一段時間后達到了LRU設置的最大數量,此時將調用LRU算法進行內存清理、替換,出現如上問題。
3. 問題解決
通過問題分析,我們大致清楚了LRU算法及線程同步是解決問題的方法,提出了兩個解決方案:
- 不使用LRU算法,通過手工管理作業生命周期
- 在代碼中加入線程同步的代碼
對比兩個方案第一種方案會比較徹底,但改動較大,第二種方案比較簡單,故先采用第二種方案,紅色部分為修改。
1 public static void setJobOver(String jobid, String date,
2 boolean isTaskJobOver) {
3
4 synchronized (isJobOverMap) {
5 isJobOverMap.put(new JobKey(jobid, date), Boolean.valueOf(isTaskJobOver));
6 }
7 }
2 boolean isTaskJobOver) {
3
4 synchronized (isJobOverMap) {
5 isJobOverMap.put(new JobKey(jobid, date), Boolean.valueOf(isTaskJobOver));
6 }
7 }
通過如上修改解決了問題。
4. 需要注意
通過以上問題和代碼分析解決了隱藏較深的一個Bug,可以借鑒的經驗有:
- 慎用LRU算法,最好存放使用即拋棄對象不存在問題;對于存放需要維護的對象時,最好不使用,存在需休眠對象被清除的風險;
- 特別要注意線程安全問題,需進行同步。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。

浙公網安備 33010602011771號