学习 RocketMQ 单机部署、消息发送、消息接收

RocketMQ 介绍

RocketMQ是一款由阿里巴巴开源的分布式消息中间件。它具有低延迟、高吞吐量、高可用性和高可靠性等特点,适用于构建具有海量消息堆积和异步解耦功能的应用系统。

为什么要使用 MQ ?

作用描述
异步系统耦合度降低,没有强依赖关系
削峰不需要同步执行的远程调用可以有效提高响应时间
解耦请求达到峰值后,后端或者数据库还可以保持固定消费速率消费,不会被压垮

RocketMQ 与其他产品对比

vs Kafka

  • 数据可靠性:RocketMQ 提供多种可靠性保证,Kafka 在极端情况下可能丢失数据。
  • 实时性:RocketMQ 在消息实时性上表现更佳。
  • 队列数与性能:RocketMQ 支持更多队列,Kafka 在高分区下性能下降。
  • 消息顺序性:RocketMQ 支持严格顺序,Kafka 可能产生乱序。
  • 生态:Kafka 生态更丰富,RocketMQ 与阿里技术栈集成好。

vs RabbitMQ

  • 性能:RocketMQ 在高并发和海量消息处理上表现更优。
  • 消息模型:RabbitMQ 模型灵活,RocketMQ 注重顺序和事务。
  • 适用场景:RabbitMQ 适用于可靠消息传递,RocketMQ 适用于高性能场景。

vs ActiveMQ

  • 跨平台与持久化:ActiveMQ 支持多种协议和数据库持久化,RocketMQ 持久化机制高效。
  • 灵活性:ActiveMQ 协议广泛,RocketMQ 多语言SDK集成友好。
  • 社区与文档:ActiveMQ 社区活跃度较低,RocketMQ 开发活跃但社区成熟度不及 RabbitMQ。

总结:RocketMQ 在数据可靠性、实时性、队列数与性能上具有优势,适合高性能和顺序消息场景。

RocketMQ 重要概念

Producer:消息的发送者,生产者 (发件人)

Consumer:消息接收者,消费者 (取件人)

Broker:暂时和传输消息的通道 (快递)

NameServer:管理Broker的;负责消息的存储和转发,接收生产者产生的消息并持久化消息;当用户发送的消息被发送到Broker时,Broker会将消息转发到与之关联的Topic中,以便让更多的接收者进行处理;各个快递公司的管理机构,相当于Broker的注册中心,保留了broker的信息 (监测快递是否健康)

Queue:队列,消息存放的位置,一个Broker中可以有多个队列 (驿站)

Topic:主题,消息的分类,用于标识同一类业务逻辑的消息 (取件码)

ConsumerGroup:消费者组,RocketMQ 中承载多个消费行为一致的消费者负载均衡分组。和消费者不同,消费者组是一个逻辑概念。

部署 Namesrver、Broker、Dashboard

Docker部署RocketMQ5.x (单机部署+配置参数详解+不使用docker-compose直接部署)_rocketmq不推荐用docker部署-CSDN博客

快速入门

1)创建一个基于 Maven 的 SpringBoot 项目,并添加以下依赖

<dependency>
  <groupId>org.apache.rocketmq</groupId>
  <artifactId>rocketmq-client</artifactId>
  <version>5.1.0</version>
</dependency>

消息生产者

  1. 创建消息生产者producer,并指定生产者组名
  2. 指定Nameserver地址
  3. 启动producer
  4. 创建消息对象,指定主题Topic、Tag和消息体
  5. 发送消息
  6. 关闭生产者
public class SyncProducer {
    public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("SyncProducer");
        producer.setNamesrvAddr("10.226.8.14:9876");
        producer.start();
        for (int i = 0; i < 2; i++) {
            Message msg = new Message("Simple", //主题
                                      "TagA",  //设置消息Tag,用于消费端根据指定Tag过滤消息。
                                      "Simple-Sync".getBytes(StandardCharsets.UTF_8) //消息体。
                                     );
            SendResult send = producer.send(msg);
            System.out.printf(i + ".发送消息成功:%s%n", send);
        }
        producer.shutdown();
    }
}

消息消费者

  1. 创建消费者comsumer、指定消费者组名
  2. 指定Nameserver地址
  3. 创建监听订阅主题Topic和Tag等
  4. 处理消息
  5. 启动消费者comsumer
public class Consumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer pushConsumer = new DefaultMQPushConsumer("SimplePushConsumer");
        pushConsumer.setNamesrvAddr("10.226.8.14:9876");
        pushConsumer.subscribe("Simple","*");
        pushConsumer.setMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                list.forEach( n->{
                    System.out.printf("收到消息: %s%n" , n);
                });
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        pushConsumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

消费模式

MQ 的消费模式可以大致分为两种,一种是推 Push,一种是拉 Pull。

Push 是服务端主动推送消息给客户端,优点是及时性较好,但如果客户端没有做好流控,旦服务端推送大量消息到客户端时,就会导致客户端消息堆积甚至崩溃。

Pull 是客户端需要主动到服务端取数据,优点是客户端可以依据自己的消费能力进行消费但拉取的频率也需要用户自己控制,拉取频繁容易造成服务端和客户端的压力,拉取间隔长又容易造成消费不及时。

Push 模式也是基于 pull 模式的,只能客户端内部封装了 api,一般场景下,上游消息生产量小或者均速的时候,选择 push 模式。在特殊场景下,例如电商大促,抢优惠券等场景可以选择 pull 模式

简单消息

1)同步发送

可靠性要求高、数据量级少、实时响应,具体实现参考上面的入门代码

2)异步发送

不等待消息返回直接进入后续流程。

public class AsyncProducer {
    public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("AsyncProducer");
        producer.setNamesrvAddr("10.226.8.14:9876");
        producer.start();
        CountDownLatch countDownLatch = new CountDownLatch(100);//计数
        for (int i = 0; i < 100; i++) {
            Message message = new Message("Simple", "TagA", "Simple-Async".getBytes(StandardCharsets.UTF_8));
            final int index = i;
            producer.send(message, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    countDownLatch.countDown();
                    System.out.printf("%d 消息发送成功%s%n", index, sendResult);
                }

                @Override
                public void onException(Throwable throwable) {
                    countDownLatch.countDown();
                    System.out.printf("%d 消息失败%s%n", index, throwable);
                    throwable.printStackTrace();
                }
            }
                         );
        }
        countDownLatch.await(5, TimeUnit.SECONDS);
        producer.shutdown();
    }
}

3)单向发送

只负责发送,不管消息是否发送成功。

public class OnewayProducer {
    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("AsyncProducer");
        producer.setNamesrvAddr("10.226.8.14:9876");
        producer.start();
        for (int i = 0; i < 10; i++) {
            Message message = new Message("Simple","TagA", "Simple-Oneway".getBytes(StandardCharsets.UTF_8));
            producer.sendOneway(message);
            System.out.printf("%d 消息发送完成 %n" , i);
        }
        Thread.sleep(5000);
        producer.shutdown();
    }
}

顺序消息

顺序消息指生产者局部有序发送到一个queue,但多个queue之间是全局无序的。

  • 顺序消息生产者样例:通过MessageQueueSelector将消息有序发送到同一个queue中。
  • 顺序消息消费者样例:通过MessageListenerOrderly消费者每次读取消息都只从一个queue中获取(通过加锁的方式实现)。

顺序消息生产者

public class OrderProducer {
    public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("OrderProducer");
        producer.setNamesrvAddr("10.226.8.14:9876");
        producer.start();
        for (int j = 0; j < 5; j++) {
            for (int i = 0; i < 10; i++) {
                Message message = new Message("OrderTopic","TagA",
                                              ("order_" + j + "_step_" + i).getBytes(StandardCharsets.UTF_8));
                SendResult sendResult = producer.send(message, new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
                        Integer id = (Integer) o;
                        int index = id % list.size();
                        return list.get(index);
                    }
                }, j);
                System.out.printf("%s%n", sendResult);
            }
        }
        producer.shutdown();
    }
}

顺序消息消费者

public class OrderConsumer {
    public static void main(String[] args) throws MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("OrderConsumer");
        consumer.setNamesrvAddr("10.226.8.14:9876");
        consumer.subscribe("OrderTopic","*");
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
                list.forEach(n->{
                    System.out.println("QueueId:"+n.getQueueId() + "收到消息内容 "+new String(n.getBody()));
                });
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

广播消息

广播消息并没有特定的消息消费者样例,这是因为这涉及到消费者的集群消费模式。

  • MessageModel.BROADCASTING:广播消息。一条消息会发给所有订阅了对应主题的消费者,不管消费者是不是同一个消费者组。
  • MessageModel.CLUSTERING:集群消息。每一条消息只会被同一个消费者组中的一个实例消费。

广播消息消费模式

public class BroadcastConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("BroadCastConsumer");
        consumer.setNamesrvAddr("10.226.8.14:9876");
        consumer.subscribe("simple","*");
        consumer.setMessageModel(MessageModel.BROADCASTING); //广播模式
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                list.forEach(n->{
                    System.out.println("QueueId:"+n.getQueueId() + "收到消息内容 "+new String(n.getBody()));
                });
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
        System.out.printf("Broadcast Consumer Started.%n");
    }
}

延迟消息

延迟消息实现的效果就是在调用producer.send方法后,消息并不会立即发送出去,而是会等一段时间再发送出去。这是RocketMQ特有的一个功能。

  • message.setDelayTimeLevel(3):预定日常定时发送。1到18分别对应messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h;可以在dashboard中broker配置查看。
  • msg.setDelayTimeMs(10L):指定时间定时发送。默认支持最大延迟时间为3天,可以根据broker配置:timerMaxDelaySec修改。

预定日程生产者

public class ScheduleProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("ScheduleProducer");
        producer.setNamesrvAddr("10.226.8.14:9876");
        producer.start();
        for (int i = 0; i < 2; i++) {
            Message msg = new Message("Schedule", //主题
                    "TagA",  //设置消息Tag,用于消费端根据指定Tag过滤消息。
                    "ScheduleProducer".getBytes(StandardCharsets.UTF_8) //消息体。
            );
            //1到18分别对应messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
            msg.setDelayTimeLevel(3);
            producer.send(msg);
            System.out.printf(i + ".发送消息成功:%s%n", LocalTime.now());
        }
        producer.shutdown();
    }
}

预定日程消费者

public class ScheduleConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer pushConsumer = new DefaultMQPushConsumer("SimplePushConsumer");
        pushConsumer.setNamesrvAddr("10.226.8.14:9876");
        pushConsumer.subscribe("Schedule","*");
        pushConsumer.setMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                list.forEach( n->{
                    System.out.printf("接收时间:%s %n", LocalTime.now());
                });
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        pushConsumer.start();
        System.out.printf("Simple Consumer Started.%n");
    }
}

指定时间生产者

public class TimeProducer {
    public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("TimeProducer");
        producer.setNamesrvAddr("192.168.43.137:9876");
        producer.start();
        for (int i = 0; i < 2; i++) {
            Message msg = new Message("Schedule", //主题
                    "TagA",  //设置消息Tag,用于消费端根据指定Tag过滤消息。
                    "TimeProducer".getBytes(StandardCharsets.UTF_8) //消息体。
            );
            // 相对时间:延时消息。此消息将在 10 秒后传递给消费者。
            msg.setDelayTimeMs(10000L);
            // 绝对时间:定时消息。设置一个具体的时间,然后在这个时间之后多久在进行发送消息
			// msg.setDeliverTimeMs(System.currentTimeMillis() + 10000L);
            producer.send(msg);
            System.out.printf(i + ".发送消息成功:%s%n", LocalTime.now());
        }
        producer.shutdown();
    }
}

指定时间消费者

public class TimeConsumer {
    public static void main(String[] args) throws MQClientException {
        DefaultMQPushConsumer pushConsumer = new DefaultMQPushConsumer("TimeConsumer");
        pushConsumer.setNamesrvAddr("10.226.8.14:9876");
        pushConsumer.subscribe("Schedule","*");
        pushConsumer.setMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                list.forEach( n->{
                    System.out.printf("接收时间:%s %n", LocalTime.now());
                });
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        pushConsumer.start();
        System.out.printf("Simple Consumer Started.%n");
    }
}

RocketMQ 如何保证消息可靠性?

我们将消息流程分为三大部分,每一部分都有可能会丢失数据。

  • 生产阶段:Producer 通过网络将消息发送给 Broker,这个发送可能会发生丢失。比如网络延迟不可达等。
  • 存储阶段:Broker 肯定是先把消息放到内存的,然后根据刷盘策略持久化到硬盘中。刚收到 Producer 的消息,放入内存,但是异常宕机了,导致消息丢失。
  • 消费阶段:消费失败。比如先提交ack再消费,处理过程中出现异常,该消息就出现了丢失。

解决方案:

  • 生产阶段:使用同步发送失败重试机制;异步发送重写回调方法检查发送结果;ACK 确认机制。
  • 存储阶段:同步刷盘机制(默认情况下是异步刷盘);集群模式采用同步复制
  • 消费阶段:正常消费处理完成才提交ACK(手动ACK);如果处理异常返回重试标识。

RocketMQ 如何解决消息积压问题?

  1. 增加消费者数量:
    增加消费者实例的数量,以提高消息的消费速度。
    确保消费者实例数量与消息队列数量匹配,以便每个队列都有专门的消费者处理。

  2. 优化消费者逻辑:
    优化消费者的处理逻辑,提高单个消费者的处理效率。
    使用批量消费的方式来减少每次消费的开销。

  3. 扩展消息队列容量:
    增加消息队列的数量,以分散消息负载。
    动态调整队列数量,增加处理能力,实现更高的并行处理。

  4. 设置消息消费失败处理机制:
    实施重试机制,确保消费失败的消息能够被重新处理。
    设置死信队列(DLQ)来处理多次消费失败的消息。

  5. 快速失败丢弃消息:
    如果某些消息可以丢弃,考虑在高峰期快速丢弃这些消息以减轻负担。

  6. 提升系统性能:
    优化服务器性能,增加硬件资源,如CPU、内存和网络带宽。
    确保网络连接的稳定性和速度,以减少延迟。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tiantian17)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值