RocketMQ学习(四)——RocketMQ消息发送

本文详细介绍了RocketMQ的网络架构,包括NameServer、Broker、Producer和Consumer的角色,以及消息发送的全流程。着重解析了DefaultMQProducer的启动流程、消息发送的选择队列策略,以及消息在Broker端的处理过程。此外,还探讨了发送消息的同步方式,以及如何选择消息发送的队列。

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

RocketMQ 网络架构图

RocketMQ分布式消息队列的网络部署架构图如下图所示
在这里插入图片描述
于上图中几个角色的说明:
(1) NameServer: RocketMQ集群的命名服务器(也可以说是注册中心),它本身是无状态的(实际情况下可能存在每个NameServer实例上的数据有短暂的不一致现象,但是通过定时更新,在大部分情况下都是一致的),用于管理集群的元数据( 例如,KV配置、Topic、Broker的注册信息)。nameserver存有全量的路由信息,提供对等的读写服务,支持快速扩缩容。nameserver接收client(producer/consumer)的请求,根据消息的topic获取相应的broker路由信息。集群部署后,节点之间无任何信息同步。
(2)Broker(Master): RocketMQ消息代理服务器主节点,起到串联Producer的消息发送和Consumer的消息消费,和将消息的落盘存储的作用;
(3)Broker(Slave): RocketMQ消息代理服务器备份节点,主要是通过同步/异步的方式(同步复制:消息发给master的同时也会将消息发送给slave 异步双写:只发送到master,再由master异步同步到slave)将主节点的消息同步过来进行备份,为RocketMQ集群的高可用性提供保障;
(4)Producer(消息生产者): 在这里为普通消息的生产者,主要基于RocketMQ-Client模块将消息发送至RocketMQ的主节点(Broker Master)。一般由业务系统负责产生消息。消息有3种发送方式:同步、异步、单向。
(5)ProducerGroup(生产组): 通常具有同样作用(同样topic)的一些producer可以归为同一个group。在事务消息机制中,如果发送某条事务消息后的producer-A宕机,使得事务消息一直处于PREPARED状态并超时,则broker会回查同一个group的其他producer,确认这条消息应该commit还是rollback。
其他核心概念:
(1)topic(主题): 一种消息的逻辑分类(消息的类型),比如说你有订单类的消息,也有库存类的消息,那么就需要进行分类存储。生产者方面:发消息时需指定topic,可以有1-n个生产者发布1个topic的消息,也1个生产者可以发布不同topic的消息。消费者方面:收消息时需订阅topic,可以有1-n个消费者组订阅1个topic的消息,1个消费者组可以订阅不同topic的消息。1个消息必须指定1个topic,topic允许自动创建与手工创建,topic创建时需要指定broker,可以指定1个或多个,name server就是通过broker与topic的映射关系来做路由。producer和consumer在生产和消费消息时,都需要指定消息的 topic,当topic匹配时,consumer 才会消费到producer发送的消息。topic与broker是多对多的关系,一个topic分布在多个broker上,一个broker可以配置多个topic。
(2)message(消息): message是消息的载体。每个message必须指定一个topic,相当于寄信的地址。message还有一个可选的tag设置,以便消费端可以基于tag进行过滤消息。message还有扩展的kv结构,例如你可以设置一个业务key到你的消息中,在broker上查找消息并诊断问题。
(3)tag(标签): 标签可以被认为是对topic的进一步细化。一般在相同业务模块中通过引入标签来标记不同用途的消息。区分相同topic下不同种类的消息。生产到哪个topic的哪个tag下,消费者也是从topic的哪个tag进行消费,即实现消息的过滤。
(4)queue(队列): queue是消息的物理管理单位,而topic是逻辑管理单位。一个topic下可以有多个queue,默认自动创建是4个,手动创建是8个。queue的引入使得消息存储可以分布式集群化,具有了水平扩展的能力。1个message只能属于1个queue、1个topic。在rocketmq中,所有消息队列都是持久化,长度无限的数据结构,所谓长度无限是指队列中的每个存储单元都是定长,访问其中的存储单元使用offset来访问,offset 为 java long 类型,64 位,理论上在 100年内不会溢出,所以认为是长度无限,另外队列中只保存最近几天的数据,之前的数据会按照过期时间来删除。也可以认为 Message Queue是一个长度无限的数组,offset就是下标。
rocketmq中,producer将消息发送给broker时,需要指定发送到哪一个queue中,默认情况下,producer会轮询的将消息发送到每个queue中,顺序是随机的,但总体上每个queue的消息数量均分,所有broker下的queue合并成一个list去轮询,也可以由程序员通过MessageQueueSelector接口来指定具体发送到哪个queue中。
对于consumer而言,会为每个consumer分配固定的队列(如果队列总数没有发生变化),consumer从固定的队列中去拉取没有消费的消息进行处理。消费端会通过RebalanceService线程,10秒钟做一次基于topic下的所有队列负载,获取同一个Consumer Group下的所有Consumer实例数或Topic的queue的个数是否改变,通知所有Consumer实例重新做一次负载均衡算法。
(5) offset(消费进度): 理解成消费进度,可自增。
(6) commit log(存储文件): 虽然每个topic下面有很多message queue,但是message queue本身并不存储消息。真正的消息存储会写在CommitLog的文件,message queue只是存储CommitLog中对应的位置信息,方便通过message queue找到对应存储在CommitLog的消息。 不同的topic,message queue都是写到相同的CommitLog 文件,也就是说CommitLog完全的顺序写。

对于上面图中几条通信链路的关系:

(1)NamerServer和NamerServer: nameserver互相独立,彼此没有通信关系,单台nameserver挂掉,不影响其他nameserver。
(2)Broker和NamerServer: Broker(Master or Slave)均会和每一个NameServer实例来建立TCP连接,定时注册topic&broker的路由信息到所有name server中。Broker在启动的时候会注册自己配置的Topic信息到NameServer集群的每一台机器中。即每一个NameServer均有该broker的Topic路由配置信息。其中,Master与Master之间无连接,Master与Slave之间有连接;
(2)Producer、Consumer与NamerServer: 每一个Producer会与NameServer集群中的一个实例建立TCP连接,从这个NameServer实例上拉取Topic路由信息;如果该nameserver挂掉,消费者会自动连接下一个nameserver,直到有可用连接为止,并能自动重连。
(3)Producer、Consumer和Broker: Producer会和它要发送的topic相关联的Master的Broker代理服务器建立TCP连接,用于发送消息以及定时的心跳信息;集群消费模式下,一个消费者集群多台机器共同消费一个topic的多个队列,一个队列只会被一个消费者消费。如果某个消费者挂掉,分组内其它消费者会接替挂掉的消费者继续消费。

客户端发送普通消息的demo方法
public class ProducerTest {
   
   
    //nameserver地址
    private String namesrvAddr = "192.168.152.129:9876;192.168.152.130:9876";

    private final DefaultMQProducer producer = new DefaultMQProducer("ProducerTest");

    private String TOPIC_TEST = "TOPIC_TEST";

    private String TAG_TEST = "TAG_TEST";

    /**
     * 初始化
     */
    public void start() {
   
   
        try {
   
   
            System.out.println("MQ:启动ProducerTest生产者");
            producer.setNamesrvAddr(namesrvAddr);
            producer.start();
            //发送消息
            for(int i=0;i<100;i++) {
   
   
                sendMessage("hello mq" + i);
            }
        } catch (MQClientException e) {
   
   
            System.out.println("MQ:启动ProducerTest生产者失败:" + e.getResponseCode() + e.getErrorMessage());
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public void sendMessage(String data) {
   
   
        System.out.println("MQ: 生产者发送消息 :{}" +  data);
        Message message = null;
        try {
   
   
            //转换成字符数组
            byte[] messageBody = data.getBytes(RemotingHelper.DEFAULT_CHARSET);
            //创建消息对象
            message = new Message(TOPIC_TEST, TAG_TEST, String.valueOf(System.currentTimeMillis()), messageBody);
            //同步发送消息
            SendResult sendResult = producer.send(message);
            //异步发送消息
            /*producer.send(message, new SendCallback() {
                public void onSuccess(SendResult sendResult) {
                    System.out.println("MQ: CouponProducer生产者发送消息" + sendResult);
                }

                public void onException(Throwable throwable) {
                    System.out.println(throwable.getMessage() +  throwable);
                }
            });*/
            //单向发送 只发送消息,不等待服务器响应,只发送请求不等待应答。
            //producer.sendOneway(message);
        } catch (Exception e) {
   
   
            if (message != null) {
   
   
                System.out.println("producerGroup:ProducerTest,Message:{}" + JSON.toJSON(message));
            }
            System.out.println("MQ: CouponProducer error :" + e);
        }
    }

    @PreDestroy
    public void stop() {
   
   
        if (producer != null) {
   
   
            producer.shutdown();
            System.out.println("MQ:关闭ProducerTest生产者");
        }
    }

    public static void main(String[] args) {
   
   
        ProducerTest producerTest = new ProducerTest();
        producerTest.start();
    }
}
RocketMQ发送普通消息的全流程解读

消息生产者发送消息的demo代码还是较为简单的,核心就几行代码,但在深入研读RocketMQ的Client模块后,发现其发送消息的核心流程还是有一些复杂的。下面将主要从DefaultMQProducer的启动流程、send发送方法和Broker代理服务器的消息处理三方面分别进行分析和阐述。

DefaultMQProducer的启动流程

在客户端发送普通消息的demo代码部分,我们先是将DefaultMQProducer实例启动起来,里面调用了默认生成消息的实现类—DefaultMQProducerImpl的start()方法。

public class DefaultMQProducer extends ClientConfig implements MQProducer {
   
   
	//处理发送消息的类内部类
	protected final transient DefaultMQProducerImpl defaultMQProducerImpl;
	
	public DefaultMQProducer(String producerGroup, RPCHook rpcHook) {
   
   
        this.createTopicKey = "TBW102";
        this.defaultTopicQueueNums = 4;
        this.sendMsgTimeout = 3000;
        this.compressMsgBodyOverHowmuch = 4096;
        this.retryTimesWhenSendFailed = 2;
        this.retryTimesWhenSendAsyncFailed = 2;
        this.retryAnotherBrokerWhenNotStoreOK = false;
        this.maxMessageSize = 4194304;
        this.producerGroup = producerGroup;
        //将自己作为参数传入,实现了两个类相互引用
        this.defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
    }

    public DefaultMQProducer(String producerGroup) {
   
   
        this(producerGroup, (RPCHook)null);
    }
	 ...
	public void start() throws MQClientException {
   
   
	   //调用内部类的start方法
	   this.defaultMQProducerImpl.start();
	}
	...
}	

让我们看看内部类DefaultMQProducerImpl是如何做的

public class DefaultMQProducerImpl implements MQProducerInner {
   
   
	//重载的方法,默认传入true
    public void start() throws MQClientException {
   
   
        this.start(true);
    }
    //启动方法
    public void start(boolean startFactory) throws MQClientException {
   
   
        switch(this.serviceState) {
   
   
       	//第一次调用进入这个case
        case CREATE_JUST:
        	//更改状态防止多次启动
            this.serviceState = ServiceState.START_FAILED;
            //验证groupname
            this.checkConfig();
            //获取了jvm进程id作为producer的intancename名。 
            if (!this.defaultMQProducer.getProducerGroup().equals("CLIENT_INNER_PRODUCER")) {
   
   
                this.defaultMQProducer.changeInstanceNameToPID();
            }
			//获取MQClientManager单例对象,调用getAndCreateMQClientInstance,获取MQClientInstance(mq客户端对象实例) 
            this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQProducer, this.rpcHook);
            
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值