【LongIntervalRetries】让我们来实现支付宝的异步回调方案
功能说明
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