一、消息发送样例
•导入MQ客户端依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>
•消息发送者步骤分析
1.创建消息生产者producer,并制定生产者组名
2.指定Nameserver地址
3.启动producer
4.创建消息对象,指定主题Topic、Tag和消息体
5.发送消息
6.关闭生产者producer
•消息消费者步骤分析
1.创建消费者Consumer,制定消费者组名
2.指定Nameserver地址
3.订阅主题Topic和Tag
4.设置回调函数,处理消息
5.启动消费者consumer
二. 消息发送
1)发送同步消息
客户端即消息生产者,给MQSever发送消息,发送完消息后,客户端的线程会阻塞,直到收到MQServer回传的结果,客户端程序才会继续往下执行。
发送同步消息可靠性高,因为它一定会得到一个发送的结果。
public class SyncProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
//1.创建DefaultMQProducer(并设置生产组名)
DefaultMQProducer producer=new DefaultMQProducer("demo_producer_group");
//2.设置Nameserver地址(填写配置文件中对应的地址,可以是一个集群)
producer.setNamesrvAddr("192.168.25.135:9876;192.168.25.138:9876");
//3.开启DefaultMQProducer
producer.start();
for(int i=0;i<5;i++){
//4.创建消息Message String topic, String tags,byte[] body
Message message = new Message("Topic_Demo",//主题
"Tag1",//标签(主要用于消息过滤)
("hello!"+i++).getBytes(RemotingHelper.DEFAULT_CHARSET)//消息内容
);
//5.发送消息
SendResult result = producer.send(message);
//线程睡1秒
TimeUnit.SECONDS.sleep(1);
}
// 6.关闭DefaultMQProducer
producer.shutdown();
}
}
2)发送异步消息
生产者将消息发送给MQ后,不会等待MQ回传的结果,会继续往下执行。异步消息的可靠性没有同步消息的高。
异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应。
public class AsyncProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
//1.创建DefaultMQProducer(并设置生产组名)
DefaultMQProducer producer=new DefaultMQProducer("demo_producer_group");
//2.设置Nameserver地址(填写配置文件中对应的地址,可以是一个集群)
producer.setNamesrvAddr("192.168.25.135:9876;192.168.25.138:9876");
//3.开启DefaultMQProducer
producer.start();
for(int i=0;i<5;i++){
//4.创建消息Message String topic, String tags,byte[] body
Message message = new Message("Topic_Demo",//主题
"Tag2",//标签(主要用于消息过滤)
("hello!"+i++).getBytes(RemotingHelper.DEFAULT_CHARSET)//消息内容
);
//5.发送异步消息
producer.send(message, new SendCallback() {
/*
* 发送成功回调函数
* */
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送结果:"+sendResult);
}
/*
* 发送失败回调函数
* */
@Override
public void onException(Throwable throwable) {
System.out.println("发送异常:"+throwable);
}
});
//线程睡1秒
TimeUnit.SECONDS.sleep(1);
}
// 6.关闭DefaultMQProducer
producer.shutdown();
}
}
public class AsyncConsumer {
public static void main(String[] args) throws MQClientException {
// 1.创建DefaultMQPushConsumer(指定消费组组名)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("demo_consumer_group");
//2.设置Nameserver地址(跟生产者Nameserver地址一致)
consumer.setNamesrvAddr("192.168.25.135:9876;192.168.25.138:9876");
//3.订阅主题Topic和Tag
consumer.subscribe("Topic_Demo",//指定所要消费的主题
"*"//过滤规则(*为获取所有标签)
);
// 4.设置回调函数,处理信息
consumer.setMessageListener(new MessageListenerConcurrently() {
//接受消息内容
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,ConsumeConcurrentlyContext context) {
for(MessageExt msg:msgs){
System.out.println(new String(msg.getBody()));
}
//消息消费完成
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
);
//5.开启Consumer
consumer.start();
}
}
3)发送事务消息
流程分析
上图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。
补充:什么是事务补偿?
事务的逆操作序列。用于恢复时撤销该事务的运行对数据库产生的影响。
1. 事务消息发送及提交
1、MQ发送方发送远程事务消息到MQ Server;
2、MQ Server给予响应, 表明事务消息已成功到达MQ Server.
3、MQ发送方Commit本地事务.
4、若本地事务Commit成功, 则通知MQ Server允许对应事务消息被消费; 若本地事务失败, 则通知MQ Server对应事务消息应被丢弃.
5、若MQ发送方超时未对MQ Server作出本地事务执行状态的反馈, 那么需要MQ Servfer向MQ发送方主动回查事务状态, 以决定事务消息是否能被消费.
6、当得知本地事务执行成功时, MQ Server允许MQ订阅方消费本条事务消息.
2. 事务补偿
(1) 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
(2) Producer收到回查消息,检查回查消息对应的本地事务的状态
(3) 根据本地事务状态,重新Commit或者Rollback
其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。
3. 事务消息状态
事务消息共有三种状态,提交状态、回滚状态、中间状态:
•TransactionStatus.CommitTransaction: 提交事务,它允许消费者消费此消息。
•TransactionStatus.RollbackTransaction: 回滚事务,它代表该消息将被删除,不允许被消费。
•TransactionStatus.Unknown: 中间状态,它代表需要检查消息队列来确定状态。
4 . 创建事务性生产者
消息发送有这么几个步骤:
1.创建消息生产者Producer,并指定生产者组名
2.设置Nameserver地址
3.启动Producer
4.创建消息对象Message,指定主题Topic、Tag和消息体
5.发送消息
6.关闭生产者Producer
public class TransactionProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
//1.创建TransactionMQProducer(并设置生产组名)
TransactionMQProducer producer=new TransactionMQProducer("demo_producer_group");
//2.设置Nameserver地址(填写配置文件中对应的地址)
producer.setNamesrvAddr("192.168.25.135:9876;192.168.25.138:9876");
//添加事务监听器,用于执行本地事务和消息回查【执行本地事务的入口】
TransactionListener transactionListener=new TransactionListenerImpl();
producer.setTransactionListener(transactionListener);
//线程池(多线程操作)
new ThreadPoolExecutor(
2,
5,
100,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(
2000),
new ThreadFactory(){
public Thread newThread(Runnable runnable){
Thread thread=new Thread(runnable);
thread.setName("client-transaction");
return thread;
}
}
);
//3.开启TransactionMQProducer
producer.start();
String[] tags={"TAGA","TAGB","TAGC"};
for(int i=0;i<3;i++){
//4.创建消息Message String topic, String tags, byte[] body
Message message = new Message("Topic_Transaction_Demo",//主题
tags[i],//标签(主要用于消息过滤)
"hello!-Transaction".getBytes(RemotingHelper.DEFAULT_CHARSET)
);
//5.发送事务消息(可以对某条消息进行事务控制,也可以对整个producer进行事务控制)
TransactionSendResult result = producer.sendMessageInTransaction(message, "hello-transaction");
System.out.println(result);
}
// 6.关闭DefaultMQProducer
//producer.shutdown();
}
}
5. 实现事务的监听接口
当发送半消息成功时,我们使用 executeLocalTransaction 方法来执行本地事务。它返回前一节中提到的三个事务状态之一。checkLocalTranscation 方法用于检查本地事务状态,并回应消息队列的检查请求。它也是返回前一节中提到的三个事务状态之一。
public class TransactionListenerImpl implements TransactionListener {
/****
* 执行本地事务
* @param msg
* @param o
* @return
*/
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object o) {
if(StringUtils.equals("TAGA",msg.getTags()))
return LocalTransactionState.COMMIT_MESSAGE;
else if(StringUtils.equals("TAGB",msg.getTags()))
return LocalTransactionState.ROLLBACK_MESSAGE;
else if(StringUtils.equals("TAGC",msg.getTags()))
return LocalTransactionState.UNKNOW;
return LocalTransactionState.UNKNOW;
}
/****
* 消息回查,及检测你当前事务id到底是执行成功还是执行失败
* @param msg
* @return
*/
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
System.out.println("消息的Tag:"+msg.getTags());
return LocalTransactionState.UNKNOW;
}
}
6. 创建事务性消费者
public class TransactonConsumer {
public static void main(String[] args) throws MQClientException {
// 1.创建DefaultMQPushConsumer(指定消费组组名)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("demo_consumer_group");
//2.设置Nameserver地址(跟生产者Nameserver地址一致)
consumer.setNamesrvAddr("192.168.25.135:9876;192.168.25.138:9876");
//设置消息拉取最大数
consumer.setConsumeMessageBatchMaxSize(2);
//3.设置subscribe,这里是要读取的主题信息
consumer.subscribe("Topic_Demo",//指定所要消费的主题
"*"//过滤规则(*为获取所有标签)
);
// 4.创建消息监听MessageListener
consumer.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//5.获取消息信息
//迭代消息信息
for(Message msg : msgs){
try {
//获取主题
String topic = msg.getTopic();
//获取标签
String tags = msg.getTags();
//获取信息
String result = new String(msg.getBody(), RemotingHelper.DEFAULT_CHARSET);
System.out.println("Consumer消费信息—topic:"+topic+",tags:"+tags+",result:"+result);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
//消息发送失败之后,重试
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
//6.返回消息读取状态
//消息消费完成
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//开启Consumer
consumer.start();
}
}
基于事务消息的最终一致性
由多个独立的本地事务和事务消息实现最终一致性,事务消息保证成功的情况下投递,失败时不投递,超时未知的情况下check,具体的实现方式不固定,常用的策略是一个唯一业务标识和幂等操作,下图是基于事务 MQ 的最终一致性常用模型:
【还有很多不足,逻辑上还没来得及梳理,待填坑!!!】