架構面試題—大併發量的訂單的解析

weixin_33724059發表於2018-12-02
點選關注,快速進階高階架構師

作者:激情的狼王

問題描述:做一個電商平臺,如何設定一個在買家下訂單後的”第60秒“發簡訊通知賣家發貨,需要考慮的是像淘寶一樣的大併發量的訂單。

原問題連結

https://www.oschina.net/question/926166_2137672

最基礎設計

最直觀能想到的解決辦法就是使用延遲佇列的實現原理,其就是一個按時間排好序的佇列,每次put的時候排序,然後take的時候就計算時間是否過期,如果過期則返回佇列第一個元素進行消費,否則當前執行緒阻塞X秒後彈出第一個元素,這個就是DelayQueue 的思路。

這種實現是最基礎的,但是也是問題最多的

1.DelayQueue 的最大容量是有上限的,承受不住過多的訂單2.每次來新的訂單都要進行排序插入合適位置,訂單量級過大時效能會很低3.這種方案只適合單機,無法橫向進行分散式擴充套件

升級版

針對上述的問題,我們採用Redis叢集來替代DelayQueue 的設計

1.生成訂單後,立即往redis群集寫訂單資訊(資訊包含下單時間)。2.根據redis叢集的結點數量,開啟相應倍數(大於等於1)的執行緒數,n個執行緒掃描一個結點3.各執行緒每次掃描得到的都是下單時間等於60s的訂單,再對這些訂單傳送簡訊,並相應的從redis移除

這種方法解決了基礎版的三個問題,在升級版裡還有一些點可以進行細化優化

1.訂單量爆發時,可以將每個訂單直接扔進redis,將壓力分給叢集2.訂單流量中低時,可以利用redis的SortedSet(有序集合)來進行操作,增大redis的掃描執行緒的顆粒度,進而提升處理效率3.Redis併發時的資料一致性問題,可以通過redis事務靈活解決

到這裡基本就可以解決本文所說的高併發量的帶時間延遲的問題了,我們再深入想一想這種設計的問題,如果時間不固定跨度廣的情況下其實輪詢的方式是不那麼理想的會空轉cpu

時間輪(TimingWheel)

Kafka中存在大量的延遲操作,比如延遲生產、延遲拉取以及延遲刪除等。Kafka並沒有使用JDK自帶的Timer或者DelayQueue來實現延遲的功能,而是基於時間輪自定義了一個用於實現延遲功能的定時器(SystemTimer)。

JDK的Timer和DelayQueue和redis的SortedSet插入和刪除操作的平均時間複雜度為O(nlog(n)),並不能滿足Kafka的高效能要求,而基於時間輪可以將插入和刪除操作的時間複雜度都降為O(1)。

時間輪的應用並非Kafka獨有,其應用場景還有很多,在Netty、Akka、Quartz、Zookeeper等元件中都存在時間輪的蹤影。

參考下圖,Kafka中的時間輪(TimingWheel)是一個儲存定時任務的環形佇列,每個元素可以存放一個定時任務列表(TimerTaskList)。TimerTaskList是一個雙向連結串列,元素是(TimerTaskEntry),其中封裝了真正的定時任務TimerTask。

架構面試題—大併發量的訂單的解析

image.png

時間輪由多個時間格組成,每個時間格代表當前時間輪的基本時間跨度(tickMs)。時間輪的時間格個數是固定的,可用wheelSize來表示,那麼整個時間輪的總體時間跨度(interval)可以通過公式 tickMs × wheelSize計算得出。時間輪還有一個錶盤指標(currentTime),currentTime當前指向的時間格也屬於到期部分,表示剛好到期,需要處理此時間格所對應的TimerTaskList的所有任務。

時間輪就像時鐘一樣,可以通過秒錶指向誰就執行誰,當時間跨度大時,可以增加時間輪的級別,如圖

架構面試題—大併發量的訂單的解析

image.png

第一層的時間輪tickMs=1ms, wheelSize=20, interval=20ms。

第二層的時間輪的tickMs為第一層時間輪的interval,即為20ms。每一層時間輪的wheelSize是固定的,都是20,那麼第二層的時間輪的總體時間跨度interval為400ms。

以此類推,這個400ms也是第三層的tickMs的大小,第三層的時間輪的總體時間跨度為8000ms。時間輪的優點

1.把任務輪詢的多個執行緒改裝為了秒針的單一輪詢2.從毫秒級或者秒級任務獲取執行改裝為批量的範圍獲取3.擴充套件性極好,顆粒度可以根據業務場景自適應4.插入和刪除操作的時間複雜度都降為O(1)

相關文章