功能說明
LongIntervalRetries是基於Quartz.Net
的一個長時間間隔重試的類庫,其主要解決何時執行以及執行結果反饋的問題。
產生的原因
簡單的說,我們提供了一系列的API供第三方調用,但因為實際API對應的業務處理時間較長,所以為了增加吞吐量,實際的業務邏輯並沒包含在API服務器,而是分布在了不同的服務器上進行處理,在業務處理結束后,再通過調用第三方提供的回調Url通知處理結果,為了保證回調正確,那么我們需要有這么一個策略:如果回調失敗,我們需要在指定時間間隔之后再次回調,如此反復直至回調成功或者達到重復次數上限。上述回調方案是不是很熟悉?嗯,好吧,直白的講,回調這一部分我們借鑒(抄襲)了支付寶支付時的回調方案,所以我們要解決的,就是通過代碼來實現回調這部分的業務場景,於是也就有了LongIntervalRetries
為什么不使用Polly
Polly是.NET基金會下的彈性和瞬態故障處理庫,其解決的問題天然符合我們回調的業務場景,但為什么在此處卻不被采用呢,原因如下:
- Polly的重試機制是個短時間內的機制,在其重試機制時間內,當前
Task
一般是通過await
阻塞的,而對於我們的場景來說,這明顯是不合適的,我們的場景並不應該發生在當前線程內 - Polly並不支持程序重啟時的重試恢復,這一點在我們的業務場景中及其重要,總不能服務器重啟后,我們還沒回調成功的業務就全部丟了吧
設計思路及演變
一開始我們的設計思路非常簡單,就是如何定時觸發回調這個業務代碼,但之后發現,為什么我們要僅限於回調呢?回調只是一個業務場景,但我們完全有可能有其它業務場景,恰恰我們也的確存在這樣的業務場景,我們需要向第三方服務商進行一些請求,該請求同樣耗時較長,該場景是不是很熟悉?只不過與我們作為服務商不同,該服務商居然沒提供回調方案,它需要我們自己定時去回調!而同樣是這個第三方,其業務請求參數具有相當的定制性,其需要我們預先做很多業務性的預處理,也就是需要做一些順序性的工作后,我們才能得到完整的請求參數。
於是我們的設計思路開始調整,最終得出該封裝應當具備的功能點:
- 其內部應該封裝掉如何定時觸發這個功能
- 其應該具備同時支持多種業務策略(策略模式)
- 其應該允許設置什么時候來觸發要執行的策略
- 其應該支持服務啟動時自動恢復未結束任務的能力
- 其應該具備最終執行結果通知的能力
為什么會考慮基於Quartz.Net
無論是定時觸發,還是業務策略,以及設置觸發時間,這些都很明顯的具備Job特性,而Quartz.Net
本身就是一個Job類庫,而且其本身允許進行並發線程數量設置,如果基於它,明顯我們可以不用考慮線程相關的問題,這可以省掉我們很大的工作量
類庫相關
該類庫在github上的地址為:https://github.com/fdstar/LongIntervalRetries
該類庫目前為v1.0.0版本,其nuget地址為:https://www.nuget.org/packages/LongIntervalRetries/
快速使用
此處僅是簡單的代碼示例,后續會有詳細的使用說明
首先我們需要聲明Job
public class SomeJob: IJob
{
public virtual Task Execute(IJobExecutionContext context)
{
return Task.FromResult(1);//默認LongIntervalRetries是通過Job是否產生異常來判斷是否執行成功的
}
}
然后我們可以將這個Job注冊到LongIntervalRetries
,同時設置重試策略,以及注冊事件監控執行結果,完整的示例如下
var retry = new StdRetry();
//聲明並注冊重試規則
string simpleRuleName = "SimpleRepeatRetryRule";
var simpleRepeatRule = new SimpleRepeatRetryRule(simpleRuleName, 5, TimeSpan.FromSeconds(2));
retry.RuleManager.AddRule(simpleRepeatRule);
var registerInfo = new RetryJobRegisterInfo
{
//指定要采用的重試規則,如果不設置,則默認使用已注冊的第一項
UsedRuleName = simpleRuleName,
//需要傳遞給IJob的上下文數據
JobMap = new Dictionary<string, object>
{
{"SomeKey","SomeValue" }
},
//開始執行時間,如果不指定則表示立刻執行
StartAt = DateTimeOffset.UtcNow.AddSeconds(3),
};
//注冊要執行的Job
retry.RegisterJob<SomeJob>(registerInfo);
//注冊每次Job執行后的通知事件
retry.RegisterEvent<SomeJob>(e =>
{//Some code
});
retry.Start();//啟動Quartz服務
//啟動服務后仍可以RegisterJob、RegisterEvent