<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    空間站

    北極心空

      BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
      15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks
    我們項目中有一個后臺任務處理程序,是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 蘆葦 閱讀(1123) 評論(0)  編輯  收藏 所屬分類: JAVA
    主站蜘蛛池模板: 亚洲六月丁香六月婷婷色伊人| 亚洲AV无码日韩AV无码导航| 亚洲国产情侣一区二区三区| 88av免费观看| 在线观看亚洲一区二区| 午夜免费福利视频| 亚洲黄色在线视频| 16女性下面扒开无遮挡免费| 亚洲国产精品久久网午夜| 日韩毛片免费无码无毒视频观看| 亚洲三级视频在线| 国产美女精品久久久久久久免费 | 国产午夜影视大全免费观看 | jizz18免费视频| 亚洲精品午夜无码专区| 在线播放免费人成毛片乱码| 亚洲91av视频| 日本免费网址大全在线观看| 亚洲中文字幕无码亚洲成A人片 | 亚洲日韩精品A∨片无码| 日本高清免费观看| 亚洲国产综合在线| 麻豆精品国产免费观看| 日亚毛片免费乱码不卡一区| 日本亚洲成高清一区二区三区| 2019中文字幕在线电影免费| 7777久久亚洲中文字幕| 免费观看四虎精品国产永久| 两个人的视频www免费| 亚洲精品电影天堂网| 日韩免费福利视频| 国产色无码精品视频免费| 亚洲av永久无码精品三区在线4| 在线观看免费亚洲| 免费一级不卡毛片| 亚洲AV无码一区二区三区牲色| 中文字幕人成人乱码亚洲电影| 国产成人精品免费午夜app| 老司机午夜在线视频免费| 亚洲国产精品热久久| 国产免费一区二区三区VR|