有哪些Java程序員必知必會的RocketMQ消息類型

本文轉載自:有哪些Java程序員必知必會的RocketMQ消息類型


一、普通消息

普通消息也叫做無序消息,簡單來說就是沒有順序的消息,producer 只管發送消息,consumer 只管接收消息,至於消息和消息之間的順序並沒有保證,可能先發送的消息先消費,也可能先發送的消息後消費。

舉個簡單例子,producer 依次發送 order id 爲 1、2、3 的消息到 broker,consumer 接到的消息順序有可能是 1、2、3,也有可能是 2、1、3 等情況,這就是普通消息。

因爲不需要保證消息的順序,所以消息可以大規模併發地發送和消費,吞吐量很高,適合大部分場景。

代碼示例:

生產者

public class Producer {
	public static void main(String[] args) throws MQClientException, InterruptedException {
		//聲明並初始化一個producer
		//需要一個producer group名字作爲構造方法的參數,這裏爲concurrent_producer
		DefaultMQProducer producer = new DefaultMQProducer("concurrent_producer");
		//設置NameServer地址,此處應改爲實際NameServer地址,
		多個地址之間用;分隔
		//NameServer的地址必須有,但是也可以通過環境變量的
		方式設置,不一定非得寫死在代碼裏
		producer.setNamesrvAddr("10.1.54.121:9876;10.1.54.
122 :9876");
		//調用start()方法啓動一個producer實例
		producer.start();
		//發送10條消息到Topic爲TopicTest,tag爲TagA,消息內容爲“Hello RocketMQ”拼接上i的值
		for (int i = 0; i < 10; i++) {
			try {
				Message msg = new Message("TopicTestConcurrent",// topic
				"TagA",// tag
				("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)// body
				);
				//調用producer的send()方法發送消息
				//這裏調用的是同步的方式,所以會有返回結果,
				同時默認發送的也是普通消息
				SendResult sendResult = producer.send(msg);
				//打印返回結果,可以看到消息發送的狀態以及一些相關信息
				System.out.println(sendResult);
			}
			catch (Exception e) {
				e.printStackTrace();
				Thread.sleep(1000);
			}
		}
		//發送完消息之後,調用shutdown()方法關閉producer
		producer.shutdown();
	}
}

消費者

public class Consumer {
	public static void main(String[] args) throws
	InterruptedException, MQClientException
	//聲明並初始化一個consumer
	//需要一個consumer group名字作爲構造方法的參數,
	這裏爲concurrent_consumer
	DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("concurrent_consumer");
	//同樣也要設置NameServer地址
	consumer.setNamesrvAddr("10.1.54.121:9876;10.1 .54 .122 : 9876");
	//這裏設置的是一個consumer的消費策略
	//CONSUME_FROM_LAST_OFFSET
	默認策略,從該隊列最尾開始消費,即跳過歷史消息
	//CONSUME_FROM_FIRST_OFFSET
	從隊列最開始開始消費,即歷史消息(還儲存在broker的)
	全部消費一遍
	//CONSUME_FROM_TIMESTAMP
	從某個時間點開始消費,和setConsumeTimestamp()配合使用,
	默認是半個小時以前
	consumer.setConsumeFromWhere(ConsumeFromWhere.
	CONSUME_FROM_FIRST_OFFSET);
	//設置consumer所訂閱的Topic和Tag,*代表全部的Tag
	consumer.subscribe("TopicTestConcurrent", "*");
	//設置一個Listener,主要進行消息的邏輯處理
	//注意這裏使用的是MessageListenerConcurrently這個接口
	consumer.registerMessageListener(new MessageListenerConcurrently() {
		@Override
		public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
		ConsumeConcurrentlyContext context) {
			System.out.println(Thread.currentThread().getName() + " Receive New Messages: " + msgs);
			//返回消費狀態
			//CONSUME_SUCCESS 消費成功
			//RECONSUME_LATER 消費失敗,需要稍後重新消費
			return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
		}
	}
	);
	//調用start()方法啓動consumer
	consumer.start();
	System.out.println("Consumer Started.");
}
}

二、有序消息

有序消息就是按照一定的先後順序的消息類型。

舉個例子來說,producer 依次發送 order id 爲 1、2、3 的消息到 broker,consumer 接到的消息順序也就是 1、2、3 ,而不會出現普通消息那樣的 2、1、3 等情況。

那麼有序消息是如何保證的呢?我們都知道消息首先由 producer 到 broker,再從 broker 到 consumer,分這兩步走。那麼要保證消息的有序,勢必這兩步都是要保證有序的,即要保證消息是按有序發送到 broker,broker 也是有序將消息投遞給 consumer,兩個條件必須同時滿足,缺一不可。

進一步還可以將有序消息分成

  1. 全局有序消息
  2. 局部有序消息

之前我們講過,topic 只是消息的邏輯分類,內部實現其實是由 queue 組成。當 producer 把消息發送到某個 topic 時,默認是會消息發送到具體的 queue 上。

1. 全局有序

舉個例子,producer 發送 order id 爲 1、2、3、4 的四條消息到 topicA 上,假設 topicA 的 queue 數爲 3 個(queue0、queue1、queue2),那麼消息的分佈可能就是這種情況,id 爲 1 的在 queue0,id 爲 2 的在 queue1,id 爲 3 的在 queue2,id 爲 4 的在 queue0。同樣的,consumer 消費時也是按 queue 去消費,這時候就可能出現先消費 1、4,再消費 2、3,和我們的預期不符。那麼我們如何實現 1、2、3、4 的消費順序呢?道理其實很簡單,只需要把訂單 topic 的 queue 數改爲 1,如此一來,只要 producer 按照 1、2、3、4 的順序去發送消息,那麼 consumer 自然也就按照 1、2、3、4 的順序去消費,這就是全局有序消息。

由於一個 topic 只有一個 queue ,即使我們有多個 producer 實例和 consumer 實例也很難提高消息吞吐量。就好比過獨木橋,大家只能一個挨着一個過去,效率低下。

那麼有沒有吞吐量和有序之間折中的方案呢?其實是有的,就是局部有序消息。

2. 局部有序

我們知道訂單消息可以再細分爲訂單創建、訂單付款、訂單完成等消息,這些消息都有相同的 order id。同時,也只有按照訂單創建、訂單付款、訂單完成的順序去消費才符合業務邏輯。但是不同 order id 的消息是可以並行的,不會影響到業務。這時候就常見做法就是將 order id 進行處理,將 order id 相同的消息發送到 topicB 的同一個 queue,假設我們 topicB 有 2 個 queue,那麼我們可以簡單的對 id 取餘,奇數的發往 queue0,偶數的發往 queue1,消費者按照 queue 去消費時,就能保證 queue0 裏面的消息有序消費,queue1 裏面的消息有序消費。

由於一個 topic 可以有多個 queue,所以在性能比全局有序高得多。假設 queue 數是 n,理論上性能就是全局有序的 n 倍,當然 consumer 也要跟着增加纔行。在實際情況中,這種局部有序消息是會比全局有序消息用的更多。

示例代碼:

生產者

public class Producer {
	public static void main(String[] args) throws UnsupportedEncodingException {
		try {
			// 聲明並初始化一個producer
			// 需要一個producer group名字作爲構造方法的參數,這裏爲ordered_producer
			DefaultMQProducer orderedProducer = new DefaultMQProducer("ordered_producer");
			// 設置NameServer地址,此處應改爲實際NameServer地址,
			多個地址之間用;分隔
			//NameServer的地址必須有,但是也可以通過環境變量的方式設置,
			不一定非得寫死在代碼裏
			orderedProducer.setNamesrvAddr("10.1.54.121:
9876;10.1.54.122:9876");
			// 調用start()方法啓動一個producer實例
			orderedProducer.start();
			// 自定義一個tag數組
			String[] tags = new String[]{"TagA", "TagB",
			"TagC", "TagD", "TagE"};
			// 發送10條消息到Topic爲TopicTestOrdered,tag
			爲tags數組按順序取值,
			// key值爲“KEY”拼接上i的值,消息內容爲
			“Hello RocketMQ”拼接上i的值
			for (int i = 0; i < 10; i++) {
				int orderId = i % 10;
				Message msg =
				new Message("TopicTestOrdered", tags[i % tags.
				length], "KEY" + i,
				("Hello RocketMQ " + i).getBytes(RemotingHelper.
				DEFAULT_CHARSET));
				SendResult sendResult = orderedProducer.send(msg, new MessageQueueSelector() {
					// 選擇發送消息的隊列
					@Override
					public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
						// arg的值其實就是orderId
						Integer id = (Integer) arg;
						// mqs是隊列集合,也就是topic所對應的所有隊列
						int index = id % mqs.size();
						// 這裏根據前面的id對隊列集合大小求餘來返回所對應的隊列
						return mqs.get(index);
					}
				}
				, orderId);
				System.out.println(sendResult);
			}
			orderedProducer.shutdown();
		}
		catch (MQClientException e) {
			e.printStackTrace();
		}
		catch (RemotingException e) {
			e.printStackTrace();
		}
		catch (MQBrokerException e) {
			e.printStackTrace();
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

至於是要實現全局有序,還是局部有序,在此示例代碼中,就取決於 TopicTestOrdered 這個 Topic 的隊列數了。

消費者

public class Consumer {
	public static void main(String[] args) throws
	MQClientException {
		//聲明並初始化一個consumer
		//需要一個consumer group名字作爲構造方法的參數,
		這裏爲concurrent_consumer
		DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ordered_consumer");
		//同樣也要設置NameServer地址
		consumer.setNamesrvAddr("10.1.54.121:9876;10.1.54 .122:9876");
		//這裏設置的是一個consumer的消費策略
		//CONSUME_FROM_LAST_OFFSET 默認策略,從該隊列最尾開始消費,
		即跳過歷史消息
		//CONSUME_FROM_FIRST_OFFSET 從隊列最開始開始消費,
		即歷史消息(還儲存在broker的)全部消費一遍
		//CONSUME_FROM_TIMESTAMP 從某個時間點開始消費,
		和setConsumeTimestamp()配合使用,默認是半個小時以前
		consumer.setConsumeFromWhere(ConsumeFromWhere.
		CONSUME_FROM_FIRST_OFFSET);
		//設置consumer所訂閱的Topic和Tag
		consumer.subscribe("TopicTestOrdered", "TagA || TagC || TagD");
		//設置一個Listener,主要進行消息的邏輯處理
		//注意這裏使用的是MessageListenerOrderly這個接口
		consumer.registerMessageListener(new MessageListenerOrderly() {
			@Override
			public ConsumeOrderlyStatus consumeMessage ( List <MessageExt> msgs, ConsumeOrderlyContext context) {
				System.out.println(Thread.currentThread().getName() + " Receive New Messages: " + msgs);
				//返回消費狀態
				//SUCCESS 消費成功
				//SUSPEND_CURRENT_QUEUE_A_MOMENT 消費失敗,暫停當前隊列的消費
				return ConsumeOrderlyStatus.SUCCESS;
			}
		}
		);
		//調用start()方法啓動consumer
		consumer.start();
		System.out.println("Consumer Started.");
	}
}

三、延時消息

延時消息,簡單來說就是當 producer 將消息發送到 broker 後,會延時一定時間後才投遞給 consumer 進行消費。

RcoketMQ的延時等級爲:1s,5s,10s,30s,1m,2m,3m,4m,5m,6m,7m,8m,9m,10m,20m,30m,1h,2h。level=0,表示不延時。level=1,表示 1 級延時,對應延時 1s。level=2 表示 2 級延時,對應5s,以此類推。

這種消息一般適用於消息生產和消費之間有時間窗口要求的場景。比如說我們網購時,下單之後是有一個支付時間,超過這個時間未支付,系統就應該自動關閉該筆訂單。那麼在訂單創建的時候就會就需要發送一條延時消息(延時15分鐘)後投遞給 consumer,consumer 接收消息後再對訂單的支付狀態進行判斷是否關閉訂單。

設置延時非常簡單,只需要在Message設置對應的延時級別即可

Message msg = new Message("TopicTest",// topic
                        "TagA",// tag
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)// body
                );
                // 這裏設置需要延時的等級即可
                msg.setDelayTimeLevel(3);
                SendResult sendResult = producer.send(msg);

本文轉載自:有哪些Java程序員必知必會的RocketMQ消息類型

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