空間站

          北極心空

            BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
            15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks

          公告

          本博客主要是在本人收集網上一些精彩技術文章,有時可能因疏忽轉載的時候沒有說明轉載出處和作者,如果您認為哪篇文章侵犯了你的版權,請通知本人: EMAIL:luwei-80@163.com 歡迎您光臨本博客!

          常用鏈接

          留言簿(15)

          我參與的團隊

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          我們項目中有一個后臺任務處理程序,是java開發application,用以處理網站提交的一些批量數據文件,因為這些數據文件數據量一般都比較大,所以寫了這個批量處理程序,用以異步處理這些批量數據文件。這個程序設計成插件式的,處理各種不同數據文件的功能單獨作為一個插件,然后使用Spring來粘合各個組件,這樣就可以很方便地對該程序進行擴展。
          ??????? 今天客戶提出一個要求:需要控制這個程序在同一主機上只能啟動一個實例。
          ??????? 為了實現客戶要求,我首先想到就是在數據庫中建一張表,程序啟動時往該表中寫入一個標志,等程序結束時再刪除標志。但這種方式存在一個問題就是,如果程序是非正常停止或被殺進程,那么這個標志就不可能被清除,那下一次啟動就會誤判為重復啟動;另外,如果用數據庫來記錄啟動標志的話,還把該程序跟數據庫緊密耦合起來,感覺很別扭。
          ????????排除了第一種方案之后,我以想到了用文件來保存啟動標志(好象一些大型的程序,諸如weblogic好象就是采用在文件中記錄啟動標志方式來控制重復啟動的)。客流量然這種方式不需要與數據庫耦合在一起,但也存在程序異常中止而無法清除啟動標志的問題,所以這個方案也被槍斃了。
          ????????我想到的第三種方案就是在JAVA中調用操作系統的查看系統進程的方式來取得系統進程,然后再檢測系統進程有特殊的進程標志來判斷是否重復啟動。但這種方式一是看起來很別扭,再者就是Window和?*nix系統中查看系統進程的命令不一樣,分成幾種情況來處理,無端地增加了程序的復雜性,也不可取。
          ??????? 能不能在內存中記錄一個啟動標志呢?理論上這應該是不可行的,因為跨JVM來相互操作內存數據是不可能。我在網上搜了一下,也沒找到相關的例子。
          ??????? 那能不能占用一點系統共享資源,來換取我們的目標呢?比較容易想到的系統資源并且不能重復使用的資源就是端口。我嘗試采用如下方案:在程序中指定一個不常用的端口(比如:12345),在程序啟動時,就指定的端口啟動一個ServerSocket,這個Socket只是為了占用這個端口,不接受任何網絡連接。如果試圖啟動第二個實例時,程序在該指定端口啟動ServerSocket時就會拋異常,這時我們就可以認為系統已經啟動過了,然后打印提示并直接退出程序即可。這種方式在理論上分析應該可以的,我開始動手修改程序。程序修改如下:
          java 代碼

          package?cn.com.pansky.xmdswz.application.scheduler;????
          ???
          import?org.apache.commons.logging.Log;????
          import?org.apache.commons.logging.LogFactory;????
          import?org.quartz.SchedulerException;????
          import?org.quartz.impl.StdScheduler;????
          import?org.springframework.beans.factory.BeanFactory;????
          import?org.springframework.context.support.ClassPathXmlApplicationContext;????
          import?cn.com.pansky.xmdswz.system.cache.CachedTableMgr;????
          import?cn.com.pansky.xmdswz.system.config.SystemConfig;????
          import?cn.com.pansky.xmdswz.utility.DateUtil;????
          import?org.quartz.JobDetail;????
          import?org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;????
          import?java.net.ServerSocket;????
          import?java.io.*;????
          ???
          /**???
          ?*?Title:?XXXXXXX??
          ?*?Description:?XXXXXXXXXXXX??
          ?*?Copyright:?Copyright?(c)?2006??
          ?*?Company:?www.pansky.com.cn??
          ?*???
          ?*?
          @author?Sheng?Youfu???
          ?*?
          @version?1.0???
          ?
          */???
          public?class?Scheduler?{????
          ??
          private?static?Log?log?=?LogFactory.getLog(Scheduler.class);????
          ???
          ??
          private?static?ServerSocket?srvSocket?=?null;?//服務線程,用以控制服務器只啟動一個實例????
          ???
          ??
          private?static?final?int?srvPort?=?12345;?????//控制啟動唯一實例的端口號,這個端口如果保存在配置文件中會更靈活????
          ???
          ??
          /**???
          ???*?定時任務配置文件???
          ???
          */???
          ??
          private?static?String?CONFIG_FILE?=?"cn/com/pansky/xmdswz/application/scheduler/Scheduling-bean.xml";????
          ???
          ???
          ??
          public?Scheduler()?{????
          ????
          //檢測系統是否只啟動一個實例????
          ????checkSingleInstance();????
          ???
          ????
          //下面讀取Spring的配置文件????
          ????SystemConfig?cfg?=?new?SystemConfig();????
          ????String?config?
          =?cfg.parseParam("SCHEDULER.CONFIG_FILE",?false);????
          ????
          if(config!=null?&&?!"".equals(?config.trim()))????
          ??????CONFIG_FILE?
          =?config;????
          ????log.debug(
          "CONFIG_FILE:?"+CONFIG_FILE);????
          ??}????
          ???
          ??
          /**???
          ???*?主函數???
          ???*?
          @param?args?String[]???
          ???*?
          @throws?Exception???
          ???
          */???
          ??
          public?static?void?main(String[]?args)?throws?Exception{????
          ????Scheduler?sch?
          =?new?Scheduler();????
          ????sch.execute();????
          ??}????
          ???
          ??
          /**???
          ???*?運行定時任務???
          ???
          */???
          ??
          public?void?execute()?{????
          ????ClassPathXmlApplicationContext?appContext?
          =?new?ClassPathXmlApplicationContext(new?String[]?{CONFIG_FILE});????
          ????BeanFactory?factory?
          =?(BeanFactory)?appContext;????
          ???
          ????
          /**???
          ?????*?裝載任務調度???
          ?????
          */???
          ????StdScheduler?scheduler?
          =?(StdScheduler)?factory.getBean("schedulerFactoryBean");????
          ????
          //先暫停所有任務,等待裝載緩存代碼表????
          ????try?{????
          ??????scheduler.pauseAll();????
          ????}?
          catch?(SchedulerException?ex)?{????
          ??????log.error(
          "",ex);????
          ????}????
          ???
          ????
          /**???
          ?????*?裝載緩存代碼表???
          ?????
          */???
          ????CachedTableMgr?cachedtableMgr?
          =?(CachedTableMgr)?factory.getBean("cachedTableMgr");????
          ????
          try?{????
          ??????cachedtableMgr.loadCodeTable();????
          ????}?
          catch?(Exception?ex)?{????
          ??????log.fatal(
          "Load?cached?table?failed.?System?will?exit.",?ex);????
          ??????System.exit(
          0);????
          ????}????
          ???
          ????
          //重新恢復所有任務????
          ????try?{????
          ??????scheduler.resumeAll();????
          ????}?
          catch?(SchedulerException?ex)?{????
          ??????log.error(
          "",ex);????
          ????}????
          ??}????
          ???
          ??
          /**???
          ???*?檢測系統是否只啟動了一個實例???
          ???
          */???
          ??
          protected?void?checkSingleInstance()?{????
          ????
          try?{????
          ??????srvSocket?
          =?new?ServerSocket(srvPort);?//啟動一個ServerSocket,用以控制只啟動一個實例????
          ????}?catch?(IOException?ex)?{????
          ??????
          if(ex.getMessage().indexOf("Address?already?in?use:?JVM_Bind")>=0)????
          ????????System.out.println(
          "在一臺主機上同時只能啟動一個進程(Only?one?instance?allowed)。");????
          ??????log.fatal(
          "",?ex);????
          ??????System.exit(
          0);????
          ????}????
          ??}????
          }????


          經過測試,程序能很好地滿足我們的要求,問題解決。
          ???????? 我之所以稱這種方式另類,是因為這種方式以犧牲一個端口的代價來達到我們的設計要求,采用這種方式的人應該不多。但我認為,只要我們犧牲的代價與我們的目標比較起來是在可接受的范圍內,這種方式就是可取的,這與我們花錢增加內存來讓程序運行更快在本質應該是相同的。
          ?
          ?
          complystill
          等級: 4星會員
          complystill的博客:complystill

          性別:
          文章: 178
          積分: 430
          圈子: 駕馭無形的力量—軟件藝法思考

          ?????? 時間: 1 星期前 ?? 評級: ? 11111?(2位會員評分) ??? ???

          確實是很不錯的一個方法, 絕大部分服務器系統上, 端口相對來說還是比較cheap的.

          這個問題經典的跨平臺解決方法是建一個命名的系統互斥量, 它的生命周期也是跟著進程的. 不過Java平臺不傾向于提供直接操作宿主系統資源的途徑, 自己也是以虛擬機為全部邏輯環境, 不提供宿主系統范圍的Inter-JVM-Communication機制. 用端口綁定方式來實現互斥確實有點另類, 不過對于純Java應用來說不失為最佳的解決方案.

          另可以有一點改進的地方, 就是綁定到 InetAddress.getLocalHost() 這個本機地址(在大多數OS上相當于127.0.0.1), 這樣進一步不會占用服務器外部ip上的端口. 不過一般的服務器上服務進程通常也不指定具體外部地址, 而是綁定到所有本機地址的端口, 即便這樣改過以后還是會影響他們的啟動. 所以這個改進除非在很大型的系統上, 服務應用各自指定具體的外部地址去綁定時才有作用.

          Sunteya
          等級: 初級會員
          Sunteya的博客:Sunteya

          文章: 2
          積分: 34

          ?????? 時間: 1 星期前 ?? 評級: ? 11111?(3位會員評分) ??? ???

          其實 監聽同一個端口還是很常用的方法,比如 Azureus(開源Java BT 客戶端) 就是這樣。

          另一個常用的方法是像 Eclipse 的 workspace 一樣。用 RandomAccessFile 把 File 的寫 鎖定掉

          posted on 2006-12-11 11:39 蘆葦 閱讀(1124) 評論(0)  編輯  收藏 所屬分類: JAVA
          主站蜘蛛池模板: 留坝县| 丽江市| 临湘市| 阳高县| 万年县| 靖西县| 长沙市| 黔江区| 广汉市| 宜春市| 上饶市| 磐石市| 泾川县| 多伦县| 皮山县| 小金县| 肃北| 沙坪坝区| 益阳市| 睢宁县| 宁强县| 邵武市| 昌平区| 耒阳市| 石林| 青冈县| 筠连县| 社会| 罗源县| 易门县| 克什克腾旗| 嵩明县| 鸡泽县| 乡宁县| 泌阳县| 弥渡县| 同仁县| 石屏县| 丰顺县| 灵宝市| 阜宁县|