RocketMq是阿里巴巴2016年開源的,貢獻給了apache,用java語言開發
優點:
1、解耦
2、削峯
3、數據分發
缺點:
1、系統可用性降低
如果MQ宕機了,就會造成影響
2、系統複雜性提高
引入消息同步問題,順序問題,消息丟失問題
3、一致性問題
如果消息訂閱方一個系統處理失敗了,幾個訂閱方的數據就會造成不一致
市場上各種MQ比較
ActiveMQ RabbitMQ RocketMq kafka
安裝
去官網下載RocketMq
解壓後啓動
> nohup sh bin/mqnamesrv &
> tail -f ~/logs/rocketmqlogs/namesrv.log
The Name Server boot success...
> nohup sh bin/mqbroker -n localhost:9876 &
> tail -f ~/logs/rocketmqlogs/broker.log
The broker[%s, 172.30.30.233:10911] boot success...
注:啓動時如果出現啓動不成功
去bin/nohup.out查看錯誤原因
ERROR: Please set the JAVA_HOME variable in your environment, We need java(x64)! !!
停止
> sh bin/mqshutdown broker
The mqbroker(36695) is running...
Send shutdown request to mqbroker(36695) OK
> sh bin/mqshutdown namesrv
The mqnamesrv(36664) is running...
Send shutdown request to mqnamesrv(36664) OK
角色
nameServer是broker的管理者
broker暫存和傳輸消息,主動上報nameServer
producer生產者發送消息,詢問nameServer,指定broker
consumer消費者消費消息,詢問nameServer,指定broker
topic區分消息的種類
messageQueue相當於Topic的分分區,用於並行發送和接收消息
集羣搭建
nameServer集羣是無狀態的,每個節點內容都是一樣的。
broker會給每一個nameServer節點上報信息
broker集羣中主節點負責寫,讀負責讀
broker的master和slave節點通過brokerName確定是否爲同一組, id爲0是主節點,其他爲從節點
producer會和NameServer集羣中一個隨機節點建立長連接,定期從nameServer取topic路由信息,並向提供topic服務的Mater建立長連接,定時向master發送心跳。producer是完全無狀態的,可集羣部署
consumer會和NameServer集羣中一個隨機節點建立長連接,定期從nameServer取topic路由信息,並向提供topic服務的Mater、slave建立長連接,定時向master、slave發送心跳。consumer既可以從master訂閱消息,也可以從slave訂閱消息,訂閱規則由broker決定
主要介紹broker集羣模式
1、單master模式
2、多master模式
3、多master和多slave(異步)
4、多master和多slave(同步)
集羣工作流程
1、啓動NameServer,等待broker、producer、consumer連上來。
2、啓動broker,與nameServer建立長連接。發送心跳包,心跳包中包含當前broker信息(broker的IP+端口以及存儲的所有topic信息),註冊成功後,NameServer中就要broker和Topic的映射關係了
3、手法消息前,創建topic,指定該topic要存儲在哪些broker上,也可以在發送消息時自動創建
4、producer發送消息,啓動時先跟nameServer集羣中的一個節點建立長連接,獲取當前發送的topic存在哪些broker上,輪詢隊列,選擇一個隊列,然後與該隊列所在的broker建立長連接,從而向broker發送消息
5、consumer跟producer相似,從NameServer中一個節點建立長連接,然後確定當前訂閱的topic所在的broker,然後直接建立連接,開始消費
集羣搭建
注意點,broker配置文件設置,根據選擇的集羣模式,選擇對應的配置文件,然後用對應的配置文件啓動集羣
rocketMq-console集羣監控。
在git上下載代碼,修改NameServer配置後,打包後放到集羣服務上。然後java -jar ***.jar後啓動
消息發送者步驟
1、創建生產者producer,定製組名
2、指定NameServer地址
3、啓動producer
4、創建消息對象,指定主題topic、tag和消息體
5、發送消息
6、關閉生產者
消息消費者步驟
1、創建消費者consumer,指定組名
2、指定NameServer地址
3、訂閱主題topic和tag
4、設置回調函數,處理消息
5、啓動消費者consumer
案例
添加依賴
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.3.0</version>
</dependency>
同步發送
生產者
public class SyncProducer {
public static void main(String[] args) throws Exception {
//Instantiate with a producer group name.
DefaultMQProducer producer = new
DefaultMQProducer("please_rename_unique_group_name");
// Specify name server addresses.
producer.setNamesrvAddr("localhost:9876");
//Launch the instance.
producer.start();
for (int i = 0; i < 100; i++) {
//Create a message instance, specifying topic, tag and message body.
Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " +
i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
//Call send message to deliver message to one of brokers.
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
}
//Shut down once the producer instance is not longer in use.
producer.shutdown();
}
}
異步發送
生產者
public class AsyncProducer {
public static void main(String[] args) throws Exception {
//Instantiate with a producer group name.
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
// Specify name server addresses.
producer.setNamesrvAddr("localhost:9876");
//Launch the instance.
producer.start();
producer.setRetryTimesWhenSendAsyncFailed(0);
for (int i = 0; i < 100; i++) {
final int index = i;
//Create a message instance, specifying topic, tag and message body.
Message msg = new Message("TopicTest",
"TagA",
"OrderID188",
"Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.printf("%-10d OK %s %n", index,
sendResult.getMsgId());
}
@Override
public void onException(Throwable e) {
System.out.printf("%-10d Exception %s %n", index, e);
e.printStackTrace();
}
});
}
//Shut down once the producer instance is not longer in use.
producer.shutdown();
}
}
單向發送
生產者
public class OnewayProducer {
public static void main(String[] args) throws Exception{
//Instantiate with a producer group name.
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
// Specify name server addresses.
producer.setNamesrvAddr("localhost:9876");
//Launch the instance.
producer.start();
for (int i = 0; i < 100; i++) {
//Create a message instance, specifying topic, tag and message body.
Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " +
i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
//Call send message to deliver message to one of brokers.
producer.sendOneway(msg);
}
//Shut down once the producer instance is not longer in use.
producer.shutdown();
}
}
消費者
public class Consumer {
public static void main(String[] args) throws InterruptedException, MQClientException {
// Instantiate with specified consumer group name.
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name");
// Specify name server addresses.
consumer.setNamesrvAddr("localhost:9876");
// Subscribe one more more topics to consume.
consumer.subscribe("TopicTest", "*");
// Register callback to execute on arrival of messages fetched from brokers.
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//Launch the consumer instance.
consumer.start();
System.out.printf("Consumer Started.%n");
}
}
消費者廣播模式和負載均衡模式
設置模式
consumer.setMessageModel(MessageModel.CLUSTERING);//默認爲負載均衡模式
consumer.setMessageModel(MessageModel.BROADCASTING);//廣播模式
保證消息順序
例如:訂單的順序流程:創建、付款、推送、完成,訂單號相同的消息會被先後發送到同一個隊列中,消費時,同一個orderId獲取到的肯定是同一個隊列.
生產者添加如下發送代碼
//根據orderId取模順序將相同orderId消息發放送到同一個隊列
int orderId = 10001;//模擬一個訂單ID
producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
int id = (int)o;
int i = id%list.size();
return list.get(i);
}
},orderId);
消費者添加如下代碼
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), list);
return ConsumeOrderlyStatus.SUCCESS;
}
});
預定消息(也叫延遲消息)
//組裝消息體
Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " +
i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
//設置延時等級
msg.setDelayTimeLevel(1);
批量發送消息
List<Message> messageList = new ArrayList();
//構建多條消息
Message msg1 = new Message();
Message msg2 = new Message();
Message msg3 = new Message();
messageList.add(msg1);
messageList.add(msg2);
messageList.add(msg3);
當批量發送消息過大,如郵件大小不超過超過1M則用如下代碼分割
public class ListSplitter implements Iterator<List<Message>> {
private final int SIZE_LIMIT = 1000 * 1000;
private final List<Message> messages;
private int currIndex;
public ListSplitter(List<Message> messages) {
this.messages = messages;
}
@Override public boolean hasNext() {
return currIndex < messages.size();
}
@Override public List<Message> next() {
int nextIndex = currIndex;
int totalSize = 0;
for (; nextIndex < messages.size(); nextIndex++) {
Message message = messages.get(nextIndex);
int tmpSize = message.getTopic().length() + message.getBody().length;
Map<String, String> properties = message.getProperties();
for (Map.Entry<String, String> entry : properties.entrySet()) {
tmpSize += entry.getKey().length() + entry.getValue().length();
}
tmpSize = tmpSize + 20; //for log overhead
if (tmpSize > SIZE_LIMIT) {
//it is unexpected that single message exceeds the SIZE_LIMIT
//here just let it go, otherwise it will block the splitting process
if (nextIndex - currIndex == 0) {
//if the next sublist has no element, add this one and then break, otherwise just break
nextIndex++;
}
break;
}
if (tmpSize + totalSize > SIZE_LIMIT) {
break;
} else {
totalSize += tmpSize;
}
}
List<Message> subList = messages.subList(currIndex, nextIndex);
currIndex = nextIndex;
return subList;
}
}
//then you could split the large list into small ones:
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
try {
List<Message> listItem = splitter.next();
producer.send(listItem);
} catch (Exception e) {
e.printStackTrace();
//handle the error
}
}
消息過濾
tag過濾
消費者在消費的時候,指定tag
//同時消費tag1和tag2
consumer.subscribe("topicTest","tag1 || tag2");
//消費所有topic爲“topicTest”消息
consumer.subscribe("topicTest","*");
sql語法過濾
//生產者自定義消息屬性
msg.putUserProperty("a",String.valueOf(i));
//消費者根據條件過濾消息去消費
consumer.subscribe("TopicTest", MessageSelector.bySql("a>5"));//根據自定義條件過濾消息
事務消息
應用
1,交易狀態
事務消息有三種狀態:
(1)TransactionStatus.CommitTransaction:提交事務,表示允許使用者使用此消息。
(2)TransactionStatus.RollbackTransaction:回滾事務,表示該消息將被刪除且不允許使用。
(3)TransactionStatus.Unknown:中間狀態,表示需要MQ進行回溯以確定狀態。
2,發送交易信息
創建事務生產方
使用TransactionMQProducer類創建生產方客戶端,並指定唯一的producerGroup。執行本地事務後,需要根據執行結果對MQ進行回覆,回覆狀態如上節所述。
package com.qing.zhao.common;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
public class TransactionMqProducer {
public static void main(String[] args) throws Exception {
TransactionMQProducer mqProducer = new TransactionMQProducer("group");
mqProducer.setNamesrvAddr("localhost:9876");
mqProducer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
if (StringUtils.equals("tag1",message.getTags())){
return LocalTransactionState.COMMIT_MESSAGE;
}else if (StringUtils.equals("tag2",message.getTags())){
return LocalTransactionState.ROLLBACK_MESSAGE;
}else if (StringUtils.equals("tag3",message.getTags())){
return LocalTransactionState.UNKNOW;
}
return LocalTransactionState.UNKNOW;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
System.out.println("消息的tag爲:"+messageExt.getTags());
return LocalTransactionState.COMMIT_MESSAGE;
}
});
String[] tag = new String[]{"tag1","tag2","tag3"};
mqProducer.start();
for (int i=0; i<3; i++){
Message msg = new Message("TopicTest" /* Topic */,
tag[i] /* Tag */,
("Hello RocketMQ "+i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
mqProducer.sendMessageInTransaction(msg,null);
}
}
}