QMQ是一款去哪兒網內部使用多年的mq。不久前(大概1-2年前)已在攜程投入生產大規模使用,年前這款mq也開源了出來。關於QMQ的相關設計文章能夠看這裏。在這裏,我假設你已經對QMQ前世此生以及其設計和實現等背景知識已經有了一個較爲全面的認識。git
對於delay-server,官方已經有了一些介紹。記住,官方一般是最賣力的那個"媒婆"。qmq-delay-server其實主要作的是轉發工做。所謂轉發,就是delay-server作的就是個存儲和投遞的工做。怎麼理解,就是qmq-client會對消息進行一個路由,即實時消息投遞到實時server,延遲消息往delay-server投遞,多說一句,這個路由的功能是由qmq-meta-server提供。投遞到delay-server的消息會存下來,到時間以後再進行投遞。如今咱們知道了存儲
和投遞
是delay-server主要的兩個功能點。那麼咱們挨個擊破.github
假如讓咱們來設計實現一個delay-server,存儲部分咱們須要解決什麼問題?我以爲主要是要解決到期投遞的到期
問題。咱們能夠用傳統db作,可是這個性能確定是上不去的。咱們也能夠用基於LSM樹的RocksDB。或者,乾脆直接用文件存儲。QMQ是用文件存儲的。而用文件存儲是怎麼解決到期
問題的呢?delay-server接收到延遲消息,就將消息append到message_log中,而後再經過回放這個message_log獲得schedule_log,此外還有一個dispatch _log用於記錄投遞記錄。QMQ還有個跟投遞相關的存儲設計,即兩層HashWheel。第一層位於磁盤上,例如,以一個小時一個刻度一個文件,咱們叫delay_message_segment,如延遲時間爲2019年02月23日 19:00 至2019年02月23日 20:00爲延遲消息將被存儲在2019022319。而且這個刻度是能夠配置調整的。第二層HashWheel位於內存中。也是以一個時間爲刻度,好比500ms,加載進內存中的延遲消息文件會根據延遲時間hash到一個HashWheel中,第二層的wheel涉及更多的是下一小節的投遞。貌似存儲到這裏就結束了,然而還有一個問題,目前當投遞的時候咱們須要將一個delay_message_segment加載進內存中,而假如咱們提早一個刻度加載進一個delay_message_segment到內存中的hashwheel,好比在2019年02月23日 18:00加載2019022319這個segment文件,那麼一個hashwheel中就會存在兩個delay_message_segment,而這個時候所佔內存是很是大的,因此這是徹底不可接收的。因此,QMQ引入了一個數據結構,叫schedule_index,即消息索引,存儲的內容爲消息的索引,咱們加載到內存的是這個schedule_index,在真正投遞的時候再根據索引查到消息體進行投遞。數據結構
解決了存儲,那麼到期的延遲消息如何投遞呢?如在上一小節存儲中所提到的,內存中的hashwheel會提早一段時間加載delay_schedule_index,這個時間天然也是能夠配置的。而在hashwheel中,默認每500ms會tick一次,這個500ms也是能夠根據用戶需求配置的。而在投遞的時候,QMQ根據實時broker進行分組多線程投遞,若是某一broker離線不可用,致使投遞失敗,delay-server會將延遲消息投遞在其餘存活
的實時broker。其實這裏對於實時的broker應該有一個關於投遞消息權重的,如今delay-server沒有考慮到這一點,不過我看已經有一個pr解決了這個問題,只是官方尚未時間看這個問題。除此以外,QMQ還考慮到了要是當前延遲消息所屬的delay_segment已經加載到內存中的hashwheel了,這個時候消息應該是直接投遞或也應加載到hashwheel中的。這裏須要考慮的狀況仍是比較多的,好比考慮delay_segment正在加載、已經加載、加載完成等狀況,對於這種狀況,QMQ用了兩個cursor來表示hashwheel加載到哪一個delay_segment以及加載到對應segment的什麼offset了,這裏仍是挺複雜的,這裏的代碼邏輯在WheelTickManager
這個類。多線程