大家在开发工程中,一般都使用过类似Mq的消息中间件产品,或者自己开发处理数据的定时任务。
它们一般的流程都是:每隔一段时间,去数据库获取有效的任务,然后执行,执行完成之后,删除任务或者将任务设置为失效。
那么这就可能存在一个潜在的风险:“雪崩效应”。
试想一下如下场景:我有个定时任务,每隔1s去数据库获取最早创建的并且有效的任务,然后执行任务。之所以获取最早创建的,是因为如果获取最新的任务,那么旧的任务可能就一直没机会执行了。
如果这个任务有问题,执行失败了,就会导致上游调度系统不断的获取到这条问题任务,不断的进行重试。
这会导致两个问题,第一,这个问题任务会一直占用计算资源,影响其他任务的正常执行。第二点,如果重试间隔时间很短,这个任务会产生雪崩效应,导致系统崩溃。
那么我们怎么避免呢?
最暴力的方法当然是,获取任务不是获取最早创建的,而是采用随机的方式。
但是,一般来说,我们还是希望任务能够尽量的顺序执行的,只是不希望被这样的问题任务给卡住,失败一次两次可能是网络的原因,但是失败多次,一般就是有错误了,就没必要不断的重试了,就需要人为的去处理。
所以,我们希望能做到:随着任务重试次数的增加,任务重试的机会越来越小。
那么,比较好的做法是:结合任务的重试次数,对重试时间进行非线性处理。让任务重试的间隔时间是刚开始是常数,然后是线性的,达到一定次数后变为非线性
具体的sql如下:
select * from task where valid=1 and update_time <= DATE_SUB(now(), INTERVAL (retry_count -1)*( retry_count -2)*(retry_count -3)+2*retry_count+1 SECOND) order by id asc limit 100
间隔时间t和重试次数r参考如下表达式:t(r) = a(r-1)*(r-2)*(r-3) + b*r + c
第一次r=0,所以间隔是常数c,后面三次重试,前面的多项式都是0,然后重试间隔是按照b线性增长的,之后就是三次方的非线性快速增长了,这样就优雅的实现了咱们的需求了。
或者是:新加个字段,next_retry_time,并建上索引,当任务失败时,在代码中通过上面的公式计算间隔时间,然后加上当前时间,写进next_retry_time,后面获取任务时候就改为:
select * from task where valid=1 and next_retry_time<now() order by id asc limit 100
战斗结束!!