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

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

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

    我的家園

    我的家園

    背景:

    ? ? ? 容錯(cuò)重試機(jī)制,是系統(tǒng)的一種自我調(diào)節(jié),是對系統(tǒng)魯棒性的一種考量,在很多后臺(tái)程序中都經(jīng)常涉及,特別是基于 ? task的系統(tǒng)中,往往這類系統(tǒng)要處理的事情很多,一個(gè)task的完成時(shí)間比較長,涉及的環(huán)境也比較復(fù)雜,出現(xiàn)很多臨時(shí)錯(cuò)誤的概率較大,比如IO讀取出錯(cuò),網(wǎng)絡(luò)臨時(shí)不可用等等,同時(shí)這種系統(tǒng)往往對響應(yīng)時(shí)間要求不是很高,更加看重系統(tǒng)的穩(wěn)定和魯棒性;另外對于依賴于第三方的遠(yuǎn)程調(diào)用,或者說其他資源的獲取,也常常涉及要考慮容錯(cuò)重試;讓程序在不人工干預(yù)的情況下,處理更多的場景;

    ?

    適用場景:

    ? ? 從上面的描述我們可以總結(jié)它的適用場景:

    ? ? 1、系統(tǒng)對響應(yīng)時(shí)間不太關(guān)心;

    ? ? 2、系統(tǒng)對魯棒性要求較高;

    ? ? 3、系統(tǒng)涉及遠(yuǎn)程調(diào)用,資源獲取;

    ?

    設(shè)計(jì)目標(biāo):

    ? ? 1、通用性:希望作為一個(gè)獨(dú)立的模塊,為需要的程序提高方便的使用;

    ? ? 2、輕量級:希望是一個(gè)輕量級的實(shí)現(xiàn),不對使用的系統(tǒng)有太強(qiáng)的侵入性;

    ? ? 3、細(xì)粒度:重試以方法為單位,而不用重試整個(gè)task;

    ?

    概要設(shè)計(jì):

    ? ? ?要保持重試模塊的獨(dú)立性,不侵入到原有的系統(tǒng)中,首先的面臨的問題是,需要重試的數(shù)據(jù)從何而來,我們很容易想到DB,那么整個(gè)系統(tǒng)應(yīng)該分為兩大塊,一是client,負(fù)責(zé)將需要重試的數(shù)據(jù)放入DB,這個(gè)也就是各個(gè)應(yīng)用程序要做的事情;第二是server,負(fù)責(zé)將DB中數(shù)據(jù)取出,做相應(yīng)的處理;大家可以看到這是一個(gè)典型的生產(chǎn)者-消費(fèi)者模式;

    ?

    ?


    ?

    這里區(qū)分應(yīng)用程序和容錯(cuò)服務(wù)器,只是概念上的,因?yàn)槿蒎e(cuò)服務(wù)器事實(shí)上必須依賴于引用程序(需要執(zhí)行部分引用程序);所以在實(shí)際應(yīng)用中,一般在一臺(tái)虛擬機(jī)上,如果是應(yīng)用本身在多臺(tái)服務(wù)器上的話,可以通過配置項(xiàng)決定是否啟用容錯(cuò)重試功能;

    ?

    詳細(xì)設(shè)計(jì)-UML圖:

    ?

    ?

    1、系統(tǒng)的UML類圖


    ?

    2、類圖說明:

    ?

    ? ?1)、從上圖可以看出系統(tǒng)主要可以分為三大塊的內(nèi)容,第一塊是TaskExecutor 類以上部分,通過spring的TaskExecutor與下面的模塊弱依賴,這一塊主要負(fù)責(zé)從數(shù)據(jù)庫去取出需要處理的、并且應(yīng)該自己(loadbalance)處理的數(shù)據(jù);講數(shù)據(jù)封裝在一個(gè)Notify中,交給NotifyServerServices處理;

    ? ?2)、第二塊為TaskExecutor和RetryHandlerStrategy之前的部分;這一部分主要關(guān)注容錯(cuò)重試處理后的工作;如:成功則刪除DB中的記錄,否則,負(fù)責(zé)判斷是否還需要重試,間隔時(shí)間等;

    ? ?3)、第三塊為每個(gè)系統(tǒng)自己實(shí)現(xiàn)的各種RetryHandlerStrategy類;他們負(fù)責(zé)真正的重試工作;這里所有的類,可以看成是一個(gè)sever,對于client端來說,是非常簡單的,因?yàn)樗恍枰v數(shù)據(jù)插入到數(shù)據(jù)庫,可以通過一個(gè)clientService提供一個(gè)createNotify方法,供應(yīng)用調(diào)用;

    ?

    詳細(xì)設(shè)計(jì)-數(shù)據(jù)庫設(shè)計(jì):

    ?

    1、一共需要兩張表,task、task_history;兩張字段完全一樣:

    ?

    字段 類型 描述 可空 默認(rèn)值
    task_id varchar2 PK、UUID NOT
    create_time DATE 創(chuàng)建時(shí)間 NOT
    handle_time DATE 任務(wù)待執(zhí)行的時(shí)間 NOT
    task_handler varchar2 任務(wù)處理器類型 NOT
    load_balance_num number 負(fù)責(zé)均衡值 NOT 0
    task_parameter varchar2 執(zhí)行任務(wù)的參數(shù),json格式 YES
    retry_count number 已經(jīng)重試的次數(shù) NOT 0
    retry_reason varchar2 失敗的原因 YES

    ?

    說明:

    1)、load_balance_num:當(dāng)使用集群的時(shí)候可以考慮,可以通過上圖的LoadBalanceNumStrategy類來控制他的值,比如平均分布,比如按照機(jī)器的性能使用權(quán)重分布;

    2)、task_parameter:這個(gè)用來保存重試的參數(shù),可以約定為一種格式,自己方便解析就好了,比如json、xml等;

    3)、retry_count:當(dāng)系統(tǒng)要求我最多重試5次的時(shí)候可以使用這個(gè)參數(shù);當(dāng)5次后還是失敗,直接移動(dòng)要?dú)v史表中,人工處理;

    4)、handle_time:當(dāng)然系統(tǒng)要求第二次重試的時(shí)候時(shí)間間隔30分鐘的時(shí)候使用;當(dāng)處理失敗的時(shí)候更新這個(gè)時(shí)間;

    5)、task_handler:任務(wù)處理器類型,比如上面類圖中的RetryHandler,通過spring,得到RetryHandler的實(shí)例來做處理;

    ?

    關(guān)鍵類偽代碼:

    ?

    從上面的設(shè)計(jì)圖可以發(fā)現(xiàn)主要只有兩個(gè)類,即是:NotifyScheduleMainExecutor和NotifyServerServiceImpl,其他的都是一些策略類;這里偽代碼描述這個(gè)兩個(gè)類的邏輯,策略類可以自己選擇不同的實(shí)現(xiàn);

    ?

    1、NotifyScheduleMainExecutor:

    ?

    ?

    if(NotifyHandlerStrategy != null){
      獲取本機(jī)待處理的handler的列表;
    }
    if(LoadBalanceNumStrategy != null){
      獲取本機(jī)待處理的loand_balance_num的列表
    }
    if(NotifyMaxNumPerJobStrategy != null){
      獲取本機(jī)每次調(diào)度的處理的最大的notify記錄數(shù)
    }
    執(zhí)行輪詢語句,提取待處理的任務(wù)的列表
    for(對每一個(gè)notify){
      if(NotifyIdCacheStrategy已經(jīng)包含該ID){
         說明線程已經(jīng)在執(zhí)行,  
       }else{
          放入cache;
          TaskExecutor.excute(new notifyExecutor(notify,notifyServerService))
       }
    }

    ? 分析一下這里的查詢sql:

    ?

    基礎(chǔ)的sql =  select * from notify where handle_time <=sysdate ;
    if(handlerlist 不為空)
    {
      sql+=sql+ and hander in (handlerlist)
    } 
    if(loadbalancenumlist不為空)
    {
      sql+=sql+ and load_balance_num in (loadbalancenumlist)
    } 
    if(maxnum不為空)
    {
      sql+=sql+ and rownum<=maxnum
    } 

    ?

    2、?NotifyServerServiceImpl

    ?

    處理結(jié)果=success;
    errormessage=null;
    根據(jù)notify的task_handler得到處理的handler;
    try{
        handler.invoke(notify.getparameter())返回notifyHandlerResult
        if(notifyHandlerResult == null){
           throw exception;
        }else if(notifyHandlerResult==失敗){
            處理結(jié)果=fail;
            errormessage=原因;
        }
    }cath(){
        使用NotifyHandlerExceptionStrategy處理;返回notifyHandlerResult
        if(notifyHandlerResult == null){
             處理結(jié)果=exception;
            errormessage=原因;
        }else if(notifyHandlerResult==失敗){
            處理結(jié)果=fail;
            errormessage=原因;
        }
    }
    try{
        if(notifyHandlerResult=success){
            清除DB的數(shù)據(jù);
        }else{
            得到已經(jīng)重試的次數(shù)oldRetryCount;
            得到上一次執(zhí)行的時(shí)間oldExecuteTime;
            根據(jù)NotifyRetryStrategy類返回重試策略的結(jié)果 notifyRetryResult;
            if(需要重試){
                 重試次數(shù)+1;
                 計(jì)算下一次時(shí)間;
                 設(shè)置上一次失敗原因;
                 更新DB;
            }else{
                 移動(dòng)到歷史表中;
            }    
       }
    }cath{
       
    }finally{
        cache的操作;
    }
    
    

    ?

    使用及client配置:

    ? ? 現(xiàn)在假設(shè)有一個(gè)應(yīng)用需要使用容錯(cuò)機(jī)制,需要的操作:

    1、引入二方庫;

    2、在需要容錯(cuò)的方法里面調(diào)用clientService提供的createNotify方法,插入項(xiàng)目的數(shù)據(jù);

    3、編寫重試處理類;必須繼承RetryHandlerStrategy接口;

    4、編寫配置文件:整個(gè)系統(tǒng)依賴spring,可以分為三個(gè)配置文件,一個(gè)是client,一個(gè)是server,另外是handler,下面給出一個(gè)例子:

    ?

    ?

    client.xml

    ?

    <?xml version="1.0" encoding="UTF-8"?>
    <beans>
    	<bean id="notifyClientService" class="com.*.service.impl.NotifyClientServiceImpl">
    		<!-- notify.load_balance_num字段值生成、以及調(diào)度時(shí)where條件中取值的策略實(shí)現(xiàn)類,可自行擴(kuò)展 -->
    		<!-- 當(dāng)有多臺(tái)notify服務(wù)器時(shí)才有用,用于平衡各臺(tái)server間的壓力;一般不用配置 -->
    		<property name="loadBalanceNumStrategy" ref="alternateLoadBalanceNumStrategy" />
    		<property name="notifyDao" ref="notifyDao" />
    	</bean>
    
    	<!-- 生成0、1交替的LOAD_BALANCE_NUM值,適用于2臺(tái)Notify服務(wù)器 -->
    	<bean id="alternateLoadBalanceNumStrategy" class="com.*.strategy.impl.AlternateLoadBalanceNumStrategyImpl">
    		<!-- 主機(jī)名對應(yīng)的LOAD_BALANCE_NUM列表,多個(gè)用,隔開 -->
    		<property name="lbnMapByHostname">
    			<map>
    				<entry key=“dev1" value="0"/>
    				<entry key="dev2" value="1"/>
    			</map>
    		</property>
    	</bean>
    	
    	<bean id="notifyDao" class="com.*.dao.impl.NotifyDaoImpl">
    		<!-- ref可以修改為自己應(yīng)用中已經(jīng)配置過的sqlMapClientTemplate bean,要求內(nèi)部已經(jīng)嵌入datasource -->
    		<property name="sqlMapClientTemplate" ref="sqlMapTemplate" />
    		<property name="namespace" value="com.*.notify" />
    	</bean>
    </beans>
    ?

    server.xml

    ?

    ?

    <?xml version="1.0" encoding="UTF-8"?>
    <beans >
    	<!-- 該文件是Notify Server運(yùn)行時(shí)需要的配置文件,加載了該文件,調(diào)度就會(huì)自動(dòng)執(zhí)行; -->
    	<!-- 若Client/Server都在同一個(gè)應(yīng)用中,則請?jiān)诓渴饡r(shí)區(qū)別加載該文件,否則會(huì)導(dǎo)致多臺(tái)服務(wù)器執(zhí)行相同的調(diào)度任務(wù) -->
    	
    	<!-- 添加對Notify Client的配置支持 -->
    	<import resource="billing-spring-notify-client.xml" />
    	<!-- end for Notify Client -->
    	
    	<!-- 任務(wù)從此處開始加載 -->
    	<bean id="notifySpringScheduledExecutorFactoryBean" class="org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean">
    		<property name="scheduledExecutorTasks">
    			<list>
    				<ref bean="retryScheduledExecutorTask" />
    			</list>
    		</property>
    	</bean>
    
    	<!-- 容錯(cuò)任務(wù) -->
    	<bean id="retryScheduledExecutorTask" class="org.springframework.scheduling.concurrent.ScheduledExecutorTask">
    		<property name="runnable" ref="retryScheduledMainExecutor" />
    		<!-- 初次執(zhí)行任務(wù)delay時(shí)間,單位為ms,默認(rèn)值為0,代表首次加載任務(wù)時(shí)立即執(zhí)行;比如1min -->
    		<property name="delay" value="5000" />
    		<!-- 間隔時(shí)間,單位為ms,默認(rèn)值為0,代表任務(wù)只執(zhí)行一次;比如2min -->
    		<property name="period" value="1000" />
    		<!-- 是否采用fixedRate方式進(jìn)行任務(wù)調(diào)度,默認(rèn)為false,即采用fixedDelay方式 -->
    		<!-- fixedRate:定時(shí)間隔執(zhí)行,不管上次任務(wù)是否已執(zhí)行完畢;fixedDelay:每次任務(wù)執(zhí)行完畢之后delay固定的時(shí)間 -->
    		<property name="fixedRate" value="true" />
    	</bean>
    	
    	<!-- 容錯(cuò)主線程 -->
    	<bean id="retryScheduledMainExecutor" class="com.*.NotifyScheduledMainExecutor">
    		<!-- 針對Notify服務(wù)端的Service,用于更新Notify重試信息等 -->
    		<property name="notifyServerService" ref="notifyServerService" />
    		<!-- notify.notifyId緩存策略實(shí)現(xiàn)類,可自行擴(kuò)展 -->
    		<property name="notifyIdCacheStrategy" ref="defaultNotifyIdCacheStrategy" />
    		<!-- notify.load_balance_num字段值生成、以及調(diào)度時(shí)where條件中取值的策略實(shí)現(xiàn)類,可自行擴(kuò)展 -->
    		<!-- 當(dāng)有多臺(tái)notify服務(wù)器時(shí)才有用,用于平衡各臺(tái)server間的壓力;一般不用配置 -->
    		<!-- <property name="loadBalanceNumStrategy" ref="alternateLoadBalanceNumStrategy" /> -->
    		<!-- notify.handler字段值在調(diào)度時(shí)where條件中取值的策略實(shí)現(xiàn)類,可自行擴(kuò)展 -->
    		<!-- 當(dāng)有多臺(tái)notify服務(wù)器時(shí)才有用,用于表明某臺(tái)server可執(zhí)行哪些handler;一般不用配置 -->
    		<property name="notifyHandlerStrategy" ref="handerNameByRetry" />
    		<!-- 當(dāng)有多臺(tái)notify服務(wù)器時(shí)才有用,用于設(shè)置某臺(tái)server調(diào)度時(shí)每次讀取的Notify最大數(shù),用于覆蓋maxNum;一般不用配置 -->
    		<!-- <property name="notifyMaxNumPerJobStrategy" ref="defaultNotifyMaxNumPerJobStrategy" /> -->
    		<!-- 用于并發(fā)的線程池 -->
    		<property name="notifyTaskExecutor" ref="syncTaskExecutor" />
    		<!-- 每次調(diào)度讀取的Notify最大記錄數(shù),默認(rèn)為1000 -->
    		<property name="maxNum" value="1000" />
    		<property name="notifyDao" ref="notifyDao" />
    	</bean>
    	
    	<!-- 同步處理 -->
    	<bean id="syncTaskExecutor" class="org.springframework.core.task.SyncTaskExecutor">
    	</bean>
    	
    	<bean id="notifyServerService" class="com.*.impl.NotifyServerServiceImpl">
    		<!-- 針對任務(wù)執(zhí)行失敗后Notify如何重試的策略實(shí)現(xiàn)類,可自行擴(kuò)展 -->
    		<property name="notifyRetryStrategy" ref="defaultNotifyRetryStrategy" />
    		<!-- 針對任務(wù)執(zhí)行失敗后異常處理策略實(shí)現(xiàn)類,可自行擴(kuò)展 -->
    		<!-- 默認(rèn)不對異常進(jìn)行補(bǔ)救,具體handler實(shí)現(xiàn)類中若返回NULL或拋出異常,則均按異常處理,直接將Notify記錄遷移到歷史表中,不進(jìn)行重試; -->
    		<!-- <property name="notifyHandlerExceptionStrategy" ref="defaultNotifyHandlerExceptionStrategy" /> -->
    		<!-- 描述見notifyScheduledMainExecutor -->
    		<property name="notifyIdCacheStrategy" ref="defaultNotifyIdCacheStrategy" />
    		<!-- 事務(wù)模板,需保證能夠找到對應(yīng)的bean -->
    		<property name="transactionTemplate" ref="transactionTemplate" />
    		<property name="notifyDao" ref="notifyDao" />
    	</bean>
    
    	<!-- 以下幾個(gè)default*的bean為系統(tǒng)提供的默認(rèn)實(shí)現(xiàn),若有需要,可自行擴(kuò)展,但必須實(shí)現(xiàn)相應(yīng)接口 -->
    	<bean id="defaultNotifyIdCacheStrategy" class="com.*.DefaultNotifyIdCacheStrategyImpl" />
    	<bean id="defaultNotifyHandlerExceptionStrategy" class="com*.impl.DefaultNotifyHandlerExceptionStrategyImpl" />
    	<!--容錯(cuò)handler-->
    	<bean id="handerNameByRetry" class="com.*.asyn.HandlerNameFilter">
    		<property name="handerNames">
    			<list>
    				<value>retryHandler</value>
    			</list>
    		</property>
    	</bean>
    	
    	<bean id="defaultNotifyMaxNumPerJobStrategy" class="com.*.DefaultNotifyMaxNumPerJobStrategyImpl">
    		<!-- 主機(jī)名對應(yīng)的每次調(diào)度讀取Notify記錄的最大值 -->
    		<property name="maxNumPerJobMapByHostname">
    			<map>
    				<entry key="dev1" value="500"/>
    				<entry key="dev2" value="800"/>
    			</map>
    		</property>
    	</bean>
     	<bean id="defaultNotifyRetryStrategy" class="com.*.DefaultNotifyRetryStrategyImpl">
    		<!-- 任務(wù)執(zhí)行失敗之后每次重試的間隔ms數(shù) -->
    		<property name="retryIntervals">
    			<list>
    				<!-- 依次為第一次間隔1min,第二次5min,第三次10min,第四次30min,第五次1h -->
    				<value>60000</value>
    				<value>300000</value>
    				<value>600000</value>
    				<value>1800000</value>
    				<value>3600000</value>
    			</list>
    		</property>
    	</bean>
    	<!-- end default* -->
    </beans>

    handler.xml

    ?

    <?xml version="1.0" encoding="UTF-8"?>
    <beans >
    	
    	<bean id="retryHandler" class="com.*asyn.RetryHandler" />
    </beans>
    ?

    總結(jié):

    現(xiàn)在回去看系統(tǒng)的目標(biāo)實(shí)現(xiàn)情況:

    1、通用性:整個(gè)模塊對應(yīng)用系統(tǒng)的侵入性是很小了,可以打包為一個(gè)二方庫,在公司范圍的使用;對于應(yīng)用來說只增加幾個(gè)配置文件,在需要重試的地方,通過通過接口,完全于模塊解耦;

    2、輕量級:很明顯,模塊只是依賴spring,

    3、細(xì)粒度:在上面的設(shè)計(jì)中,并沒有特別強(qiáng)調(diào)細(xì)粒度,是因?yàn)閷τ谶x擇多大粒度完全由應(yīng)用自己決定,應(yīng)用在自己的重試實(shí)現(xiàn)類和方法之間平衡,對模塊來講,沒有任何限制;

    ?

    ?






    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 亚洲人成人无码网www电影首页| 免费女人18毛片a级毛片视频| 亚洲无人区码一二三码区别图片| a在线观看免费视频| 亚洲一区爱区精品无码| 巨胸狂喷奶水视频www网站免费| 亚洲性久久久影院| 韩国免费a级作爱片无码| 久久久久国产亚洲AV麻豆 | 日韩高清在线免费观看| 亚洲妇女熟BBW| 免费看无码自慰一区二区| 亚洲精华国产精华精华液网站| 成人黄动漫画免费网站视频 | 57PAO成人国产永久免费视频| 亚洲成aⅴ人片在线观| 日本XXX黄区免费看| 亚洲熟妇无码av另类vr影视| 成人五级毛片免费播放| 精品国产亚洲第一区二区三区| 人人狠狠综合久久亚洲高清| 亚洲另类激情综合偷自拍| 四虎在线最新永久免费| 亚洲日产乱码一二三区别 | 区久久AAA片69亚洲| 国产成人AV免费观看| 亚洲黄色片在线观看| 无码高潮少妇毛多水多水免费| 亚洲国产一区二区三区在线观看| 亚洲精品无码成人片在线观看| 无码人妻精品中文字幕免费| JLZZJLZZ亚洲乱熟无码| 久久国产乱子伦精品免费不卡| 亚洲精品国产精品乱码不99| 曰批全过程免费视频网址| 91在线亚洲综合在线| 亚洲精品和日本精品| 日本免费一区二区在线观看| 污视频网站在线免费看| 色婷婷六月亚洲婷婷丁香| 57pao国产成永久免费视频|