RocketMQ不遵循JMS规范,自己有一套自定义的机制,使用订阅主题的方式发送和接收任务,支持广播和集群两种消费模式。
集群模式:设置消费端对象属性:MessageModel.CLUSTERING,这种方式就可以达到ActiveMQ水平扩展负载均衡消费消息的实现。比较特殊的是这种行为可以支持先发送数据(生产端先发送到MQ),消费端订阅主题发生在生产端之后也可以收到数据,比较灵活。
广播模式:设置消费端对象属性:MessageModel.BROADCASTING,相当于生产端发送数据到MQ,多个消费端都可以获得数据。
在RocketMQ里有个很重要的概念,就是GroupName,无论是生产端还是消费端,都必须指定一个GroupName,这个组名称是维护在应用系统级别上。
比如生产端指定一个ProduccerGroupName,这个名称需要由应用系统来保证唯一性,一类Producer集合的名称,这类Producer通常发送一类消息,且发送逻辑一致。消费端同理。
Topic主题,每个主题表示一个逻辑上的存储概念,而在MQ上,会有着与之对应的多个Queue队列,这个是物理存储的概念。
RocketMQ提供了三种不同的producer:
1.NomalProducer 普通
2.OrderProducer 顺序
3.TransactionProducer 事务
1.普通模式:使用传统的send发送消息,不能保证消息的顺序一致性。
2.顺序模式:可以严格的保证消息的顺序执行。遵循全局顺序的时候使用一个queue,局部顺序使用多个queue并行消费。
3.事务模式:支持事务方式对消息进行提交处理,在rocket里事务分两个阶段。
第一个阶段把消息传给MQ,只不过消费端不可见,但数据其实已经在Broker上了。
第二个阶段为本地消息回调处理,如果都成功返回COMMIT_MESSAGE,则在broker上的数据对消费端可见,失败则为ROLLBACK_MESSAGE,消费端不可见。
顺序消费
//如果使用顺序消费,则必须自己实现MessageQueueSelector,保证消息进入同一个队列中
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> msgs, Message msg, Object arg) {
Integer id = (Integer)arg;
return msgs.get(id);
}
}, 0);
broker只保证消息是顺序发送到消费端,但若消费端是多线程的,可能收到的第二个消息会比第一个消息处理得更快。
//messageListenerOrderly 保证顺序消费,消费端接收的是同一个队列的消息,避免多线程时顺序错乱
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> arg0, ConsumeOrderlyContext arg1) {
return null;
}
});
consumer开启多线程,只需设置consumer的线程数。
consumer.setConsumeThreadMax(10);
consumer.setConsumeThreadMin(10);
事务
生产端先将凭证消息发送到broker服务器上,凭证消息对消费端不可见;再回调执行本地事务,若执行成功则返回COMMIT,broker再将凭证消息对消费端可见,若失败返回ROLLBACK。
Producer:
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
String group_name = "transaction_producer";
final TransactionMQProducer producer = new TransactionMQProducer(group_name);
producer.setNamesrvAddr("192.168.0.2:9876;192.168.0.3:9876");
producer.setCheckRequestHoldMax(200);
producer.setCheckThreadPoolMaxSize(20);
producer.setCheckThreadPoolMinSize(5);
producer.start();
//服务器回调producer,检查本地事务分支成功还是失败
producer.setTransactionCheckListener(new TransactionCheckListener() {
@Override
public LocalTransactionState checkLocalTransactionState(MessageExt msg) {
System.out.println("state --" + new String(msg.getBody()));
return LocalTransactionState.COMMIT_MESSAGE;
}
});
TransactionExecuterImpl tranExecuter = new TransactionExecuterImpl();
for(int i=0; i<2 ; i++) {
try {
Message msg = new Message("TopicTransaction","transaction" + i,"key",("hello " + i).getBytes());
SendResult sendResult = producer.sendMessageInTransaction(msg, tranExecuter, "tq");
System.out.println(sendResult);
}catch(Exception e) {
e.printStackTrace();
}
}
Thread.sleep(3000);
producer.shutdown();
}
}
TransactionExecuterImpl:
/*
* 执行本地事务,由客户端回调
*/
public class TransactionExecuterImpl implements LocalTransactionExecuter {
@Override
public LocalTransactionState executeLocalTransactionBranch(Message msg, Object arg) {
System.out.println("msg :" + new String(msg.getBody()));
System.out.println("arg :" + arg);
String tag = msg.getTags();
if(tag.equals("transaction1")) {
//这里有一个分阶段提交任务概念
System.out.println("这里处理业务逻辑,如操作数据库,失败情况下进行ROLLBACK");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
return LocalTransactionState.COMMIT_MESSAGE;
//return LocalTransactionState.UNKNOW;
}
}
Consumer:
public class Consumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("transaction_consumer");
//设置consumer第一次启动是从队列头部开始还是尾部开始消费,若非第一次启动,那么按照上次消费的位置继续消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.subscribe("TopicTransaction", "*");
//批量消费,一次消费多少条消息,默认为1条,最大情况能拿多少条不代表每次能拿这么多条
//consumer.setConsumeMessageBatchMaxSize(3);
//messageListenerOrderly 保证顺序消费,消费端接收的是同一个队列的消息,避免多线程时顺序错乱
/*consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> arg0, ConsumeOrderlyContext arg1) {
return null;
}
});*/
consumer.setConsumeThreadMax(10);
consumer.setConsumeThreadMin(10);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
//System.out.println(Thread.currentThread().getName() + "Receive: " + msgs);
//获取一次性消费多少条消息
//System.out.println("消息条数 : " + msgs.size());
MessageExt msg1 = null;
try {
for(MessageExt msg : msgs) {
msg1 = msg;
String topic = msg.getTopic();
String msgbody = new String(msg.getBody(),"utf-8");
String tag = msg.getTags();
System.out.println("收到消息: " + "topic:" + topic + " tags:" + tag + " msg:" + msgbody);
}
} catch (Exception e) {
e.printStackTrace();
//若已经重试了5次则不再重试
if(msg1.getReconsumeTimes() == 5) {
//此处记录日志操作。。。
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.setNamesrvAddr("192.168.0.2:9876;192.168.0.3:9876");
consumer.start();
System.out.println("Consumer started...");
}
}
Producer console:
msg :hello 0
arg :tq
SendResult [sendStatus=SEND_OK, msgId=C0A8000200002A9F0000000000003B1C, messageQueue=MessageQueue [topic=TopicTransaction, brokerName=broker-a, queueId=0], queueOffset=0]
msg :hello 1
arg :tq
这里处理业务逻辑,如操作数据库,失败情况下进行ROLLBACK
SendResult [sendStatus=SEND_OK, msgId=C0A8000200002A9F0000000000003C9E, messageQueue=MessageQueue [topic=TopicTransaction, brokerName=broker-a, queueId=1], queueOffset=0]
Consumer console :
收到消息: topic:TopicTransaction tags:transaction0 msg:hello 0