一、消息发送样例

•导入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)发送事务消息

流程分析

pular消息队列 功能 消息队列使用教程_pular消息队列 功能


上图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。

补充:什么是事务补偿?
事务的逆操作序列。用于恢复时撤销该事务的运行对数据库产生的影响。

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 的最终一致性常用模型:

pular消息队列 功能 消息队列使用教程_消息发送_02


【还有很多不足,逻辑上还没来得及梳理,待填坑!!!】