轉(zhuǎn)至元數(shù)據(jù)結(jié)尾
轉(zhuǎn)至元數(shù)據(jù)起始
- 主流程不依賴于通知返回結(jié)果,即通知失敗與否不影響主流程的完成
- 通知應(yīng)該在主流程完成之后再發(fā)出,需要避免出現(xiàn)主流程回滾而通知已經(jīng)被發(fā)出的情況
- 通知需要被可靠送達(dá),任何情況下不應(yīng)該存在通知沒(méi)有被送達(dá)的情況存在
- 接收方具有消息冪等性,即我們只關(guān)注通知是否被送達(dá),對(duì)于通知是否可能被重復(fù)發(fā)送,我們不做保證,通知的接收方需要采取措施保證冪等性
- 為了不影響主流程的完成,我們應(yīng)該在主流程事務(wù)提交之后再發(fā)送消息。但是通常這一點(diǎn)在程序中處理比較困難,比如要在Action中處理,相關(guān)的參數(shù)需要從service層傳輸?shù)絚ontroller層
- 主流程一旦完成就必須確保消息要被發(fā)出,不能允許主流程事務(wù)已經(jīng)提交而通知沒(méi)有被觸發(fā)的情況。很多意外情況可能導(dǎo)致主流程已經(jīng)提交而通知沒(méi)有發(fā)出,比如服務(wù)器突然宕機(jī),網(wǎng)絡(luò)出現(xiàn)問(wèn)題,數(shù)據(jù)庫(kù)連接不上,數(shù)據(jù)庫(kù)宕機(jī)等
- 如果發(fā)送失敗應(yīng)該有可以定時(shí)重發(fā)的機(jī)制。這就需要開發(fā)定時(shí)任務(wù)及將通知記錄到相關(guān)的存儲(chǔ)介質(zhì)中以便可以定時(shí)重發(fā),如果重試多次失敗還需要報(bào)警等。如果這樣的邏輯每次都要開發(fā)一遍,將造成許多重復(fù)的工作量。并且每個(gè)這樣的通知接口就設(shè)計(jì)一張表,一個(gè)定時(shí)任務(wù)也不可行,會(huì)造成系統(tǒng)難以維護(hù)
- 定時(shí)任務(wù)觸發(fā)的通知發(fā)送和主流程觸發(fā)的定時(shí)發(fā)送還可能存在沖突,需要采用樂(lè)觀鎖等機(jī)制以避免無(wú)謂的重復(fù)發(fā)送通知
- 通過(guò)使用Spring的TransactionSynchronizationManager將具體的通知操作延遲到當(dāng)前事務(wù)提交后才執(zhí)行
- 在主流程中將需要觸發(fā)的通知消息保存到消息表中,跟主流程在同一個(gè)事務(wù)中提交,保證只要主流程提交了,通知消息就被持久化了
- 使用定時(shí)任務(wù)定時(shí)檢查消息表中是否還存在未發(fā)出的消息,如果存在則根據(jù)重發(fā)策略(如最大重發(fā)次數(shù),重發(fā)間隔等)進(jìn)行重發(fā)
- 在發(fā)送消息時(shí)檢查表中狀態(tài)和發(fā)送次數(shù)的值是否發(fā)生改變,如果已經(jīng)發(fā)生改變說(shuō)明該次發(fā)送已經(jīng)被其他程序處理,則取消此次發(fā)送
- 將消息存儲(chǔ)在統(tǒng)一的通知消息表中,消息的參數(shù)可以直接以json數(shù)據(jù)的格式存儲(chǔ)到消息字段中。通知消息的重發(fā)策略,報(bào)警觸發(fā)條件,具體發(fā)送類則統(tǒng)一存放到通知定義表中

- 表設(shè)計(jì)
1. solid_notify_def(通知定義表) Field | Type | Comment |
|---|
| id | bigint(20) NOT NULL | 自增主鍵 | | name | varchar(40) NOT NULL | 通知名稱 | | impl_class | varchar(400) NOT NULL | 通知的實(shí)現(xiàn)類 | | method | varchar(20) NOT NULL | 通知的方法名稱 | | max_retry_times | int(11) NOT NULL | 最大重試次數(shù),-1表示unlimited | | max_retry_interval | int(11) NOT NULL | 最大重試間隔,以分鐘為單位 | | alert_trigger_type | tinyint(4) NOT NULL | 報(bào)警觸發(fā)類型,0 - 最終失敗, 1 - 失敗, 2 - NEVER,3 - 失敗若干次后報(bào)警 | | email | varchar(400) NOT NULL | 調(diào)用失敗后通知人郵件,注意調(diào)用失敗是指經(jīng)過(guò)若干次重試后超過(guò)最大重試次數(shù)后失敗,逗號(hào)分隔 | | description | varchar(400) NULL | 對(duì)該通知定義的描述 | | retry_interval | varchar(400) NOT NULL | 重試間隔,可為固定長(zhǎng)度或列表或策略類或表達(dá)式,s - 秒,m - 分鐘,h - 小時(shí), d - 天 | | alert_trigger_fail_times | int(11) NULL | 失敗多少次后觸發(fā)報(bào)警 | | need_confirm | tinyint(4) NOT NULL | 是否需要確認(rèn),0 - 不需要, 1 - 需要 | | max_confirm_delay | int(11) NOT NULL | 在需要確認(rèn)時(shí)經(jīng)過(guò)多少時(shí)間后認(rèn)為調(diào)用失敗,以秒為單位,0表示無(wú)限制等下去 |
2. solid_notify_msg(通知消息表) Field | Type | Comment |
|---|
| id | bigint(20) NOT NULL | 自增主鍵 | | def_id | bigint(20) NOT NULL | 通知定義id | | param | text NULL | 通知參數(shù) | | description | text NULL | 描述信息,如該通知的一些上下文信息 | | errors | text NULL | 調(diào)用失敗記錄的錯(cuò)誤信息 | | status | tinyint(4) NOT NULL | 該通知的狀態(tài),0 - 未發(fā)送, 1 - 發(fā)送成功, 2 - 發(fā)送失敗, 3 - 發(fā)送最終失敗,4 - 待確認(rèn) | | retry_times | int(11) NOT NULL | 重試次數(shù) | | last_call_time | datetime NULL | 最后調(diào)用時(shí)間 | | nex_call_time | datetime NULL | 下次執(zhí)行時(shí)間 | | create_time | datetime NOT NULL | 該通知最初創(chuàng)建時(shí)間 |
3. solid_notify_msg_history(通知消息歷史表) 和通知消息表表結(jié)構(gòu)完全一樣,歷史表主要用來(lái)將已經(jīng)是成功狀態(tài)或者最終失敗狀態(tài)的消息從通知消息表中移除,以免通知消息表中積攢太多消息影響性能。
- 類設(shè)計(jì)

其中addNotice方法實(shí)現(xiàn)如下:
@Override @Transactional public SolidNotifyMsgWithBLOBs addNotice(String defName, String param, String description) { final SolidNotifyMsgWithBLOBs solidNotifyMsgWithBLOBs = new SolidNotifyMsgWithBLOBs(); SolidNotifyDef solidNotifyDef = getDefByName(defName); solidNotifyMsgWithBLOBs.setDefId(solidNotifyDef.getId()); solidNotifyMsgWithBLOBs.setParam(param); solidNotifyMsgWithBLOBs.setDescription(description); solidNotifyMsgWithBLOBs.setCreateTime(new Date()); solidNotifyMsgWithBLOBs.setStatus(SolidNotifyMsgStatus.NOT_SEND.getValue()); solidNotifyMsgWithBLOBs.setRetryTimes(0); solidNotifyMsgWithBLOBs.setNexCallTime(Calendar.getInstance().getTime()); solidNotifyMsgMapper.insert(solidNotifyMsgWithBLOBs); TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { SolidNotifyDef solidNotifyDef = solidNotifyDefMapper.selectByPrimaryKey(solidNotifyMsgWithBLOBs.getDefId()); sendNotice(solidNotifyMsgWithBLOBs, solidNotifyDef); } }); return solidNotifyMsgWithBLOBs; } - 時(shí)序圖

使用方法
1. 在服務(wù)方法中需要通知外部系統(tǒng)的地方調(diào)用SolidNotifyService::addNotice方法,并將參數(shù)轉(zhuǎn)化成json格式 2. 在服務(wù)類或其他類中添加通知回調(diào)方法,在回調(diào)方法中實(shí)際調(diào)用外部系統(tǒng),若調(diào)用失敗則拋出異常,調(diào)用者會(huì)自動(dòng)處理該異常并更新對(duì)應(yīng)的通知消息記錄狀態(tài),注意回調(diào)方法只能有一個(gè)SolidNofityMsgWithBLOBs類型的參數(shù) 3. 在solid_notify_def表中增加一條記錄,注意如果是服務(wù),對(duì)應(yīng)的impl_class填寫的應(yīng)該是接口名而不是實(shí)現(xiàn)類名 應(yīng)用舉例1. 目前DspInfoServiceImpl::setDsp方法中需要同步相關(guān)dsp信息到運(yùn)管平臺(tái)(采用發(fā)送rabbitmq消息的方式),在該方法末尾調(diào)用了addNotice方法: solidNotifyService.addNotice("syncToOmp", JSONObject.toJSONString(dspInfo), "");其中"syncToOmp"為該通知定義的名稱 2. 并增加方法DspInfoServiceImpl::syncToOmp作為回調(diào)方法:
@Override public void syncToOmp(SolidNotifyMsgWithBLOBs solidNotifyMsgWithBLOBs) { try { DspInfo dspInfo = JSONObject.parseObject(solidNotifyMsgWithBLOBs.getParam(), DspInfo.class); sendToOmp(dspInfo); } catch (Exception e) { logger.error("syncToOmp failed", e); throw new NexException("syncToOmp failed", e); } } 3. 在solid_notify_def表中增加了一條記錄:
id | name | impl_class | method | max_retry_times | max_retry_interval | alert_trigger_type | email | description | retry_interval |
|---|
| 1 | syncToOmp | com..b.nex.service.dspinfo.DspInfoService | syncToOmp | 2 | -1 | 0 | bjlihaiwu@corp. | 同步dsp信息到運(yùn)管平臺(tái) | 5m |
注意事項(xiàng)與常見(jiàn)問(wèn)題1. solid_notify_msg中的description字段用于存儲(chǔ)一些上下文信息,可為空。比方說(shuō)在執(zhí)行過(guò)程中的一些結(jié)果信息希望存儲(chǔ)到description中 2. 在回調(diào)方法中拋出異??梢宰屚ㄖ蝿?wù)以失敗結(jié)束,這時(shí)候會(huì)根據(jù)重試策略決定重試還是最終失敗。也可以主動(dòng)設(shè)置SolidNofityMsgWithBLOBs的status字段為最終失敗,此后該通知將不再發(fā)送并按照?qǐng)?bào)警策略決定是否觸發(fā)報(bào)警 3. retry_interval為數(shù)組的情況可以用于一些重試間隔不均勻的場(chǎng)合,比如第一次是5秒鐘后重試,第二次是5分鐘后重試,第三次是一個(gè)小時(shí)候重試,第四次為一天后重試,則可以定義為[5s, 5m, 1h, 1d],下次執(zhí)行時(shí)間會(huì)在當(dāng)前時(shí)間的基礎(chǔ)上加上對(duì)應(yīng)次數(shù)的重試間隔 4. 重試間隔目前還不支持表達(dá)式和策略類,稍后開發(fā) 5. 回調(diào)方法中拋出任何異常都不會(huì)導(dǎo)致主流程回滾 6. controller中如希望立即知道通知消息是否調(diào)用成功,可檢查addNotice方法返回的通知消息的狀態(tài),注意只能將原返回對(duì)象的引用直接傳遞回controller層再檢查,而不可在service層檢查或者復(fù)制,因?yàn)樵趕ervice調(diào)用結(jié)束后才會(huì)執(zhí)行回調(diào)并更新該返回對(duì)象 7. 對(duì)于需要確認(rèn)的消息,如給消息隊(duì)列發(fā)送消息并且需要發(fā)送發(fā)確認(rèn)或者消費(fèi)者確認(rèn)的功能,可以使用need_confirm為1,此時(shí)發(fā)送后消息進(jìn)入待確認(rèn)狀態(tài),只有收到確認(rèn)消息才能視為成功,max_confirm_delay設(shè)置為最大允許的等待確認(rèn)時(shí)間,超過(guò)這個(gè)時(shí)間將開始重新發(fā)送
|