在說到消息中間件的時候,我們通常都會談到一個特性:消息的順序消費問題。這個問題看起來很簡單:Producer發(fā)送消息1, 2, 3。。。 Consumer按1, 2, 3。。。順序消費。
但實際情況卻是:無論RocketMQ,還是Kafka,缺省都不保證消息的嚴格有序消費!
這個特性看起來很簡單,但為什么缺省他們都不保證呢?
“嚴格的順序消費”有多么困難
下面就從3個方面來分析一下,對于一個消息中間件來說,”嚴格的順序消費”有多么困難,或者說不可能。
發(fā)送端
發(fā)送端不能異步發(fā)送,異步發(fā)送在發(fā)送失敗的情況下,就沒辦法保證消息順序。
比如你連續(xù)發(fā)了1,2,3。 過了一會,返回結(jié)果1失敗,2, 3成功。你把1再重新發(fā)送1遍,這個時候順序就亂掉了。
存儲端
對于存儲端,要保證消息順序,會有以下幾個問題:
(1)消息不能分區(qū)。也就是1個topic,只能有1個隊列。在Kafka中,它叫做partition;在RocketMQ中,它叫做queue。 如果你有多個隊列,那同1個topic的消息,會分散到多個分區(qū)里面,自然不能保證順序。
(2)即使只有1個隊列的情況下,會有第2個問題。該機器掛了之后,能否切換到其他機器?也就是高可用問題。
比如你當前的機器掛了,上面還有消息沒有消費完。此時切換到其他機器,可用性保證了。但消息順序就亂掉了。
要想保證,一方面要同步復制,不能異步復制;另1方面得保證,切機器之前,掛掉的機器上面,所有消息必須消費完了,不能有殘留。很明顯,這個很難!??!
接收端
對于接收端,不能并行消費,也即不能開多線程或者多個客戶端消費同1個隊列。
總結(jié)
從上面的分析可以看出,要保證消息的嚴格有序,有多么困難!
發(fā)送端和接收端的問題,還好解決一點,限制異步發(fā)送,限制并行消費。但對于存儲端,機器掛了之后,切換的問題,就很難解決了。
你切換了,可能消息就會亂;你不切換,那就暫時不可用。這2者之間,就需要權(quán)衡了。
業(yè)務需要全局有序嗎?
通過上面分析可以看出,要保證一個topic內(nèi)部,消息嚴格的有序,是很困難的,或者說條件是很苛刻的。
那怎么辦呢?我們一定要使出所有力氣、用盡所有辦法,來保證消息的嚴格有序嗎?
這里就需要從另外一個角度去考慮這個問題:業(yè)務角度。正如在下面這篇博客中所說的:
http://m.itdecent.cn/p/453c6e7ff81c
實際情況中:
(1)不關(guān)注順序的業(yè)務大量存在;
(2) 隊列無序不代表消息無序。
第(2)條的意思是說:我們不保證隊列的全局有序,但可以保證消息的局部有序。
舉個例子:保證來自同1個order id的消息,是有序的!
下面就看一下在Kafka和RocketMQ中,分別是如何對待這個問題的:
Kafka中:發(fā)送1條消息的時候,可以指定(topic, partition, key) 3個參數(shù)。partiton和key是可選的。
如果你指定了partition,那就是所有消息發(fā)往同1個partition,就是有序的。并且在消費端,Kafka保證,1個partition只能被1個consumer消費。
或者你指定key(比如order id),具有同1個key的所有消息,會發(fā)往同1個partition。也是有序的。
RocketMQ: RocketMQ在Kafka的基礎(chǔ)上,把這個限制更放寬了一步。只指定(topic, key),不指定具體發(fā)往哪個隊列。也就是說,它更加不希望業(yè)務方,非要去要一個全局的嚴格有序。
關(guān)鍵點:這個放開,其實牽涉到一個更大的問題。就是RocketMQ和Kafka在底層存儲上面的重大差異。這個我在上1篇,”撥亂反正“”續(xù)篇中,有過介紹。
后面在源碼分析序列中,會進一步分析這個問題。
關(guān)于“消息順序”這個問題,就討論到此為止。