互聯(lián)網(wǎng)面試必備之超時(shí)訂單自動(dòng)關(guān)閉的優(yōu)雅實(shí)現(xiàn)
任務(wù)場(chǎng)景
12306訂單,比如我們買(mǎi)了一張票,一般會(huì)給30分鐘的支付時(shí)間,如果30分鐘內(nèi)沒(méi)有支付,則系統(tǒng)會(huì)自動(dòng)取消訂單,然后釋放鎖定座位。

那么超時(shí)訂單有哪幾種實(shí)現(xiàn)方式呢?
超時(shí)訂單自動(dòng)關(guān)閉的優(yōu)雅實(shí)現(xiàn)
1、數(shù)據(jù)庫(kù)輪詢(xún)(30S)
訂單表(訂單ID,狀態(tài),創(chuàng)建時(shí)間)

輪詢(xún)數(shù)據(jù)庫(kù)會(huì)帶來(lái)什么問(wèn)題?
- 輪詢(xún)大部分時(shí)間其實(shí)是在做無(wú)用功,我們假設(shè)一張訂單是45分鐘過(guò)期,每分鐘我們掃描一次,對(duì)這張訂單來(lái)說(shuō),要掃描45次以后,才會(huì)檢查到這張訂單過(guò)期,這就意味著數(shù)據(jù)庫(kù)的資源(連接,IO)被白白浪費(fèi)了;
- 處理上的不及時(shí),一個(gè)待支付的電影票訂單我們假設(shè)是12:00:35過(guò)期,但是上次掃描的時(shí)間是12:00:30,那么這個(gè)訂單實(shí)際的過(guò)期時(shí)間是什么時(shí)候?12:01:30,和我本來(lái)的過(guò)期時(shí)間差了55秒鐘。放在業(yè)務(wù)上,會(huì)帶來(lái)什么問(wèn)題?這張電影票,假設(shè)是最后一張,有個(gè)人12:00:55來(lái)買(mǎi)票,買(mǎi)得到嗎?當(dāng)然買(mǎi)不到了。那么這張電影票很有可能就浪費(fèi)了。如果縮短掃描的時(shí)間間隔,第一只能改善不能解決,第二,又會(huì)對(duì)數(shù)據(jù)庫(kù)造成更大的壓力。
2、使用DelayQueue 實(shí)現(xiàn)

DelayQueue: 阻塞隊(duì)列(先進(jìn)先出)
- 支持阻塞的插入方法:意思是當(dāng)隊(duì)列滿(mǎn)時(shí),隊(duì)列會(huì)阻塞插入元素的線(xiàn)程,直到隊(duì)列不滿(mǎn)
- 支持阻塞的移除方法:意思是在隊(duì)列為空時(shí),獲取元素的線(xiàn)程會(huì)等待隊(duì)列變?yōu)榉强铡?/li>
Delayed接口使對(duì)象成為延遲對(duì)象,它使存放在DelayQueue類(lèi)中的對(duì)象具有了激活日期。該接口強(qiáng)制實(shí)現(xiàn)下列兩個(gè)方法。
應(yīng)用重啟帶來(lái)的問(wèn)題
- 保存在Queue 中的訂單會(huì)丟失
- 已過(guò)期的訂單不會(huì)被處理

解決之道

從系統(tǒng)伸縮性角度考慮:應(yīng)用集群化了怎么辦?
集群化了會(huì)帶來(lái)什么問(wèn)題?應(yīng)用之間會(huì)相互搶奪訂單,特別是在應(yīng)用重啟的時(shí)候,重新啟動(dòng)的那個(gè)應(yīng)用會(huì)把不屬于自己的訂單,也全部加載到自己的隊(duì)列里去,一是造成內(nèi)存的浪費(fèi),二來(lái)會(huì)造成訂單的重復(fù)處理,而且加大了數(shù)據(jù)庫(kù)的壓力。
解決方案:
1、 給每臺(tái)服務(wù)器編號(hào),然后在訂單表里登記每條訂單的服務(wù)器編號(hào);2,更簡(jiǎn)單的,在訂單表里登記每臺(tái)服務(wù)器的IP地址,修改相應(yīng)的sql語(yǔ)句即可。
幾個(gè)問(wèn)題:如果有一臺(tái)服務(wù)器掛了怎么辦?運(yùn)維吃干飯的嗎?服務(wù)器掛了趕緊啟動(dòng)啊。如果是某臺(tái)服務(wù)器下線(xiàn)或者宕機(jī),起不來(lái)怎么辦?這個(gè)還是還是稍微有點(diǎn)麻煩,需要人工干預(yù)一下,手動(dòng)把庫(kù)里的每條訂單數(shù)據(jù)的服務(wù)器編號(hào)改為目前正常的服務(wù)器的編號(hào),不過(guò)也就是一條sql語(yǔ)句的事,然后想辦法讓正常的服務(wù)器進(jìn)行處理(重啟正常的服務(wù)器)。
能不能同時(shí)解決伸縮性和擴(kuò)展性問(wèn)題?
用delayqueue是隊(duì)列,分布式情況我們何不直接引入消息中間件呢?一舉解決我們應(yīng)用的伸縮性和擴(kuò)展性問(wèn)題
我們可以使用ActiveMQ的延遲和定時(shí)投遞
3、ActiveMQ延遲隊(duì)列
修改配置文件(activemq.xml),增加延遲和定時(shí)投遞支持
- <broker xmlns="http://activemq.apache.org/schema/core"
- brokerName="localhost" dataDirectory="${activemq.data}"
- schedulerSupport="true">
需要把幾個(gè)描述消息定時(shí)調(diào)度方式的參數(shù)作為屬性添加到消息,broker端的調(diào)度器就會(huì)按照我們想要的行為去處理消息。
一共有4個(gè)屬性:
- AMQ_SCHEDULED_DELAY :延遲投遞的時(shí)間
- AMQ_SCHEDULED_PERIOD :重復(fù)投遞的時(shí)間間隔
- AMQ_SCHEDULED_REPEAT:重復(fù)投遞次數(shù)
- AMQ_SCHEDULED_CRON:Cron表達(dá)式
ActiveMQ也提供了一個(gè)封裝的消息類(lèi)型:org.apache.activemq.ScheduledMessage,可以使用這個(gè)類(lèi)來(lái)輔助設(shè)置,使用例子如:延遲60秒
- MessageProducer producer = session.createProducer(destination);
- TextMessage message = session.createTextMessage("test msg");
- long time = 60 * 1000;
- message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, time);
- producer.send(message);
例子:延遲30秒,投遞10次,間隔10秒:
- TextMessage message = session.createTextMessage("test msg");
- long delay = 30 * 1000;
- long period = 10 * 1000;
- int repeat = 9;
- message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delay);
- message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, period);
- message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);
- 也可使用 CRON 表達(dá)式,如message.setStringProperty(ScheduledMessage.AMQ_SCHEDULED_CRON, "0 * * * *");
也可使用 CRON 表達(dá)式,如message.setStringProperty(ScheduledMessage.AMQ_SCHEDULED_CRON, "0 * * * *");
4、Redis有序集合
Redis 有序集合
Redis sortedSet 集合(sorted set 也叫zset) 是一個(gè)有序集合,每個(gè)元素(member)都關(guān)聯(lián)了一個(gè)score,可以通過(guò)score排序獲取集合中的值。
zset常用命令:
- 添加元素:zadd key score member[[score member]]
- 按順序查詢(xún)?cè)兀簔range key start stop [withscores]
- 查詢(xún)?cè)豷core:zscore key member
- 移除元素: zrem key member 【member ...】
將訂單超時(shí)時(shí)間戳(long)與訂單號(hào)分別設(shè)置為score與member,系統(tǒng)掃描第一個(gè)元素判斷定是否超時(shí),拿到分?jǐn)?shù)最小的,超時(shí)時(shí)間最早的。判斷與當(dāng)前時(shí)間戳的關(guān)系

生產(chǎn)者

消費(fèi)者

5、Redis Set 集合
將訂單延遲時(shí)間的秒級(jí)時(shí)間戳設(shè)置為set集合的key,value 為訂單ID
sadd set的key item的項(xiàng)值,item項(xiàng)可以有多個(gè)
按秒級(jí)的時(shí)間進(jìn)行聚合,即 key為時(shí)間戳,里面可以由多個(gè)ID


總結(jié)
1、DB輪詢(xún)
- 優(yōu)點(diǎn): 實(shí)現(xiàn)簡(jiǎn)單、無(wú) 技術(shù)難點(diǎn)、異?;謴?fù)、支持分布式/進(jìn)群環(huán)境
- 缺點(diǎn):影響數(shù)據(jù)庫(kù)性能、時(shí)效性差、效率低
2、DelayedQueue
- 優(yōu)點(diǎn): 實(shí)現(xiàn)簡(jiǎn)單、性能較好
- 缺點(diǎn): 異?;謴?fù)困難、分布式/集群實(shí)現(xiàn)坤丹
3、redis
優(yōu)點(diǎn) ; 解耦、異?;謴?fù)、支持分布式/集群環(huán)境
缺點(diǎn)
- 增加redis維護(hù)、占用寬帶
- 有序 集合缺點(diǎn): 當(dāng)sortedSet集合中元素過(guò)多時(shí),插入性能降低
- Set集合缺點(diǎn): 已經(jīng)超時(shí)未處理的訂單不好處理