新聞中心
在支付系統(tǒng)中,訂單通常是具有時效性的,例如在下單30分鐘后如果還沒有完成支付,那么就要取消訂單,不能再執(zhí)行后續(xù)流程。說到這,可能大家的第一反應(yīng)是啟動一個定時任務(wù),來輪詢訂單的狀態(tài)是否完成了支付,如果超時還沒有完成,那么就去修改訂單的關(guān)閉字段。當(dāng)然,在數(shù)據(jù)量小的時候這么干沒什么問題,但是如果訂單的數(shù)量上來了,那么就會出現(xiàn)讀取數(shù)據(jù)的瓶頸,畢竟來一次全表掃描還是挺費時的。

針對于定時任務(wù)的這種缺陷,關(guān)閉訂單的這個需求大多依賴于延時任務(wù)來實現(xiàn),這里說明一下延時任務(wù)與定時任務(wù)的最大不同,定時任務(wù)有執(zhí)行周期的,而延時任務(wù)在某事件觸發(fā)后一段時間內(nèi)執(zhí)行,并沒有執(zhí)行周期。
對于延時任務(wù),可能大家對于RabbitMQ的延時隊列會比較熟悉,用起來也是得心應(yīng)手,但是你是否知道使用Redis也能實現(xiàn)延時任務(wù)的功能呢,今天我們就來看看具體應(yīng)該如何實現(xiàn)。
使用Redis實現(xiàn)的延時隊列,需要借助Redisson的依賴:
org.redisson redisson-spring-boot-starter 3.10.7
首先實現(xiàn)往延時隊列中添加任務(wù)的方法,為了測試時方便,我們把延遲時間設(shè)為30秒。
- @Component
- public class UnpaidOrderQueue {
- @Autowired
- RedissonClient redissonClient;
- public void addUnpaid(String orderId){
- RBlockingQueue
blockingFairQueue = redissonClient.getBlockingQueue("orderQueue"); - RDelayedQueue
delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue); - System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+" 添加任務(wù)到延時隊列");
- delayedQueue.offer(orderId,30, TimeUnit.SECONDS);
- }
- }
添加一個對隊列的監(jiān)聽方法,通過實現(xiàn)CommandLineRunner接口,使它在springboot啟動時就開始執(zhí)行:
- @Component
- public class QueueRunner implements CommandLineRunner {
- @Autowired
- private RedissonClient redissonClient;
- @Autowired
- private OrderService orderService;
- @Override
- public void run(String... args) throws Exception {
- new Thread(()->{
- RBlockingQueue
blockingFairQueue = redissonClient.getBlockingQueue("orderQueue"); - RDelayedQueue
delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue); - delayedQueue.offer(null, 1, TimeUnit.SECONDS);
- while (true){
- String orderId = null;
- try {
- orderId = blockingFairQueue.take();
- } catch (Exception e) {
- continue;
- }
- if (orderId==null) {
- continue;
- }
- System.out.println(String.format(DateTime.now().toString(JodaUtil.HH_MM_SS)+" 延時隊列收到:"+orderId));
- System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+" 檢測訂單是否完成支付");
- if (orderService.isTimeOut(orderId)) {
- orderService.closeOrder(orderId);
- }
- }
- }).start();
- }
- }
在方法中,單獨啟動了一個線程來進行監(jiān)聽,如果有任務(wù)進入延時隊列,那么取到訂單號后,調(diào)用我們OrderService提供的檢測是否訂單過期的服務(wù),如果過期,那么執(zhí)行關(guān)閉訂單的操作。
創(chuàng)建簡單的OrderService用于測試,提供創(chuàng)建訂單,檢測超時,關(guān)閉訂單方法:
- @Service
- public class OrderService {
- @Autowired
- UnpaidOrderQueue unpaidOrderQueue;
- public void createOrder(String order){
- System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+" 創(chuàng)建訂單:"+order);
- unpaidOrderQueue.addUnpaid(order);
- }
- public boolean isTimeOut(String orderId){
- return true;
- }
- public void closeOrder(String orderId){
- System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+ " 關(guān)閉訂單");
- }
- }
執(zhí)行請求,看一下結(jié)果:
在訂單創(chuàng)建30秒后,檢測到延時隊列中有任務(wù)任務(wù),調(diào)用檢測超時方法檢測到訂單沒有完成后,自動關(guān)閉訂單。
除了上面這種延時隊列的方式外,Redisson還提供了另一種方式,也能優(yōu)雅的關(guān)閉訂單,方法很簡單,就是通過對將要過期的key值的監(jiān)聽。
創(chuàng)建一個類繼承KeyExpirationEventMessageListener,重寫其中的onMessage方法,就能實現(xiàn)對過期key的監(jiān)聽,一旦有緩存過期,就會調(diào)用其中的onMessage方法:
- @Component
- public class RedisExpiredListener extends KeyExpirationEventMessageListener {
- public static final String UNPAID_PREFIX="unpaidOrder:";
- @Autowired
- OrderService orderService;
- public RedisExpiredListener(RedisMessageListenerContainer listenerContainer) {
- super(listenerContainer);
- }
- @Override
- public void onMessage(Message message, byte[] pattern) {
- String expiredKey = message.toString();
- if (expiredKey.startsWith(UNPAID_PREFIX)){
- System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+" " +expiredKey+"已過期");
- orderService.closeOrder(expiredKey);
- }
- }
- }
因為可能會有很多key的過期事件,因此需要對訂單過期的key加上一個前綴,用來判斷過期的key是不是屬于訂單事件,如果是的話那么進行關(guān)閉訂單操作。
再在寫一個測試接口,用于創(chuàng)建訂單和接收支付成功的回調(diào)結(jié)果:
- @RestController
- @RequestMapping("order")
- public class TestController {
- @Autowired
- RedisTemplate redisTemplate;
- @GetMapping("create")
- public String setTemp(String id){
- String orderId= RedisExpiredListener.UNPAID_PREFIX+id;
- System.out.println(DateTime.now().toString(JodaUtil.HH_MM_SS)+" 創(chuàng)建訂單:"+orderId);
- redisTemplate.opsForValue().set(orderId,orderId,30, TimeUnit.SECONDS);
- return id;
- }
- @GetMapping("fallback")
- public void successFallback(String id){
- String orderId= RedisExpiredListener.UNPAID_PREFIX+id;
- redisTemplate.delete(orderId);
- }
- }
在訂單支付成功后,一般我們會收到第三方的一個支付成功的異步回調(diào)通知。如果支付完成后收到了這個回調(diào),那么我們主動刪除緩存的未支付訂單,那么也就不會監(jiān)聽到這個訂單的orderId的過期失效事件。
但是這種方式有一個弊端,就是只能監(jiān)聽到過期緩存的key,不能獲取到對應(yīng)的value。而通過延時隊列的方式,可以通過為RBlockingQueue添加泛型的方式,保存更多訂單的信息,例如直接將對象存進隊列中:
- RBlockingQueue
blockingFairQueue = redissonClient.getBlockingQueue("orderQueue"); - RDelayedQueue
delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue);
這樣的話我們再從延時隊列中獲取的時候,能夠拿到更多我們需要的屬性。綜合以上兩種方式,監(jiān)聽過期更為簡單,但存在的一定的局限性,如果我們只需要對訂單進行判斷的話那么功能也能夠滿足我們的需求,如果需要在過期時獲取更多的訂單屬性,那么使用延時隊列的方式則更為合適。究竟選擇哪種,就要看大家的業(yè)務(wù)場景了。
本文名稱:使用Redisson優(yōu)雅關(guān)閉訂單
網(wǎng)頁網(wǎng)址:http://m.5511xx.com/article/coedddo.html


咨詢
建站咨詢
