RocketMQ 入門
RocketMQ 入門
引言
發現寫過了RabbitMQ
和Kafka
,這次補上RocketMQ
,之後有空會講講這幾個訊息中介軟體的實際場景選型和選擇原因。
本篇文章不寫整合Springboot
,因為Springboot
你可能看不到這些細節
。
引入依賴
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>自己選個版本</version>
</dependency>
NameServer
這裡簡單說一下在RocketMQ
中的一些概念,RocketMQ
的broker
關係是靠NameServer
來維護的,這和Kafka
依賴於Zookeeper
的道理是一樣的,都是借用另外一箇中介軟體去實現服務發現(broker發現)
,而且RocketMQ
經常會和Kafka
做對比。
Topic 和 Tag
-
Topic
訊息主題
,通過Topic
對不同的業務訊息進行分類。 -
Tag
訊息標籤
,用來進一步區分
某個Topic下的訊息分類
,訊息從生產者發出即帶上的屬性。
阿里的適用場景
:
- 訊息型別是否一致:如普通訊息、事務訊息、定時(延時)訊息、順序訊息,不同的訊息型別使用不同的 Topic,無法通過 Tag 進行區分。
- 業務是否相關聯:沒有直接關聯的訊息,如淘寶交易訊息,京東物流訊息使用不同的 Topic 進行區分;而同樣是天貓交易訊息,電器類訂單、女裝類訂單、化妝品類訂單的訊息可以用 Tag 進行區分。
Producer 傳送訊息
同步訊息
訊息的Producer
作為訊息生產者
一直會等待到MQ
返回資訊,一般很重要的訊息會用到同步訊息。
DefaultMQProducer producer = new DefaultMQProducer("test_group");
producer.setNamesrvAddr("192.168.0.102:9876;192.168.0.111:9876");
producer.start();
Message msg = new Message("test_topic","sync_tag","message body".getBytes());
SendResult result = producer.send(msg);
//在result中我們可以獲得傳送狀態,訊息ID,佇列ID
SendStatus status = result.getSendStatus();
String msgId = result.GetMsgId(); //還可以獲取offsetMsgId msgId並非一定不重複
int queueId = result.getMessageQueue().getQueueId();
System.out.println("status:" + status + " msgId:" + msgId + " queueId:" + queueId);
producer.shutdown();
非同步訊息
非同步訊息
和同步訊息
的區別就不說了,API
的區別在於send的第2個引數
需要傳遞一個SendCallback
(需要注意的是send
方法過載了很多個
,第2個引數
也可以不是回撥,下面會寫順序訊息
)。
DefaultMQProducer producer = new DefaultMQProducer("test_group");
producer.setNamesrvAddr("192.168.0.102:9876;192.168.0.111:9876");
producer.start();
Message msg = new Message("test_topic","async_tag","async message body".getBytes());
CountDownLatch latch = new CountDownLatch(1);
producer.send(msg, new SendCallback(){
//成功
public void onSuccess(SendResult sendResult){
System.out.println("成功結果:" + sendResult);
latch.countDown();
}
//失敗
public void onException(Throwable e){
System.out.println("傳送錯誤" + e);
latch.countDown();
}
});
//避免主執行緒提前結束
latch.await();
producer.shutdown();
單向訊息
同步訊息
和非同步訊息
都可以算是雙向訊息
,因為無論成功
還是失敗
,都會有返回結果
,單向訊息
的意思就是只管發不管結果
,無論傳送成功
還是失敗
,都不關心(不會有返回結果),一般傳送日誌
會用到。
DefaultMQProducer producer = new DefaultMQProducer("test_group");
producer.setNamesrvAddr("192.168.0.102:9876;192.168.0.111:9876");
producer.start();
Message msg = new Message("test_topic","oneway_tag","oneway message body".getBytes());
//無返回值
producer.sendOneway(msg);
producer.shutdown();
Consumer 消費訊息
PushConsumer及廣播模式
我們先看看broker主動推訊息
的方式如何消費訊息
。
注意:在RabbitMQ
中想要不輪詢消費
必須不同佇列
,在Kafka
中必須group名不同
,但是RocketMQ
中就算同group同topic
也可以設定(setMessageModel)
為廣播模式
。
//broker主動push的方式
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("test_group");
consumer.setNamesrvAddr("192.168.0.102:9876;192.168.0.111:9876");
//topic和tag 訂閱 ,tag支援過濾,具體可以看看文件
//consumer.subscribe("test_topic","sync_tag");
//consumer.subscribe("test_topic","sync_tag || async_tag");
consumer.subscribe("test_topic","*");
//設定為廣播模式,如果不設定多個消費者 group相同且topic也相同會是輪詢模式。
consumer.setMessageModel(MessageModel.BROADCASTING);
//註冊監聽器
consumer.registerMessageListener(new MessageListenerConcurrently(){
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context){
System.out.println(msgs);
//返回消費成功狀態
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
進階
順序訊息
順序訊息就是字面意思,按照傳送順序來消費(FIFO先入先出)。
應用場景例如:狀態機
建立 付款 成功 失敗。相同訂單號進入同一個佇列,那麼消費時同一個佇列的訊息
就不會出現亂序
問題。
注意:Message ID
有可能出現衝突(重複)
的情況,所以真正需要冪等驗證
可以使用message.setKey("ORDERID");
,消費訊息
時可以使用 message.getKey()
來做冪等處理
。
順序Producer
DefaultMQProducer producer = new DefaultMQProducer("test_group");
producer.setNamesrvAddr("192.168.0.102:9876;192.168.0.111:9876");
producer.start();
//模擬3個訊息 1001為key(訂單號)
Message msgBuilding = new Message("order_topic", "order_tag", "1001" , "building...".getBytes());
Message msgPurchasing = new Message("order_topic", "order_tag", "1001", "Purchasing".getBytes());
Message msgSuccessfully = new Message("order_topic", "order_tag", "1001", "Successfully".getBytes());
Message msgBuilding2 = new Message("order_topic", "order_tag", "1002" , "building...".getBytes());
Message msgPurchasing2 = new Message("order_topic", "order_tag", "1002", "Purchasing".getBytes());
Message msgSuccessfully2 = new Message("order_topic", "order_tag", "1002", "Successfully".getBytes());
//建立一個佇列選擇器
MessageQueueSelector queueSelector = new MessageQueueSelector(){
/**
*
* @param mqs: 佇列集合
* @param msg: 訊息物件
* @param arg: 額外引數 producer.send如果第二個引數是佇列選擇器,那麼arg就是第三個引數
* @return: 佇列
*
*/
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, object arg){
Long orderId = (Long) arg;//這裡也可以使用msg.getKey();
int index = (int)(orderId % mqs.size()); //同餘演算法即可,相同訂單絕對會進入同一個佇列
return mqs.get(index); //返回選擇到的佇列
}
};
//下面3條訊息經過佇列選擇器會進入相同的佇列,因為他們的key(訂單號)是相同的
SendResult result = producer.send(msgBuilding, queueSelector, Long.parseLong(msgBuilding.getKey()));
System.out.println("msgBuilding res:" + result);
result = producer.send(msgPurchasing, queueSelector, Long.parseLong(msgBuilding.getKey()));
System.out.println("msgPurchasing res:" + result);
result = producer.send(msgSuccessfully, queueSelector, Long.parseLong(msgBuilding.getKey()));
System.out.println("msgSuccessfully res:" + result);
result = producer.send(msgBuilding2, queueSelector, Long.parseLong(msgBuilding.getKey()));
System.out.println("msgBuilding res:" + result);
result = producer.send(msgPurchasing2, queueSelector, Long.parseLong(msgBuilding.getKey()));
System.out.println("msgPurchasing res:" + result);
result = producer.send(msgSuccessfully2, queueSelector, Long.parseLong(msgBuilding.getKey()));
System.out.println("msgSuccessfully res:" + result);
producer.shutdown();
順序Consumer
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("test_group");
consumer.setNamesrvAddr("192.168.0.102:9876;192.168.0.111:9876");
consumer.subscribe("order_topic","*");
//注意訊息監聽器不是Concurrently了
consumer.registerMessageListener(new MessageListenerOrderly(){
//返回消費成功狀態 這裡也不是ConcurrentlyStatus了
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context){
for (MessageExt msg : msgs){
System.out.println(msg);
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
延遲訊息
不解釋了啊,之前在RabbitMQ
文章中已經寫了延遲訊息的意思。
需要注意的是:RocketMQ
的延遲時間不能自定義
,它只支援固定的一個級別(列表)
,延遲時間
必須是這個級別中
的一項(從1級開始)
,private string messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
,傳送者對訊息設定delayTimeLevel
,消費者沒有什麼要求。
DefaultMQProducer producer = new DefaultMQProducer("test_group");
producer.setNamesrvAddr("192.168.0.102:9876;192.168.0.111:9876");
producer.start();
Message msg = new Message("delay_topic","test_delay","message body".getBytes());
msg.setDelayTimeLevel(3);//設定訊息的延遲時間級別
SendResult result = producer.send(msg);
System.out.println("result:" + result);
producer.shutdown();
TransactionMQProducer 傳送事務訊息
TransactionMQProducer producer = new TransactionMQProducer("test_group");
producer.setNamesrvAddr("192.168.0.102:9876;192.168.0.111:9876");
producer.setTransactionListener(new TransactionListener(){
//執行本地事務方法
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg){
if ("tag1".equals(msg.getTags())){ // 如果訊息標籤為tag1就提交
return LocalTransactionState.COMMIT_MESSAGE;
}else if ("tag2".equals(msg.getTags())){ // 如果訊息標籤為tag2就回滾
return LocalTransactionState.ROLLBACK_MESSAGE;
}
return LocalTransactionState.UNKNOW; //否則返回未知,返回未知後會進入回查方法
}
//回查方法
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg){
System.out.println("checkLocalTransaction tag:" + msg.getTags());
return LocalTransactionState.COMMIT_MESSAGE; //回查後我們可以選擇回滾或提交
}
});
producer.start();
Message msg = new Message("transaction_topic","tag1","message body".getBytes());
Message msg2 = new Message("transaction_topic","tag2","message body".getBytes());
Message msg3 = new Message("transaction_topic","tag3","message body".getBytes());
// 第二個引數 在TransactionListener的executeLocalTransaction方法裡arg會被傳進去
SendResult result = producer.sendMessageInTransaction(msg, null);
System.out.println("result:" + result);
result = producer.sendMessageInTransaction(msg2, null);
System.out.println("result2:" + result);
result = producer.sendMessageInTransaction(msg3, null);
System.out.println("result3:" + result);
producer.shutdown();
相關文章
- RocketMQ入門MQ
- RocketMQ快速入門MQ
- Rocketmq 入門介紹MQ
- 十分鐘入門RocketMQMQ
- 訊息佇列之-RocketMQ入門佇列MQ
- RocketMQ系列一:入門級使用演示MQ
- rocketmq事務訊息入門介紹MQ
- 大家心心念唸的RocketMQ5.x入門手冊來嘍MQ
- 快速入門一篇搞定RocketMq-實現微服務實戰落地MQ微服務
- 入門入門入門 MySQL命名行MySql
- 如何入CTF的“門”?——所謂入門就是入門
- 何入CTF的“門”?——所謂入門就是入門
- scala 從入門到入門+
- makefile從入門到入門
- ACM入門之新手入門ACM
- RocketMqMQ
- RocketMQ(7)---RocketMQ順序消費MQ
- RocketMQ(5)---RocketMQ重試機制MQ
- 【小入門】react極簡入門React
- gRPC(二)入門:Protobuf入門RPC
- 【RocketMq】商用RocketMq和開源RocketMq的相容問題解決方案MQ
- 【RocketMQ】RocketMQ儲存結構設計MQ
- 《Flutter 入門經典》之“Flutter 入門 ”Flutter
- 新手入門,webpack入門詳細教程Web
- Android入門教程 | RecyclerView使用入門AndroidView
- linux新手入門――shell入門(轉)Linux
- rocketmq配置MQ
- rocketMQ一MQ
- rocketmq 概念MQ
- MyBatis從入門到精通(一):MyBatis入門MyBatis
- SqlSugar ORM 入門到精通【一】入門篇SqlSugarORM
- Storm入門指南第二章 入門ORM
- VUE入門Vue
- MyBatis 入門MyBatis
- CSS 入門CSS
- JavaScript 入門JavaScript
- Nginx 入門Nginx
- RabbitMQ入門MQ