RocketMQ實戰4

轉自:http://www.jianshu.com/p/6868ddceaa5b

RocketMQ 3.2.6的事務機制

在上一篇博客中,已經知道RocketMQ 3.0.8是支持事務回查機制,但是在RocketMQ 3.2.6中取消了這個功能,下面我們繼續以轉賬功能分析我們自己如何解決這個問題。



轉賬流程

在正常情況下,當然沒有問題,如果第五步(向MQ發送確認消息)出現失敗,加上RocketMQ 3.2.6版本沒有事務回查機制,就會導致這條轉賬消息,在A銀行完成了操作,但是遲遲對B銀行系統不可見!



解決RocketMQ 3.2.6不支持事務回查的思路

用戶U1從A銀行系統轉賬給B銀行系統的用戶U2的處理過程如下:

第一步:A銀行系統生成一條轉賬消息,以事務消息的方式寫入RocketMQ,此時B銀行系統不可見這條消息

第二步:寫入MQ成功後,回調A銀行系統,對T1,T2表進行操作(很顯然需要是一個事務)

我們重點關注下T2表,這個表是用來幹嘛的呢?每條轉賬消息都會在T2表中,該表有2個特殊的字段:status,updatetime。(用途會在後文詳述)

第三步:完成第二步,接下來發送確認消息給MQ,如果這個確認消息發送成功,那麼這條轉賬消息,將對B銀行系統可見。然後B銀行系統,會在一個事務中完成對t3,t5的操作。

如果發送確認消息給MQ失敗的處理思路:

首先,B銀行系統,有一個定時任務(比如說每隔1MIN執行一次),掃描表t5,取得一段時間內的數據,發送給A銀行系統。要知道t5中的數據,必然是A銀行系統成功處理併發送確認消息成功的轉賬數據。爲什麼要發送給A銀行系統呢,其實就是爲了找到那些發送確認消息失敗的轉賬數據。那麼怎麼發給A銀行系統呢,這個方式比較多,可以考慮在來一個Topic,也可以考慮Netty等。發送給A銀行系統,其實就是爲了更新t2表的status,updatetime。

這裏有一個關鍵,如何“掃描表t5,取得一段時間內的數據”?這就是t4的作用,在t4中記錄一個time字段,每次定時任務啓動,先更新time(比如設定爲當前系統時間,設置前的的時間爲old),然後掃描出t5中大於這個old時間的轉賬數據,如此循環往復。

其次,A銀行系統,也有一個定時任務(可以根據業務消費能力定,可以大一些),掃描t2表(指定status及updatetime條件),將那些確認消息發送失敗的轉賬消息找出來,更新updatetime併發送給MQ。

這樣,我們並沒有改動RocketMQ 3.2.6的源碼,而是在外圍解決了事務回查!

其實到這裏,你可以發現RocketMQ的一個特點,就是將生產者和MQ綁定,而不需要特別處理消費者,這是爲什麼呢?因爲消息只要發往RocketMQ成功,那麼就意味着成功,爲什麼這麼說?

前面,我們說過,消費者端消費消息只會產生2種錯誤,第一:timeout,第二:exception。要知道RocketMQ對於超時,會不斷重試;對於消費異常,會根據消費端的返回碼,會有重試機制保證。也就是,RocketMQ一定會讓消息得到消費,如果消費有問題,只能是消費者的問題,而不會是RocketMQ的問題!


Pull Or Push

在前面的博客已經提到,在RocketMQ中Consumer分爲2類:Push Consumer、Pull Consumer。以前的例子都是Push Consumer,接下來,爲大家介紹下Pull Consumer。



通過MQPullConsumerScheduleService進行操作



註冊回調並啓動

從表面意思上來看,好像Push是MQ推送給消費者,而Pull是消費者從MQ中拉取;其實本質上都是拉取模式PULL,即消費者從MQ中輪詢取得消息。

在Push模式下,Consumer把輪詢過程封裝了,並註冊了MessageListener監聽器,取到消息後,喚醒MessageListener監聽器中的consumeMessage()進行消費,所以給我們造成了感覺上好像是“推消息”。

在Pull模式下,需要特別注意的是,本質上是從一個Topic下的所有Queue進行拉取,而且每個Queue都必須記錄拉取位置,否則會導致重複消費。還有拉取的時間間隔,拉取的大小等等。不過所有的這一切,MQPullConsumerScheduleService都替我們考慮清楚了,提供updateConsumeOffset去更新消費的隊列的位置(默認5S同步一次),提供setPullNextDelayTimeMillis設置下次拉取的時間間隔(應該設置的大一些,至少大於5S)。

仔細回想下,對於Push方式的回調   和  Pull方式的回調,還有什麼關鍵區別麼?

對於Push而言,不論是基於MessageListenerConcurrently的,還是基於MessageListenerOrderly的,都有返回值的;而Pull的doPullTask的返回值卻是void?

這意味,我們需要在pull方式中,注意自己處理每條消息消費的異常情況!



運行結果

通過運行結果,可以印證上面的觀點:爲什麼每次消費都是4條開始,4條結束呢?因爲一個Topic下有4個Queue,而且上面的代碼實際上會針對每個Queue開啓一個線程去消費!


RocketMQ Filter組件介紹

對於ActiveMQ而言,我們可以通過JMS Selectors機制(就是類似於SQL的語法)來實現過濾,很easy。那麼和RocketMQ Filter組件有什麼區別呢?

雖然,2者都能實現過濾,但是RocketMQ Filter的性能要更高效些,因爲RocketMQ是在broker上將過濾後的數據發往filter,然後消費者直接從filter上取得數據;而ActiveMQ是消費者直接在broker上進行過濾消費!(當然,對於RocketMQ而言,Tag機制已經足夠應付日常絕大數的過濾功能,除非你的業務對性能有特別高的要求)



RocketMQ Filter機制

具體怎麼做呢?這裏我就不演示了,網上有很多例子,這裏只說下大致的過程:

第一:broker-xxx.properties中指定filter個數 

第二:上傳一段JAVA代碼,其實就是一個類


到這裏,整個RocketMQ實戰系列就結束呢,你學到了麼,體會到RocketMQ的強大了麼?

See u next blog!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章