24小時內未支付的訂單過時失效。
這個太簡單了,就是在查詢的時候判斷是否失效,若是失效了就給他設置失效狀態。可是弊端也很明顯,每次查詢都要對未失效的訂單作判斷,若是用戶不查詢,訂單就不失效,那麼若是有相似統計失效狀態個數的功能,將會受到影響,因此只能適用於簡單獨立的場景。簡直low爆了。java
這種是常見的方法,利用一個定時器,在設置的週期內輪詢檢查並處理須要過時的訂單。
具體實現有基於Timer
的,有基於Quartz
,還有springboot自帶的Scheduler
,實現起來比較簡單。
就寫一下第三個的實現方法吧:redis
@EnableScheduling
@Scheduled
註解,以下圖那麼簡單。
弊端spring
基於JDK的實現方法,將未支付的訂單放到一個有序的隊列中,程序會自動依次取出過時的訂單。
若是當前沒有過時的訂單,就會阻塞,直至有過時的訂單。因爲每次只處理過時的訂單,而且處理的時間也很精準,不存在定時調度方案的那兩個弊端。
實現:
1.首先建立一個訂單類OrderDelayDto
須要實現Delayed
接口。而後重寫getDelay()
方法和compareTo()
方法,只加了訂單編號和過時時間兩個屬性。
這兩個方法很重要,getDelay()
方法實現過時的策略,好比,訂單的過時時間等於當前時間就是過時,返回負數就表明須要處理。不然不處理。compareTo()
方法實現訂單在隊列中的排序規則,這樣即便後面加入的訂單,也能加入到排序中,我這裏寫的規則是按照過時時間排序,最早過時的排到最前面,這一點很重要,由於排在最前面的若是沒有被處理,就會進入阻塞狀態,後面的不會被處理。springboot
import lombok.Data; import java.util.Date; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; /** * @author mashu * Date 2020/5/17 16:25 */ @Data public class OrderDelayDto implements Delayed { /** * 訂單編號 */ private String orderCode; /** * 過時時間 */ private Date expirationTime; /** * 判斷過時的策略:過時時間大於等於當前時間就算過時 * * @param unit * @return */ @Override public long getDelay(TimeUnit unit) { return unit.convert(this.expirationTime.getTime() - System.currentTimeMillis(), TimeUnit.NANOSECONDS); } /** * 訂單加入隊列的排序規則 * * @param o * @return */ @Override public int compareTo(Delayed o) { OrderDelayDto orderDelayDto = (OrderDelayDto) o; long time = orderDelayDto.getExpirationTime().getTime(); long time1 = this.getExpirationTime().getTime(); return time == time1 ? 0 : time < time1 ? 1 : -1; } }
其實這樣已經算是寫好了。我沒有耍你。
寫個main 方法測試一下,建立兩個訂單o1和o2,放入到延時隊列中,而後while()方法不斷的去取。
在此方法內經過隊列的take()
方法得到已過時的訂單,而後作出相應的處理。多線程
public static void main(String[] args) { DelayQueue<OrderDelayDto> queue = new DelayQueue<>(); OrderDelayDto o1 = new OrderDelayDto(); //第一個訂單,過時時間設置爲一分鐘後 o1.setOrderCode("1001"); Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE, 1); o1.setExpirationTime(calendar.getTime()); OrderDelayDto o2 = new OrderDelayDto(); //第二個訂單,過時時間設置爲如今 o2.setOrderCode("1002"); o2.setExpirationTime(new Date()); //往隊列中放入數據 queue.offer(o1); queue.offer(o2); // 延時隊列 while (true) { try { OrderDelayDto take = queue.take(); System.out.println("訂單編號:" + take.getOrderCode() + " 過時時間:" + take.getExpirationTime()); } catch (InterruptedException e) { e.printStackTrace(); } } }
運行結果:
ide
我故意把第二個訂單的過時時間設置爲第一個訂單以前,從結果能夠看出,他們已經自動排序把最早過時的排到了最前面。
第一個訂單的失效時間是當前時間的後一分鐘,結果也顯示一分鐘後處理了第一條訂單。測試
2.然而一般狀況下,咱們會使用多線程去取延時隊列中的數據,這樣即便線程啓動以後也能動態的向隊列中添加訂單。
建立一個線程類OrderCheckScheduler
實現Runnable
接口,
添加一個延時隊列屬性,重寫run()
方法,在此方法內經過隊列的take()
方法得到已過時的訂單,而後作出相應的處理。優化
import java.util.concurrent.DelayQueue; /** * @author mashu * Date 2020/5/17 14:27 */ public class OrderCheckScheduler implements Runnable { // 延時隊列 private DelayQueue<OrderDelayDto> queue; public OrderCheckScheduler(DelayQueue<OrderDelayDto> queue) { this.queue = queue; } @Override public void run() { while (true) { try { OrderDelayDto take = queue.take(); System.out.println("訂單編號:" + take.getOrderCode() + " 過時時間:" + take.getExpirationTime()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
好了,寫個方法測試一下:this
public static void main(String[] args) { // 建立延時隊列 DelayQueue<OrderDelayDto> queue = new DelayQueue<>(); OrderDelayDto o1 = new OrderDelayDto(); //第一個訂單,過時時間設置爲一分鐘後 o1.setOrderCode("1001"); Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE, 1); o1.setExpirationTime(calendar.getTime()); OrderDelayDto o2 = new OrderDelayDto(); //第二個訂單,過時時間設置爲如今 o2.setOrderCode("1002"); o2.setExpirationTime(new Date()); //運行線程 ExecutorService exec = Executors.newFixedThreadPool(1); exec.execute(new OrderCheckScheduler(queue)); //往隊列中放入數據 queue.offer(o1); queue.offer(o2); exec.shutdown(); }
結果和上面的同樣,圖就不截了,相信我。spa
基於redis的過時提醒功能,聽名字就知道這個方案最是純真、最直接的,就是單純處理過時的訂單。
修改個redis的配置吧先,由於redis默認不開啓過時提醒。notify-keyspace-events
改成notify-keyspace-events "Ex"
寫一個類用來接收來自redis的暖心提醒OrderExpirationListener
,繼承一下KeyExpirationEventMessageListener
抽象類。重寫onMessage()
方法,在此方法中處理接收到的過時key.
import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.listener.KeyExpirationEventMessageListener; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.stereotype.Component; import java.util.Date; /** * @author mashu * Date 2020/5/17 23:01 */ @Component public class OrderExpirationListener extends KeyExpirationEventMessageListener { public OrderExpirationListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } @Override public void onMessage(Message message, byte[] pattern) { final String expiredKey = message.toString(); System.out.println("我過時了" + expiredKey+"當前時間:"+new Date()); } }
ok,向redis中存入一個訂單,過時時間爲1分鐘。
redis.set("orderCode/10010", "1", 1L, TimeUnit.MINUTES); System.out.println("redis存入訂單號 key: orderCode/10010,value:1,過時時間一分鐘,當前時間"+new Date());
運行結果:
除此以外還有用到消息隊列的。夜深了,我得玩會遊戲了。
沒有絕對的好方案,只有在不一樣場景下的更合適的方案。隨着需求的變化,技術的革新,方案也會不斷的被優化和迭代,惟一不變的是工資。