[轉載]防止JAVA程式重複啟動的一個另類解決辦法

sdvingo發表於2007-05-18
我們專案中有一個後臺任務處理程式,是java開發application,用以處理網站提交的一些批次資料檔案,因為這些資料檔案資料量一般都比較大,所以寫了這個批次處理程式,用以非同步處理這些批次資料檔案。這個程式設計成外掛式的,處理各種不同資料檔案的功能單獨作為一個外掛,然後使用Spring來粘合各個元件,這樣就可以很方便地對該程式進行擴充套件。
今天客戶提出一個要求:需要控制這個程式在同一主機上只能啟動一個例項。
為了實現客戶要求,我首先想到就是在資料庫中建一張表,程式啟動時往該表中寫入一個標誌,等程式結束時再刪除標誌。但這種方式存在一個問題就是,如果程式是非正常停止或被殺程式,那麼這個標誌就不可能被清除,那下一次啟動就會誤判為重複啟動;另外,如果用資料庫來記錄啟動標誌的話,還把該程式跟資料庫緊密耦合起來,感覺很彆扭。
排除了第一種方案之後,我以想到了用檔案來儲存啟動標誌(好象一些大型的程式,諸如weblogic好象就是採用在檔案中記錄啟動標誌方式來控制重複啟動的)。客流量然這種方式不需要與資料庫耦合在一起,但也存在程式異常中止而無法清除啟動標誌的問題,所以這個方案也被槍斃了。
我想到的第三種方案就是在JAVA中呼叫作業系統的檢視系統程式的方式來取得系統程式,然後再檢測系統程式有特殊的程式標誌來判斷是否重複啟動。但這種方式一是看起來很彆扭,再者就是Window和 *nix系統中檢視系統程式的命令不一樣,分成幾種情況來處理,無端地增加了程式的複雜性,也不可取。
能不能在記憶體中記錄一個啟動標誌呢?理論上這應該是不可行的,因為跨JVM來相互操作記憶體資料是不可能。我在網上搜了一下,也沒找到相關的例子。
那能不能佔用一點系統共享資源,來換取我們的目標呢?比較容易想到的系統資源並且不能重複使用的資源就是埠。我嘗試採用如下方案:在程式中指定一個不常用的埠(比如:12345),在程式啟動時,就指定的埠啟動一個ServerSocket,這個Socket只是為了佔用這個埠,不接受任何網路連線。如果試圖啟動第二個例項時,程式在該指定埠啟動ServerSocket時就會拋異常,這時我們就可以認為系統已經啟動過了,然後列印提示並直接退出程式即可。這種方式在理論上分析應該可以的,我開始動手修改程式。程式修改如下:
java 程式碼
  1. package cn.com.pansky.xmdswz.application.scheduler;
  2. import org.apache.commons.logging.Log;
  3. import org.apache.commons.logging.LogFactory;
  4. import org.quartz.SchedulerException;
  5. import org.quartz.impl.StdScheduler;
  6. import org.springframework.beans.factory.BeanFactory;
  7. import org.springframework.context.support.ClassPathXmlApplicationContext;
  8. import cn.com.pansky.xmdswz.system.cache.CachedTableMgr;
  9. import cn.com.pansky.xmdswz.system.config.SystemConfig;
  10. import cn.com.pansky.xmdswz.utility.DateUtil;
  11. import org.quartz.JobDetail;
  12. import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
  13. import java.net.ServerSocket;
  14. import java.io.*;
  15. /**
  16. * Title: XXXXXXX
  17. * Description: XXXXXXXXXXXX
  18. * Copyright: Copyright (c) 2006
  19. * Company:
  20. *
  21. * @author Sheng Youfu
  22. * @version 1.0
  23. */
  24. public class Scheduler {
  25. private static Log log = LogFactory.getLog(Scheduler.class);
  26. private static ServerSocket srvSocket = null; //服務執行緒,用以控制伺服器只啟動一個例項
  27. private static final int srvPort = 12345; //控制啟動唯一例項的埠號,這個埠如果儲存在配置檔案中會更靈活
  28. /**
  29. * 定時任務配置檔案
  30. */
  31. private static String CONFIG_FILE = "cn/com/pansky/xmdswz/application/scheduler/Scheduling-bean.xml";
  32. public Scheduler() {
  33. //檢測系統是否只啟動一個例項
  34. checkSingleInstance();
  35. //下面讀取Spring的配置檔案
  36. SystemConfig cfg = new SystemConfig();
  37. String config = cfg.parseParam("SCHEDULER.CONFIG_FILE", false);
  38. if(config!=null && !"".equals( config.trim()))
  39. CONFIG_FILE = config;
  40. log.debug("CONFIG_FILE: "+CONFIG_FILE);
  41. }
  42. /**
  43. * 主函式
  44. * @param args String[]
  45. * @throws Exception
  46. */
  47. public static void main(String[] args) throws Exception{
  48. Scheduler sch = new Scheduler();
  49. sch.execute();
  50. }
  51. /**
  52. * 執行定時任務
  53. */
  54. public void execute() {
  55. ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] {CONFIG_FILE});
  56. BeanFactory factory = (BeanFactory) appContext;
  57. /**
  58. * 裝載任務排程
  59. */
  60. StdScheduler scheduler = (StdScheduler) factory.getBean("schedulerFactoryBean");
  61. //先暫停所有任務,等待裝載快取程式碼表
  62. try {
  63. scheduler.pauseAll();
  64. } catch (SchedulerException ex) {
  65. log.error("",ex);
  66. }
  67. /**
  68. * 裝載快取程式碼表
  69. */
  70. CachedTableMgr cachedtableMgr = (CachedTableMgr) factory.getBean("cachedTableMgr");
  71. try {
  72. cachedtableMgr.loadCodeTable();
  73. } catch (Exception ex) {
  74. log.fatal("Load cached table failed. System will exit.", ex);
  75. System.exit(0);
  76. }
  77. //重新恢復所有任務
  78. try {
  79. scheduler.resumeAll();
  80. } catch (SchedulerException ex) {
  81. log.error("",ex);
  82. }
  83. }
  84. /**
  85. * 檢測系統是否只啟動了一個例項
  86. */
  87. protected void checkSingleInstance() {
  88. try {
  89. srvSocket = new ServerSocket(srvPort); //啟動一個ServerSocket,用以控制只啟動一個例項
  90. } catch (IOException ex) {
  91. if(ex.getMessage().indexOf("Address already in use: JVM_Bind")>=0)
  92. System.out.println("在一臺主機上同時只能啟動一個程式(Only one instance allowed)。");
  93. log.fatal("", ex);
  94. System.exit(0);
  95. }
  96. }
  97. }
[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/443058/viewspace-915510/,如需轉載,請註明出處,否則將追究法律責任。

相關文章