消息队列——RocketMQ示例

消息队列——RocketMQ示例

1. 简介

  • 定位:分布式消息中间件、消息队列
  • 语言:Java
  • 性能:10万级吞吐量,ms级时效性
  • 可靠性:分布式架构,可靠性非常高
  • 其他:由阿里在2016年贡献至Apache基金会,已成为顶级项目。历经双十一考验,能够处理万亿级别的消息。

2. 集群架构与工作流程

  • 集群架构示意图(来自互联网)
    集群架构示意图
  • 工作流程
    • 首先,启动NameServer集群,负责管理Broker、Producer、Consumer的连接、负责管理Topic的元信息。(作用类似于Kafka中的ZooKeeper)
    • 接着,启动Broker集群,注册到NameServer集群中,保持心跳。一个集群可以由多个Broker组组成,一个Broker组(由BrokerName标识)包括Master、Slave(由BrokerId标识),Master负责接收生产的数据,Slave负责备份Master的数据。(同Kafka差异较大)
      • 同步:数据生产到Master,并将数据同步到Slave后,才向生产端回应Ack
      • 异步:数据生产到Master后,马上向生产端回应Ack,不管Slave数据是否已经同步
    • 创建一个Topic,Topic可以手动指定将数据存储到哪些Broker,也可以自动分配。(没有Topic的话,发送时也能自动创建)
    • Producer生产数据时,会与NameServer集群保持长连接,定期获取对应Topic的信息,找到对应Master节点并发送数据。注意,Producer只能向Broker中的Master生产数据。
    • Consumer消费数据时,会与NameServer集群保持长连接,定期获取对应Topic的信息,找到对应Master、Slave节点并获取数据(包括Push、Pull)。注意,Consumer可以从Master或Slave消费数据(由Broker配置决定)。
      • Push方式: 由Broker推消息到Consumer
      • Pull方式: 由Consumer主动从Broker拉数据
    • 消息顺序:一个Broker内有多个MessageQueue,消息在一个MessagQueue内是有序的。(和Kafka类同,Kafka的消息在一个Partition内有序)

3. 简单示例

  • Maven依赖导包
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-client</artifactId>
        <version>4.6.0</version>
    </dependency>
    
  • 生产者 (同步、异步、单向)
    import org.apache.rocketmq.client.producer.DefaultMQProducer;
    import org.apache.rocketmq.client.producer.SendCallback;
    import org.apache.rocketmq.client.producer.SendResult;
    import org.apache.rocketmq.common.message.Message;
    
    /**
     * Description: RocketMQ 简单的Producer示例 (同步、异步、单向)
     * <br/>
     * Date: 2020/1/6 17:28
     *
     * @author ALion
     */
    public class SimpleProducer {
    
        public static void main(String[] args) throws Exception {
            // 创建Producer
            // 实例化Producer对象,并指定生产者组名为producer_name
            DefaultMQProducer producer = new DefaultMQProducer("producer_name");
            // 指定NameServer集群地址,多个用;分隔
            producer.setNamesrvAddr("192.168.1.110:9876;192.168.1.111:9876;192.168.1.112:9876");
            // 启动Producer
            producer.start();
    
            // 开始生产数据
            for (int i = 0; i < 100; i++) {
                // 构建消息对象,包括Topic、Tag、MessageBody
                // Topic: 主题
                // Tag: 一个主题下面可以分多个Tag。可以理解为一个业务功能(Topic)下有多种消息,Tag用于对消息分类。
                // MessageBody: 你要发送的消息,因为网络传输需要字节码,所以要转换一下
                Message msg = new Message(
                        "Topic_Test",
                        "Tag_A",
                        ("Hello RocketMQ " + i).getBytes()
                );
    
                // 【同步发送方式】
                // 发送消息到RocketMQ集群
                SendResult sendResult = producer.send(msg);
                // 打印结果
                System.out.printf("%s%n", sendResult);
    
                // 【异步发送方式】
    //            producer.send(msg, new SendCallback() {
    //                @Override
    //                public void onSuccess(SendResult sendResult) {
    //                    // 发送成功时调用
    //                    System.out.printf("%s%n", sendResult);
    //                }
    //
    //                @Override
    //                public void onException(Throwable throwable) {
    //                    // 发送失败时调用
    //                    throwable.printStackTrace();
    //                }
    //            });
    
                // 【单向发送方式】(也是异步,但是不获取响应)
    //            producer.sendOneway(msg);
            }
    
            // 关闭Producer
            producer.shutdown();
        }
    
    }
    
  • 消费者 (Push方式、Pull方式)
    import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer;
    import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
    import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
    import org.apache.rocketmq.common.message.MessageExt;
    import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
    
    import java.util.List;
    
    /**
     * Description: RocketMQ 简单的Consumer示例 (Push方式、Pull方式)
     * <br/>
     * Date: 2020/1/6 17:58
     *
     * @author ALion
     */
    public class SimpleConsumer {
    
        public static void main(String[] args) throws Exception {
            // 创建Consumer
            // 实例化Consumer对象,并指定消费者组名为push_consumer_name
            // 【Push方式】 由Broker推消息到Consumer
            DefaultMQPushConsumer pushConsumer = new DefaultMQPushConsumer("push_consumer_name");
            // 指定NameServer集群地址,多个用;分隔
            pushConsumer.setNamesrvAddr("192.168.1.110:9876;192.168.1.111:9876;192.168.1.112:9876");
            // 设置订阅的Topic与Tag
            // 订阅多个Tag,用||分隔,例如 "Tag_A || Tag_B"、"*"
            pushConsumer.subscribe("Topic_Test", "Tag_A");
    
            // 设定消费模式 CLUSTERING与BROADCASTING
            // CLUSTERING: 负载均衡(默认)。多个消费者消费时,分别消费所有消息的一部分。
            // BROADCASTING: 广播模式。多个消费者消费时,都全部消费。
    //        pushConsumer.setMessageModel(MessageModel.CLUSTERING);
    
            // 注册一个回调监听,用于接收消息
            pushConsumer.registerMessageListener(new MessageListenerConcurrently() {
                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                    for (MessageExt messageExt : list) {
                        // 穿过的消息是字节码,需要解码
                        byte[] body = messageExt.getBody();
                        System.out.println(new String(body));
                    }
    
                    // 回复 Broker "消费成功"
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });
    
            // 启动Consumer
            pushConsumer.start();
    
            // 【Pull方式】 由Consumer主动从Broker拉数据
    //        DefaultLitePullConsumer pullConsumer = new DefaultLitePullConsumer("pull_consumer_name");
    //        pullConsumer.setNamesrvAddr("192.168.1.110:9876;192.168.1.111:9876;192.168.1.112:9876");
    //        pullConsumer.subscribe("Topic_Test", "Tag_A");
    //        while (true) {
    //            List<MessageExt> messageExts = pullConsumer.poll(1000); // 超时时间1000ms
    //            for (MessageExt messageExt : messageExts) {
    //                byte[] body = messageExt.getBody();
    //                System.out.println(new String(body));
    //            }
    //        }
        }
    
    }
    

4. 有序消息示例

  • 原理:将需要保证顺序的消息发送到同一MessageQueue,即可保证有序
  • 生产者
    import org.apache.rocketmq.client.producer.DefaultMQProducer;
    import org.apache.rocketmq.client.producer.MessageQueueSelector;
    import org.apache.rocketmq.client.producer.SendResult;
    import org.apache.rocketmq.common.message.Message;
    import org.apache.rocketmq.common.message.MessageQueue;
    
    import java.util.List;
    
    /**
     * Description: RocketMQ 有序Producer示例
     * <br/>
     * Date: 2020/1/6 17:28
     *
     * @author ALion
     */
    public class OrderedProducer {
    
        public static void main(String[] args) throws Exception {
            // 创建Producer
            DefaultMQProducer producer = new DefaultMQProducer("producer_name");
            producer.setNamesrvAddr("192.168.1.110:9876;192.168.1.111:9876;192.168.1.112:9876");
            producer.start();
    
            // 开始生产数据
            for (int i = 0; i < 100; i++) {
                // orderId 消息的唯一标识
                // 用实际生产环境的用户id等替代该值,能够保证该用户数据的有序性
                int orderId  = i % 10;
                Message msg = new Message(
                        "Topic_Ordered",
                        "Tag_A",
                        "KEY" + i, // 标识唯一一条消息
                        ("Hello RocketMQ " + i).getBytes()
                );
    
                // 发送消息到RocketMQ集群
                SendResult sendResult = producer.send(
                        msg,
                        // MessageQueueSelector用于决定消息发送到哪个队列
                        new MessageQueueSelector() {
                            @Override
                            public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                                // arg 即 orderId
                                // 同一个orderId的数据应该发送到同一个MessageQueue
                                int idx = (Integer) arg % mqs.size();
                                return mqs.get(idx);
                            }
                        },
                        orderId
                );
                // 打印结果
                System.out.printf("%s%n", sendResult);
            }
    
            // 关闭Producer
            producer.shutdown();
        }
    
    }
    
  • 消费者
    import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
    import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
    import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
    import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
    import org.apache.rocketmq.client.exception.MQClientException;
    import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
    import org.apache.rocketmq.common.message.MessageExt;
    
    import java.util.List;
    
    /**
     * Description: RocketMQ 有序Consumer示例
     * <br/>
     * Date: 2020/1/6 19:35
     *
     * @author ALion
     */
    public class OrderedConsumer {
    
        public static void main(String[] args) throws MQClientException {
            // 创建Consumer
            DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_group_name");
            // 设置从最开始的位置消费,默认是最后的位置开始(CONSUME_FROM_LAST_OFFSET)
            consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
            // 订阅并监听消息
            consumer.subscribe("Topic_Ordered", "Tag_A");
            // 注意,这里需要传 MessageListenerOrderly
            consumer.registerMessageListener(new MessageListenerOrderly() {
                @Override
                public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                    for (MessageExt messageExt : msgs) {
                        System.out.println(
                                Thread.currentThread().getName() +
                                " Receive New Messages: " + new String(messageExt.getBody())
                        );
                    }
    
                    return ConsumeOrderlyStatus.SUCCESS;
                }
            });
    
            // 启动Consumer
            consumer.start();
        }
    
    }
    

5. 事务消息示例

  • 事务机制示意图(来自互联网)
    事务机制示意图
    1. 生产者生产消息到RocketMQ集群
    2. RocketMQ集群返回Ack,表示生产成功
    3. 生产者处理本地事务
    4. 事务的状态
      • 如果成功,提交 LocalTransactionState.COMMIT_MESSAGE
      • 如果失败,回滚 LocalTransactionState.ROLLBACK_MESSAGE
      • 仍在处理中, 未知 LocalTransactionState.UNKNOW
    5. RocketMQ集群在未收到COMMIT_MESSAGE或ROLLBACK_MESSAGE前,会定时回查生产者
    6. 确认消息事务状态(提交、回滚、未知),直到状态变为提交或回滚
  • 生产者
    import org.apache.rocketmq.client.producer.SendResult;
    import org.apache.rocketmq.client.producer.TransactionMQProducer;
    import org.apache.rocketmq.common.message.Message;
    
    /**
     * Description: RocketMQ 事务Producer示例
     * <br/>
     * Date: 2020/1/6 19:51
     *
     * @author ALion
     */
    public class TransactionProducer {
    
        public static void main(String[] args) throws Exception {
            // 创建Producer
            TransactionMQProducer producer = new TransactionMQProducer("transaction_producer_name");
            producer.setNamesrvAddr("192.168.1.110:9876;192.168.1.111:9876;192.168.1.112:9876");
            // 设置事务监听器,用于RocketMQ集群来回查事务处理状态
            producer.setTransactionListener(new MyTransactionListener());
            producer.start();
    
            // 开始生产数据
            for (int i = 0; i < 100; i++) {
                // i 能除尽5时,给一个乱码,用于模拟事务失败回滚
                String suffix = i % 5 == 0 ? "!@#" : i + "";
                String body = "Hello RocketMQ ," + suffix;
                Message msg = new Message(
                        "Topic_Transaction",
                        "Tag_A",
                        body.getBytes()
                );
    
                // 发送消息到RocketMQ集群
                // null表示对所有消息进行事务控制
                SendResult sendResult = producer.sendMessageInTransaction(msg, null);
                // 打印结果
                System.out.printf("%s%n", sendResult);
    
            }
    
            // 关闭Producer
            producer.shutdown();
        }
    
    }
    
  • 事务处理监听器
    import org.apache.rocketmq.client.producer.LocalTransactionState;
    import org.apache.rocketmq.client.producer.TransactionListener;
    import org.apache.rocketmq.common.message.Message;
    import org.apache.rocketmq.common.message.MessageExt;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * Description: 事务处理监听器
     * <br/>
     * Date: 2020/1/6 20:16
     *
     * @author ALion
     */
    public class MyTransactionListener implements TransactionListener {
    
        // 设置一个Map,用于标识每条消息的事务状态
        // value=0 表示处理失败
        // value=1 表示处理成功
        // value=其他值 表示未知
        private ConcurrentHashMap<String, Integer> transactionMap = new ConcurrentHashMap<>();
    
        @Override
        public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            // 处理事务
    
            // 一开始数据还在处理,先设置一个其他值(例如3),表示未知
            transactionMap.put(msg.getTransactionId(), 3);
    
            // 可以直接阻塞式处理
            // 也可以开子线程(或线程池)进行异步处理
            // 处理完后需要更新transactionMap中的状态
            new Thread(() -> doSomething(msg, arg)).start();
    
            // 还在处理中,先返回未知状态
            return  LocalTransactionState.UNKNOW;
        }
    
        private void doSomething(Message msg, Object arg) {
            String content = new String(msg.getBody());
            String[] fields = content.split(",");
            try {
                String txt = fields[0];
                int number = Integer.parseInt(fields[1]);
                System.out.println("txt = " + txt + ", number = " + number);
    
                // 解析成功,修改状态为1,事务提交
                transactionMap.put(msg.getTransactionId(), 1);
            } catch (NumberFormatException e) {
                e.printStackTrace();
                // 解析失败,修改状态为0,事务回滚
                transactionMap.put(msg.getTransactionId(), 0);
            }
        }
    
        @Override
        public LocalTransactionState checkLocalTransaction(MessageExt msg) {
            // 如果executeLocalTransaction返回 LocalTransactionState.UNKNOW
            // MQ将会定时回查该方法,以确定事务状态
            Integer integer = transactionMap.get(msg.getTransactionId());
            if (integer != null) {
                switch (integer) {
                    case 0:
                        return LocalTransactionState.COMMIT_MESSAGE;
                    case 1:
                        return LocalTransactionState.ROLLBACK_MESSAGE;
                    default:
                        return LocalTransactionState.UNKNOW;
                }
            }
    
            return LocalTransactionState.UNKNOW;
        }
    
    }
    
发布了128 篇原创文章 · 获赞 45 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/alionsss/article/details/103860531