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

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

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

    空間站

    北極心空

      BlogJava :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
      15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks
    我們項(xiàng)目中有一個后臺任務(wù)處理程序,是java開發(fā)application,用以處理網(wǎng)站提交的一些批量數(shù)據(jù)文件,因?yàn)檫@些數(shù)據(jù)文件數(shù)據(jù)量一般都比較大,所以寫了這個批量處理程序,用以異步處理這些批量數(shù)據(jù)文件。這個程序設(shè)計成插件式的,處理各種不同數(shù)據(jù)文件的功能單獨(dú)作為一個插件,然后使用Spring來粘合各個組件,這樣就可以很方便地對該程序進(jìn)行擴(kuò)展。
    ??????? 今天客戶提出一個要求:需要控制這個程序在同一主機(jī)上只能啟動一個實(shí)例。
    ??????? 為了實(shí)現(xiàn)客戶要求,我首先想到就是在數(shù)據(jù)庫中建一張表,程序啟動時往該表中寫入一個標(biāo)志,等程序結(jié)束時再刪除標(biāo)志。但這種方式存在一個問題就是,如果程序是非正常停止或被殺進(jìn)程,那么這個標(biāo)志就不可能被清除,那下一次啟動就會誤判為重復(fù)啟動;另外,如果用數(shù)據(jù)庫來記錄啟動標(biāo)志的話,還把該程序跟數(shù)據(jù)庫緊密耦合起來,感覺很別扭。
    ????????排除了第一種方案之后,我以想到了用文件來保存啟動標(biāo)志(好象一些大型的程序,諸如weblogic好象就是采用在文件中記錄啟動標(biāo)志方式來控制重復(fù)啟動的)。客流量然這種方式不需要與數(shù)據(jù)庫耦合在一起,但也存在程序異常中止而無法清除啟動標(biāo)志的問題,所以這個方案也被槍斃了。
    ????????我想到的第三種方案就是在JAVA中調(diào)用操作系統(tǒng)的查看系統(tǒng)進(jìn)程的方式來取得系統(tǒng)進(jìn)程,然后再檢測系統(tǒng)進(jìn)程有特殊的進(jìn)程標(biāo)志來判斷是否重復(fù)啟動。但這種方式一是看起來很別扭,再者就是Window和?*nix系統(tǒng)中查看系統(tǒng)進(jìn)程的命令不一樣,分成幾種情況來處理,無端地增加了程序的復(fù)雜性,也不可取。
    ??????? 能不能在內(nèi)存中記錄一個啟動標(biāo)志呢?理論上這應(yīng)該是不可行的,因?yàn)榭鏙VM來相互操作內(nèi)存數(shù)據(jù)是不可能。我在網(wǎng)上搜了一下,也沒找到相關(guān)的例子。
    ??????? 那能不能占用一點(diǎn)系統(tǒng)共享資源,來換取我們的目標(biāo)呢?比較容易想到的系統(tǒng)資源并且不能重復(fù)使用的資源就是端口。我嘗試采用如下方案:在程序中指定一個不常用的端口(比如:12345),在程序啟動時,就指定的端口啟動一個ServerSocket,這個Socket只是為了占用這個端口,不接受任何網(wǎng)絡(luò)連接。如果試圖啟動第二個實(shí)例時,程序在該指定端口啟動ServerSocket時就會拋異常,這時我們就可以認(rèn)為系統(tǒng)已經(jīng)啟動過了,然后打印提示并直接退出程序即可。這種方式在理論上分析應(yīng)該可以的,我開始動手修改程序。程序修改如下:
    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;?//服務(wù)線程,用以控制服務(wù)器只啟動一個實(shí)例????
    ???
    ??
    private?static?final?int?srvPort?=?12345;?????//控制啟動唯一實(shí)例的端口號,這個端口如果保存在配置文件中會更靈活????
    ???
    ??
    /**???
    ???*?定時任務(wù)配置文件???
    ???
    */???
    ??
    private?static?String?CONFIG_FILE?=?"cn/com/pansky/xmdswz/application/scheduler/Scheduling-bean.xml";????
    ???
    ???
    ??
    public?Scheduler()?{????
    ????
    //檢測系統(tǒng)是否只啟動一個實(shí)例????
    ????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);????
    ??}????
    ???
    ??
    /**???
    ???*?主函數(shù)???
    ???*?
    @param?args?String[]???
    ???*?
    @throws?Exception???
    ???
    */???
    ??
    public?static?void?main(String[]?args)?throws?Exception{????
    ????Scheduler?sch?
    =?new?Scheduler();????
    ????sch.execute();????
    ??}????
    ???
    ??
    /**???
    ???*?運(yùn)行定時任務(wù)???
    ???
    */???
    ??
    public?void?execute()?{????
    ????ClassPathXmlApplicationContext?appContext?
    =?new?ClassPathXmlApplicationContext(new?String[]?{CONFIG_FILE});????
    ????BeanFactory?factory?
    =?(BeanFactory)?appContext;????
    ???
    ????
    /**???
    ?????*?裝載任務(wù)調(diào)度???
    ?????
    */???
    ????StdScheduler?scheduler?
    =?(StdScheduler)?factory.getBean("schedulerFactoryBean");????
    ????
    //先暫停所有任務(wù),等待裝載緩存代碼表????
    ????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);????
    ????}????
    ???
    ????
    //重新恢復(fù)所有任務(wù)????
    ????try?{????
    ??????scheduler.resumeAll();????
    ????}?
    catch?(SchedulerException?ex)?{????
    ??????log.error(
    "",ex);????
    ????}????
    ??}????
    ???
    ??
    /**???
    ???*?檢測系統(tǒng)是否只啟動了一個實(shí)例???
    ???
    */???
    ??
    protected?void?checkSingleInstance()?{????
    ????
    try?{????
    ??????srvSocket?
    =?new?ServerSocket(srvPort);?//啟動一個ServerSocket,用以控制只啟動一個實(shí)例????
    ????}?catch?(IOException?ex)?{????
    ??????
    if(ex.getMessage().indexOf("Address?already?in?use:?JVM_Bind")>=0)????
    ????????System.out.println(
    "在一臺主機(jī)上同時只能啟動一個進(jìn)程(Only?one?instance?allowed)。");????
    ??????log.fatal(
    "",?ex);????
    ??????System.exit(
    0);????
    ????}????
    ??}????
    }????


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

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

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

    確實(shí)是很不錯的一個方法, 絕大部分服務(wù)器系統(tǒng)上, 端口相對來說還是比較cheap的.

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

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

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

    文章: 2
    積分: 34

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

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

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

    posted on 2006-12-11 11:39 蘆葦 閱讀(1123) 評論(0)  編輯  收藏 所屬分類: JAVA
    主站蜘蛛池模板: 最近国语视频在线观看免费播放| 久久精品免费一区二区| 亚洲AV天天做在线观看| 国产成人免费网站| h片在线播放免费高清| 亚洲婷婷综合色高清在线| 免费a级毛片大学生免费观看| 免费人成在线观看视频高潮| 亚洲成a人片在线不卡| 亚洲综合色在线观看亚洲| 免费h片在线观看网址最新| 五月天婷婷精品免费视频| 亚洲神级电影国语版| 亚洲精品成人片在线观看| xxxx日本免费| 久久一区二区免费播放| 亚洲天堂男人影院| 国产V亚洲V天堂无码| 国产在线观看免费不卡| xxxx日本免费| 国产成年无码久久久免费| 亚洲乱码国产乱码精华| 亚洲午夜精品久久久久久人妖| 亚洲第一页日韩专区| 成人无码区免费视频观看| 精品免费tv久久久久久久| 粉色视频免费入口| 亚洲激情视频图片| 97se亚洲综合在线| 国产AV无码专区亚洲AV漫画| 黄a大片av永久免费| 59pao成国产成视频永久免费 | 亚洲精品tv久久久久久久久| 午夜视频在线在免费| 3344免费播放观看视频| 两个人看www免费视频| www成人免费观看网站| 在线观看亚洲视频| 亚洲国产精品99久久久久久| 亚洲精品资源在线| 亚洲国产综合专区电影在线|