分佈式消息系統作爲實現分佈式系統可擴展,可伸縮性的關鍵組件,需要具有高吞吐量,高可用等特定,而談到消息系統的設計,就回避不了兩個問題:
1 消息的順序問題
2 消息的重複問題
RocketMQ作爲阿里開源的一款高性能、高吞吐量的消息中間件,它是怎樣來解決這兩個問題的?RocketMQ 有哪些關鍵特性?其實現原理是怎樣的?
一、順序消息
消息有序指的是可以按照消息的發送順序來消費。例如:一筆訂單產生了 3 條消息,分別是訂單創建、訂單付款、訂單完成。消費時,要按照順序依次消費纔有意義。與此同時多筆訂單之間又是可以並行消費的。首先來看如下示例:
假如生產者產生了2條消息:M1、M2,要保證這兩條消息的順序,應該怎樣做?你腦中想到的可能是這樣:
假定M1發送到S1,M2發送到S2,如果要保證M1先於M2被消費,那麼需要M1到達消費端被消費後,通知S2,然後S2再將M2發送到消費端。
這個模型存在的問題是,如果M1和M2分別發送到兩臺Server上,就不能保證M1先達到MQ集羣,也不能保證M1被先消費。換個角度看,如果M2先於M1達到MQ集羣,甚至M2被消費後,M1才達到消費端,這時消息也就亂序了,說明以上模型是不能保證消息的順序的。如何才能在MQ集羣保證消息的順序?一種簡單的方式就是將M1、M2發送到同一個Server上:
這樣可以保證M1先於M2到達MQServer(生產者等待M1發送成功後再發送M2),根據先達到先被消費的原則,M1會先於M2被消費,這樣就保證了消息的順序。
這個模型也僅僅是理論上可以保證消息的順序,在實際場景中可能會遇到下面的問題:
只要將消息從一臺服務器發往另一臺服務器,就會存在網絡延遲問題。如上圖所示,如果發送M1耗時大於發送M2的耗時,那麼M2就仍將被先消費,仍然不能保證消息的順序。即使M1和M2同時到達消費端,由於不清楚消費端1和消費端2的負載情況,仍然有可能出現M2先於M1被消費的情況。
那如何解決這個問題?將M1和M2發往同一個消費者,且發送M1後,需要消費端響應成功後才能發送M2。
聰明的你可能已經想到另外的問題:如果M1被髮送到消費端後,消費端1沒有響應,那是繼續發送M2呢,還是重新發送M1?一般爲了保證消息一定被消費,肯定會選擇重發M1到另外一個消費端2,就如下圖所示。
回過頭來看消息順序問題,嚴格的順序消息非常容易理解,也可以通過文中所描述的方式來簡單處理。總結起來,要實現嚴格的順序消息,簡單且可行的辦法就是:
保證
生產者 - MQServer - 消費者
是一對一對一的關係這樣的設計雖然簡單易行,但也會存在一些很嚴重的問題,比如:
- 並行度就會成爲消息系統的瓶頸(吞吐量不夠)
- 更多的異常處理,比如:只要消費端出現問題,就會導致整個處理流程阻塞,我們不得不花費更多的精力來解決阻塞的問題。
有些問題,看起來很重要,但實際上我們可以通過合理的設計或者將問題分解來規避。如果硬要把時間花在解決問題本身,實際上不僅效率低下,而且也是一種浪費。從這個角度來看消息的順序問題,我們可以得出兩個結論:
- 不關注亂序的應用實際大量存在
- 隊列無序並不意味着消息無序