Java秒殺系統(tǒng)(十一):定時(shí)任務(wù)補(bǔ)充處理超時(shí)未支付的訂單

作者: 修羅debug
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。



摘要:本篇博文是“Java秒殺系統(tǒng)實(shí)戰(zhàn)系列文章”的第十一篇,本篇博文我們將借助定時(shí)任務(wù)調(diào)度組件來(lái)輔助“失效超時(shí)未支付的訂單記錄”的處理,用以解決上篇博文中采用“RabbitMQ死信隊(duì)列失效處理超時(shí)未支付的訂單”的瑕疵!

內(nèi)容:上篇文章我們介紹了如何采用消息中間件RabbitMQ的死信隊(duì)列失效處理超時(shí)未支付的訂單,實(shí)戰(zhàn)完畢之后,相信各位小伙伴對(duì)死信隊(duì)列應(yīng)該有了一個(gè)初步的認(rèn)識(shí)以及使用。在該業(yè)務(wù)場(chǎng)景中,雖然死信隊(duì)列可以“近乎完美”地解決那樣的需求,但是卻仍然存在著一點(diǎn)瑕疵,即 “當(dāng)許多訂單記錄恰好在某個(gè)TTL集中失效,準(zhǔn)備交由RabbitMQ的死信隊(duì)列進(jìn)行處理時(shí),此時(shí)恰好RabbitMQ的服務(wù)掛掉了。。?!?/span>

RabbitMQ的服務(wù)突然掛掉,將意味著在那個(gè)時(shí)候那些本該被失效處理的訂單記錄卻遲遲沒(méi)有得到處理(因?yàn)镽abbitMQ已經(jīng)不提供服務(wù)了?。m然后面重啟了RabbitMQ服務(wù),消息也重新進(jìn)行了處理,但是卻有可能在這段時(shí)間內(nèi)用戶“重新下不了單”或者其他一些意想不到的問(wèn)題!

各位小伙伴看到這里,估計(jì)應(yīng)該明白個(gè)大概了!雖然會(huì)抱怨“他娘的,無(wú)緣無(wú)故,RabbitMQ的服務(wù)咋會(huì)掛掉”!抱怨歸抱怨,現(xiàn)實(shí)卻仍舊是現(xiàn)實(shí),隱患仍然是存在的,那既然存在隱患,那就得找尋一些方法來(lái)解決。

本篇博文我們將采用“定時(shí)器”或者叫“定時(shí)任務(wù)調(diào)度”的方式來(lái)輔助處理,即我們會(huì)設(shè)定一下Cron時(shí)間,定時(shí)在數(shù)據(jù)庫(kù)中輪詢獲取status = 0(即狀態(tài)為未支付)的訂單,并更新失效掉那些時(shí)間已經(jīng)超過(guò)了TTL的訂單?。╯et status = -1)。下面我們進(jìn)入代碼實(shí)戰(zhàn)環(huán)節(jié):

(1) 定時(shí)器的實(shí)現(xiàn)我們暫且采用一個(gè)比較簡(jiǎn)單的方式,即@Scheduled注解來(lái)實(shí)現(xiàn),我們將所有的簡(jiǎn)單定時(shí)器的實(shí)現(xiàn)都放在SchedulerService服務(wù)類中,其完整源代碼如下所示:

@Service
public class SchedulerService {
private static final Logger log= LoggerFactory.getLogger(SchedulerService.class);

@Autowired
private ItemKillSuccessMapper itemKillSuccessMapper;

@Autowired
private Environment env;

//定時(shí)獲取status=0的訂單并判斷是否超過(guò)TTL,然后進(jìn)行失效
@Scheduled(cron = "0 0/30 * * * ?")
public void schedulerExpireOrders(){
try {
List<ItemKillSuccess> list=itemKillSuccessMapper.selectExpireOrders();
if (list!=null && !list.isEmpty()){
//java8的寫(xiě)法
list.stream().forEach(i -> {
if (i!=null && i.getDiffTime() > env.getProperty("scheduler.expire.orders.time",Integer.class)){
itemKillSuccessMapper.expireOrder(i.getCode());
}
});
}
}catch (Exception e){
log.error("定時(shí)獲取status=0的訂單并判斷是否超過(guò)TTL,然后進(jìn)行失效-發(fā)生異常:",e.fillInStackTrace());
}
}
}

其中的遍歷,Debug是采用了Java8的Steam API。上述這種定時(shí)任務(wù)調(diào)度的寫(xiě)法簡(jiǎn)單粗暴,而且還快?。ㄊ郎衔涔o(wú)堅(jiān)不摧、無(wú)快不破!)但是,快歸快,在這里還是需要提一點(diǎn)注意的地方。

(2) 即如果在該項(xiàng)目有多個(gè)這樣的定時(shí)任務(wù),那么有一點(diǎn)值得注意的是:多個(gè)定時(shí)任務(wù)的執(zhí)行時(shí)間Cron如果挨得很近,那么效率一定是瓶頸,這是因?yàn)槿绻皇巧厦婺菢訉?xiě),那么始終只會(huì)有一個(gè)、單一的線程在執(zhí)行上面的定時(shí)任務(wù)!

試想一下,如果有定時(shí)任務(wù)A、B、C都是每隔5分鐘執(zhí)行一次,那么很明顯,將會(huì)有一些定時(shí)任務(wù)處于阻塞的狀態(tài)而遲遲不能在規(guī)定的時(shí)間內(nèi)執(zhí)行完!

因此,為了解決此種“痛點(diǎn)”,我們需要加入一些自定義的配置,即針對(duì)“定時(shí)任務(wù)調(diào)度”我們加入“線程池”的配置,其完整的源代碼如下所示,各位小伙伴可以寫(xiě)多幾個(gè)定時(shí)器,然后觀察控制臺(tái)的輸出信息,即可看到每個(gè)不同的定時(shí)任務(wù)采用了不同的線程執(zhí)行。

/**
* 定時(shí)任務(wù)多線程處理的通用化配置
* @Author:debug (SteadyJack)
* @Date: 2019/6/29 21:45
**/
@Configuration
public class SchedulerConfig implements SchedulingConfigurer{
//針對(duì)定時(shí)任務(wù)調(diào)度-配置多線程(線程池)
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
}
}

(3) 至此,關(guān)于定時(shí)任務(wù)輔助失效處理秒殺系統(tǒng)中“超時(shí)未支付的訂單記錄”的代碼已經(jīng)實(shí)戰(zhàn)完畢了!可以調(diào)整一下Cron的取值,然后將整個(gè)系統(tǒng)運(yùn)行在外置的Tomcat服務(wù)器,稍等片刻即可看到定時(shí)任務(wù)的執(zhí)行了,在這里就不貼圖了!  

補(bǔ)充:

1、目前,這一秒殺系統(tǒng)的整體構(gòu)建與代碼實(shí)戰(zhàn)已經(jīng)全部完成了,該秒殺系統(tǒng)對(duì)應(yīng)的視頻教程的鏈接地址為:https://www.fightjava.com/web/index/course/detail/6,可以點(diǎn)擊鏈接進(jìn)行試看以及學(xué)習(xí),實(shí)戰(zhàn)期間有任何問(wèn)題都可以留言或者與Debug聯(lián)系、交流!

2、另外,Debug也開(kāi)源了該秒殺系統(tǒng)對(duì)應(yīng)的完整的源代碼以及數(shù)據(jù)庫(kù),其地址可以來(lái)這里下載:https://gitee.com/steadyjack/SpringBoot-SecondKill 記得Fork跟Star?。。。?/span>

3、最后,不要忘記了關(guān)注一下Debug的技術(shù)微信公眾號(hào):