RocketMQ特性
脑图
组成
生产者和消费者
broker
nameserver
topic
RocketMQ事务消息
事务消息的三个接口
生产者和监听器代码
基于事务消息设计可靠消息
RocketMQ特性
- Java开发的MQ,虽然源码中中文注释不多但是也能看。
- 和rabbitMQ相比,RocketMQ整体结构比较简单
- 使用
topic
区分消息 broker上topic的queue
中存放的是消息的索引,topic
多了并不影响性能- 很容易就能实现
顺序消息、延迟消息、事务消息(可靠消息)
脑图
组成
生产者和消费者
…
broker
存放消息的,有多台是主从结构。
生产者把消息发送给broker
后,broker
会把消息写到文件中。broker
根据消息的topic
区分消息并把消息的地址
放到对应的queue
中。
默认情况下一个topic
分四个queue
,每个queue
可以连接一个消费者
nameserver
nameserver
保存的是broker,topic,生产者,消费者
的信息。
nameserver可以有多台,不是主从结构互不通信,每一台nameserver保存的都是全量信息,可以独立对外提供服务
topic
topic是一个很重要的概念,topic既是消息类的一个属性,也是broker的一个逻辑上的组件。
RocketMQ事务消息
事务消息:事务消息发送到broker(消息服务器)后不能马上被消费者消费,需要broker调用生产者的接口并拿到COMMIT_MESSAGE
的结果后才能被消费者消费。
事务消息的三个接口
sendMessageInTransaction
:生产者发送半事务消息executeLocalTransaction
:TransactionListener
接口中的方法,由生产者实现。消息已经成功发送到了服务器,消息服务器会回调用这个接口。生产者需要返回COMMIT_MESSAGE、 ROLLBACK_MESSAGE、 UNKNOW、或不返回(生产者异常,网络异常等)checkLocalTransaction
:TransactionListener
接口中的方法,由生产者实现。executeLocalTransaction
方法没有给消息服务器返回COMMIT_MESSAGE
或ROLLBACK_MESSAGE
状态时(也可能是网络问题导致消息服务器没有收到),消息服务器会调用这个接口。生产者仍需要返回消息状态(同上)
消息发送到broker:半消息(消息不能被消费)
executeLocalTransaction:返回COMMIT_MESSAGE
消息可以被消费,返回ROLLBACK_MESSAGE
消息被删除,返回UNKNOW
不确定需要调用checkLocalTransaction
去确认
checkLocalTransaction:直到返回COMMIT_MESSSAGE
或ROLLBACK_MESSAGE
,时间太长没有拿到commit或rollback
消息也会被删除
代码
/**
* 使用 TransactionMQProducer类创建生产者,并指定唯一的 ProducerGroup,就可以设置自定义线程池来处理这些检查请求。
* 执行本地事务后、需要根据执行结果对消息队列进行回复。回传的事务状态在请参考前一节。
*/
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException, IOException {
TransactionListener transactionListener = new TransactionListenerImpl();
TransactionMQProducer producer = new TransactionMQProducer(RocketConst.producer_group);
ExecutorService executorService = Executors.newFixedThreadPool(10);
producer.setExecutorService(executorService);
producer.setTransactionListener(transactionListener);
producer.start();
try {
// topic, tag, body
Message message = new Message(RocketConst.topic, RocketConst.tag,
("json消息可以反序列化为一个实体类").getBytes(RemotingHelper.DEFAULT_CHARSET));
message.setKeys("全局唯一和业务相关");
message.setTransactionId(""); // 这里设置没有用,消息服务器会重新设置的
SendResult sendResult = producer.sendMessageInTransaction(message, null);
System.out.printf("%s%n", sendResult);
} catch (MQClientException | UnsupportedEncodingException e) {
e.printStackTrace();
}
// 等待消息服务器回调 transactionListener
System.in.read();
}
}
public class TransactionListenerImpl implements TransactionListener {
private final AtomicInteger transactionIndex = new AtomicInteger(0);
private final ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();
/**
* 消息发送到了服务器回调这个方法(此时消息并不能被消费)
* 此方法返回: LocalTransactionState.COMMIT_MESSAGE 消息可以被消费
* 此方法返回: LocalTransactionState.ROLLBACK_MESSAGE 消息需要被作废(本地事务失败)
* 此方法返回: LocalTransactionState.UNKNOW 现在结果不明,待会在确认
* (本地处理时间较长可以返回UNKNOW,一段时间后服务器会调用checkLocalTransaction方法 再次确认)
*/
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 获取事务id(businessId),本地业务处理决定事务是否提交
String msgKeys = msg.getKeys();
if("全局唯一".equals(msgKeys)) {
throw new RuntimeException("抛异常,让服务器调用确认方法");
}
// (commit)确定半消息,消息状态变为TransactionStatus.CommitTransaction,可以被消费
// 如果本地事务需要的时间较长,这里可以先返回UNKNOW
return LocalTransactionState.UNKNOW;
}
/**
* 上一个方法(executeLocalTransaction)没有返回任何信息
* 或返回的状态不是(COMMIT_MESSAGE或ROLLBACK_MESSAGE),broker会调用这个方法确认
*/
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
System.out.println(msg.getTransactionId());
return LocalTransactionState.COMMIT_MESSAGE;
}
}
基于事务消息的可靠消息
可靠消息:本地事务落库,消息成功发送到服务器并且能够被消费者消费。
- 1、
sendMessageInTransaction
发送消息 - 2、
executeLocalTransaction
提交本地事务,即保存transactionId、keys、消息体。落库成功返回COMMIT_MESSAGE
,长事务不能马上拿到结果则返回UNKNOW
, 落库失败返回ROLLBACK_MESSAGE
, 网络异常也没关系 - 3、
checkLocalTransaction
如果executeLocalTransaction
没有返回或返回了UNKNOW
,消息服务器会调用这个方法。重新查询事务结果(根据transactionId)并返回结果(同上)
他类型的消息
延迟消息和批量消息
https://rocketmq.apache.org/docs/%E7%94%9F%E4%BA%A7%E8%80%85/07message3
https://rocketmq.apache.org/docs/%E7%94%9F%E4%BA%A7%E8%80%85/08message4
- 延迟消息设置消息属性指定延迟等级就行了
message.setDelayTimeLevel(6);
; - 批量消息
SendResult send(Collection<Message> msgs)
,多个message放到集合就行了(topic相同)
顺序消息
RocketMQ是一个queue连接一个消费者,如果想让消息有序消费则需要把几条消息先后发送到同一个queue中,
这样就能保证这几条消息按照发送的先后顺序进行消费了。
把几个有序的消息发送到同一个queue
,指定发送到那个queue
生产者代码
int queueIndex = 1; // 要把消息发送到那个queue
SendResult sendResult1 = producer.send(message1, new ProducerQueueSelector(), queueIndex);
SendResult sendResult2 = producer.send(message2, new ProducerQueueSelector(), queueIndex);
SendResult sendResult3 = producer.send(message3, new ProducerQueueSelector(), queueIndex);
MessageQueueSelector代码
public class ProducerQueueSelector implements MessageQueueSelector {
/**
* mqs: topic下所有的队列
* msg: 消息
* arg: 生产者传递的消息
* 返回一个queue,msg会被发送到这个queue
*/
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
// 这里的arg就是生产者传递过来的queueIndex
return mqs.get((int) arg);
}
}