Java之RocketMQ详解

本文深入解析了RocketMQ的原理、应用场景、异步消息处理、负载均衡、事务消息及集群搭建,包括生产者消费者模式、消息过滤、死信队列和重试机制等关键概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RocketMQ详解

MQ简介

为什么要有MQ?

传统项目中,需要请求方去发送请求到响应方,响应方响应结果。但是如果响应方服务器宕机了就会立刻影响到请求方代码。
在这里插入图片描述

如果响应方服务器宕机,请求就会不成立,连接相当于就已经断开了。
在这里插入图片描述

当请求方和响应方需要进行业务通讯时,请求方不发请求给响应方也就是(调用方),而是发给MQ,再由MQ将消息发送给响应方。如果响应方服务器宕机,而请求方已经将消息发送给MQ了,请求方任务已经结束。此时请求方并不知道响应方是谁,响应方也不知道请求方是谁,它们只靠MQ进行通信。

某种程度来讲是对两个微服务之间的通讯进行了解耦,两台服务器之间没有那么强的耦合性了。
在这里插入图片描述

MQ(Message Queue)消息队列

是一种用来保存消息数据的队列(队列是数据结构的一种,特征为“先进先出”)。

消息

​ 服务器之间的业务请求

​ 原始架构:服务器中的A功能需要调用B、C模块才能完成。

​ 微服务架构:服务器A向服务器B发送要执行的操作(视为消息);服务器A向服务器C发送要执行的操作(视为消息)。

MQ的作用

应用解耦(本质:异步消息发送)

服务器A将消息发送给MQ,发起业务方A和接收业务方B和C进行了有效解耦,A不能感知B和C的存在。
在这里插入图片描述

本质

原来A服务器去调B、C服务器都是同步的,现在将消息交给MQ,相当于是异步消息发送。

快速应用变更维护(本质:异步消息发送)

当要进行业务变更新增D,业务必须有B、C、D三个服务同时完成。
在这里插入图片描述

只需要服务器A多发送一个消息,服务器D接进来,收到消息就可以工作。

当出现业务变更,将其中一台服务替代或者下线时,此时只需要A少发送一个消息到MQ中。

MQ工作的本质

当消息的生产者将消息发送到MQ中时,MQ中有个地方专门用来保存发送过来的消息,每发送一次就记录一次。使用消息的时候是从指定的位置读取对应的消息(类似偏移量的概念),其实并没有消息进来再删除掉这样的过程。只是通过消息的存储偏移量来描述消息的消费。

流量削峰(本质:异步消息发送)

比如,双11流量高峰期,在某个时间点服务器A的qps达到了4000,消息量忽然增大(和业务处理方B相比),当服务器B与MySQL打交道时,MySQL的qps只能达到1000,如果没有MQ,4000个请求一起过来,服务器基本就挂掉了,根本就处理不过来。

如果有MQ,每次发过去1000,让服务器进行处理,处理完再发1000过去,反复完成,服务器就可以解决这个问题。
在这里插入图片描述

举例:双11下完订单的时候并不是立刻生成运单和订单,MQ是慢慢消费这些消息的。

MQ的缺点

  • 系统可用性降低

    两台服务器通过MQ进行通讯,如果MQ挂了,两台服务器都无法工作。解决:集群
    在这里插入图片描述

  • 系统复杂度提高
  • 异步消息机制
    • 消息顺序性

      某些特定场合需要保证消息的顺序性,MQ中存放着大量的消息,如何保证消息的顺序性。

    • 消息丢失

      MQ挂了,里面的消息丢了。

    • 消息一致性

      比如一组操作需要两台服务器配合完成,发两个消息,A服务器ok,但是B服务器挂了。相当于,订单生成了,但是运单没了。同样是对于订单的一组东西。异步消息会存在,同步消息不会存在这个问题。

    • 消息重复使用

潜在问题:

消息的生产者发送给MQ的过程中,消息丢失怎么办?

MQ宕机,消息丢失怎么办?

如果保证消息的顺序性?

MQ在发给消费者的时候,消息丢失怎么办?

如何保证消息不重复消费?

常用MQ产品

  • ActiveMQ:java语言实现,万级数据吞吐量,处理速度ms级,主从架构,成熟度高
  • RabbitMQ:erlang语言实现,万级数据吞吐量,处理速度us级,主从架构,
  • RocketMQ:java语言实现,十万级数据吞吐量,处理速度ms级,分布式架构,功能强大,扩展性强
  • kafka:scala语言实现,十万级数据吞吐量,处理速度ms级,分布式架构,功能较少,应用于大数据较多
基本概念

在这里插入图片描述

消息的生产者负责生产消息,生产出来的消息给Broker(消息服务器)。有了消息服务器,消息的生产者就可以发送消息给消息服务器,同时返回回去你的消息我接收到了。消息的消费者负责消费消息。

常见的两种模式:1.消息的消费者发送一个请求过去,消息服务器给消费者一个消息,消费者进行消费,这种模式较少。2.监听器监听消息服务器中是否有消息,如果有消息就直接推送给消息的消费者(就像滴滴,直接推送给司机,司机没有拒绝的权利。美团也是同样,经常会听到店家的美团消息:您有新的外卖订单,请及时处理。商家也没有拒绝的权利)。第二种模式较多,是主流的消息消费方式。

生产者和消费者一般都是以集群形式,集群可以让工作更加高效。不然仅有的一台服务器挂了,就不能正常工作了。

消息服务器集群的工作包含:消息的接收、消息持久化、提供消息、过滤消息、高可用。

那生产者、消息服务器和消费者如何找到对方?

消息服务器注册到命名服务器上,这样命名服务器就有了所有的消息服务器的Broker IP。

消息的生产者在发消息时,去连接命名服务器,同时获取所有的Broker信息,消息的消费者同理。

命名服务器怎么才能知道这些服务器存在呢?万一有的机器挂了呢?通过心跳机制来维系,每30秒发送一个心跳包,来保障现在拿到的这个服务器就有效的。

消息包括:消息Message、主题Topic(必备。是对消息进行分类的,这是订单类的,这是会员类的)、标题Tag。一个消息可以分成好多类,提取方式不同,处理的方式不同。服务器中会根据不同的Topic创建队列。对应的Topic主题的消息会放到对应的队列中

环境搭建

命名服务器如果不启动,就没法连,所以命名服务器是要安装的第一个东西。

命名服务器安装好后,Broker就可以注册到命名服务器上了,所以第二步是安装Broker。

接下来就可以发、收消息了。可以先发消息然后再收消息,也可以先收着然后再发消息。
在这里插入图片描述
MQ的服务器在启动时会报错,因为配置有问题,需要修改runbroker.sh文件中的配置,调整到与当前机器内存匹配即可,推荐256-128m。

安装RocketMQ

上传压缩包小工具,先安装

yum -y install lrzsz
rz

此时要选择上传的zip(Apache官网自行下载)

解压zip包

unzip rocketmq-all-4.5.2-bin-release.zip

修改名称方便查看

mv rocketmq-all-4.5.2-bin-release rocketmq

进入bin目录中:
在这里插入图片描述

启动命名服务器

sh mqnamesrv

启动成功
在这里插入图片描述

启动消息服务器时,要指定要注册到哪个命名服务器上

sh mqbroker -n localhost:9876

启动消息服务器时会报错,报错原因是runbroker.sh配置有问题,需要进行修改,修改成符合你当前机器的一个合适大小,当前使用的是虚拟机,修改为256m和128m

修改前:
在这里插入图片描述

修改后:
在这里插入图片描述
接下来启动服务,启动命名服务器和消息服务器,启动时不会报错,但是运行时会报错,先行说明,如果是超时异常,无法连接,查看:
解决方案

消息发送(重点)

消息的发送主要包括 :

单生产者单消费者&同步消息

消息的发送与接收

1.消息谁来发?

2.消息发给谁?

3.消息怎么发?

4.发什么?

5.发的结果是什么?

6.收尾工作?
在这里插入图片描述

单生产者单消费者&同步消息

引入依赖坐标

<dependencies>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.5.2</version>
        </dependency>
</dependencies>
消息生产者:
package com.base;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

public class Producer {
   
    public static void main(String[] args) throws Exception {
   

        //创建发送消息对象
        DefaultMQProducer producer = new DefaultMQProducer("group1");

        //设定命名服务器地址---获取到消息服务器ip
        producer.setNamesrvAddr("192.168.200.130:9876");

        //启动发送服务
        producer.start();

        //构建消息,指定topic和body
        Message msg = new Message("topic1", "hello base".getBytes());
        //发送消息
        SendResult sendResult = producer.send(msg, 10000);
        System.out.println("sendResult = " + sendResult);

        //关闭连接
        producer.shutdown();

    }
}

打印结果

sendResult = SendResult [sendStatus=SEND_OK, msgId=C0A8006F0FA018B4AAC27A51177E0000, offsetMsgId=C0A8C88200002A9F000000000001343E, messageQueue=MessageQueue [topic=tpoic1, brokerName=broker-a, queueId=3], queueOffset=0]
消息消费者:
package com.base;

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 java.util.List;

public class Consumer {
   
    public static void main(String[] args) throws Exception {
   

        //创建一个消息接收对象consumer
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");

        //设定接收消息的命名服务器地址---获取到消息服务器ip
        consumer.setNamesrvAddr("192.168.200.130:9876");

        //设置接收消息对应的topic,对应的sub标签为任意*,之前producer没有指定tag。如果producer发送的消息指定了tag,那么也必须指定相应的tag
        consumer.subscribe("topic1", "*");

        //开启监听,用于接收消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
   
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
   
                //遍历接收到的消息
                for (MessageExt msg : list) {
   
                    System.out.println("msg = " + msg);
                    System.out.println("消息为:" + new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //启动消息接收服务
        consumer.start();

    }

}
msg = MessageExt [queueId=3, storeSize=157, queueOffset=2, sysFlag=0, bornTimestamp=1627132561754, bornHost=/192.168.200.1:10109, storeTimestamp=1627132561761, storeHost=/192.168.200.130:10911, msgId=C0A8C88200002A9F000000000001393A, commitLogOffset=80186, bodyCRC=806332349, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{
   topic='topic1', flag=0, properties={
   MIN_OFFSET=0, MAX_OFFSET=3, CONSUME_START_TIME=1627132561761, UNIQ_KEY=C0A8006FF2B418B4AAC27B0281590000, WAIT=true}, body=[104, 101, 108, 108, 111, 32, 109, 113], transactionId='null'}]
消息为:hello mq
单生产者多消费者
消息生产者与之前一致,举例改为发送10次消息
package com.one2many;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

public class Producer {
   
    public static void main(String[] args) throws Exception {
   

        //创建发送消息对象
        DefaultMQProducer producer = new DefaultMQProducer("group1");

        //设定命名服务器地址---获取到消息服务器ip
        producer.setNamesrvAddr("192.168.200.130:9876");

        //启动发送服务
        producer.start();

        for (int i = 1; i <= 10; i++) {
   
            //构建消息,指定topic和body
            Message msg = new Message("topic1", ("hello base"+i).getBytes());
            //发送消息
            SendResult sendResult = producer.send(msg, 10000);
            System.out.println("sendResult = " + sendResult);
        }

        //关闭连接
        producer.shutdown();

    }
}
消息消费者:与 之前一致。
package com.one2many;

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;

public class Consumer {
   
    public static void main(String[] args) throws Exception {
   

        //创建一个消息接收对象consumer
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");

        //设定接收消息的命名服务器地址---获取到消息服务器ip
        consumer.setNamesrvAddr("192.168.200.130:9876");

        //设置接收消息对应的topic,对应的sub标签为任意*,之前producer没有指定tag。如果producer发送的消息指定了tag,那么也必须指定相应的tag
        consumer.subscribe("topic1", "*");

        //设置消费者的消费模式:也是默认的模式负载均衡
        //consumer.setMessageModel(MessageModel.CLUSTERING);

        //设置消费者的消费模式为广播模式:所有客户端接收的消息都是一样的
        consumer.setMessageModel(MessageModel.BROADCASTING);

        //开启监听,用于接收消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
   
            public ConsumeConcurrentlyStatus <
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值