RocketMQ原理—3.源码设计简单分析下

大纲

1.Producer作为生产者是如何创建出来的

2.Producer启动时是如何准备好相关资源的

3.Producer是如何从拉取Topic元数据的

4.Producer是如何选择MessageQueue的

5.Producer与Broker是如何进行网络通信的

6.Broker收到一条消息后是如何存储的

7.Broker是如何实时更新索引文件的

8.Broker是如何实现同步刷盘以及异步刷盘的

9.Broker是如何清理存储较久的磁盘数据的

10.Consumer作为消费者是如何创建和启动的

11.消费者组的多个Consumer会如何分配消息

12.Consumer会如何从Broker拉取一批消息

1.Producer作为生产者是如何创建出来的

(1)NameServer的启动

(2)Broker的启动

(3)Broker的注册和心跳

(4)通过Producer发送消息

(1)NameServer的启动

NameServer启动后的核心架构,如下图示:

NameServer启动后,会有一个NamesrvController组件管理控制NameServer的所有行为,包括内部会启动一个Netty服务器去监听一个9876端口号,然后接收处理Broker和客户端发送过来的请求。

(2)Broker的启动

Broker启动后的核心架构,如下图示:

图片

Broker启动后,也会有一个BrokerController组件管理控制Broker的整体行为,包括初始化Netty服务器用于接收客户端的网络请求、启动处理请求的线程池、执行定时任务的线程池、初始化核心功能组件,同时还会发送注册请求到NameServer去注册自己。

(3)Broker的注册和心跳

Broker启动后,会向NameServer进行注册和定时发送注册请求作为心跳。NameServer会有一个后台进程定时检查每个Broker的最近一次心跳时间,如果长时间没心跳就认为Broker已经故障。如下图示:

图片

(4)通过Producer发送消息

假设RocketMQ集群已经启动好了NameServer,而且还启动了一批Broker,同时Broker都已经把自己注册到NameServer里去了,NameServer也会定时检查这批Broker是否存活。那么就可以让开发好的业务系统去发送消息到RocketMQ集群里,于是需要创建一个Producer实例。

实际上我们开发好的系统,最终都需要创建一个Producer实例,然后通过Producer实例发送消息到RocketMQ的Broker上去。

下面是使用Producer实例发送消息到RocketMQ的代码,可以看到Producer是如何构造出来的。

DefaultMQProducer producer = new DefaultMQProducer("order_producer_group");
producer.setNamesrvAddr("localhost:9876");
producer.start();

构造Producer的过程很简单:也就是创建一个DefaultMQProducer对象实例。在构造方法中,首先会传入所属的Producer分组,然后设置一下NameServer的地址,最后调用它的start()方法启动这个Producer即可。

创建DefaultMQProducer对象实例是一个非常简单的过程:就是创建出一个对象,然后保存它的Producer分组。设置NameServer地址也是一个很简单的过程,就是保存一下NameServer地址。

所以,最关键的还是调用DefaultMQProducer的start()方法去启动Producer这个消息生产者。

2.Producer启动时是如何准备好相关资源的

(1)DefaultMQProducer的start()方法

(2)Producer在第一次向Topic发送消息时才拉取Topic的路由数据

(3)Producer在第一次向Broker发送消息时才与Broker建立网络连接

(1)DefaultMQProducer的start()方法

接下来分析Producer在启动时是如何准备好相关资源的。Producer内部必须要有独立的线程资源,以及需要和Broker已经建立好网络连接,这样才能把消息发送出去。

在构造Producer时,它内部便会构造一个真正用于执行消息发送逻辑的DefaultMQProducerImpl组件。所以,真正的Producer生产者其实是这个DefaultMQProducerImpl组件。那么这个组件在启动的时都干了什么呢?

public class DefaultMQProducer extends ClientConfig implements MQProducer {
    protected final transient DefaultMQProducerImpl defaultMQProducerImpl;
    ...
    @Override
    public void start() throws MQClientException {
        this.setProducerGroup(withNamespace(this.producerGroup));
        this.defaultMQProducerImpl.start();
        if (null != traceDispatcher) {
            try {
                traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
            } catch (MQClientException e) {
                log.warn("trace dispatcher start failed ", e);
            }
        }
    }
    ...
}

public class DefaultMQProducerImpl implements MQProducerInner {
    ...
    public void start() throws MQClientException {
        this.start(true);
    }
    
    public void start(final boolean startFactory) throws MQClientException {
        switch (this.serviceState) {
            case CREATE_JUST:
                this.serviceState = ServiceState.START_FAILED;
                this.checkConfig();
                if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
                    this.defaultMQProducer.changeInstanceNameToPID();
                }
                this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);

                boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);
                if (!registerOK) {
                    this.serviceState = ServiceState.CREATE_JUST;
                    throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup() + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), null);
                }
                this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
                if (startFactory) {
                    mQClientFactory.start();
                }

                log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(), this.defaultMQProducer.isSendMessageWithVIPChannel());
                this.serviceState = ServiceState.RUNNING;
                break;
            case RUNNING:
            case START_FAILED:
            case SHUTDOWN_ALREADY:
                throw new MQClientException("The producer service state not OK, maybe started once, " + this.serviceState + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null);
            default:
                break;
        }
        this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
        this.startScheduledTask();
    }
    ...
}

其实,上述start()方法的具体逻辑暂时不需要深入分析,因为其中的逻辑并没有直接与Producer发送消息相关联。比如拉取Topic的路由数据、选择MessageQueue、跟Broker建立长连接、发送消息到Broker等这些核心逻辑,其实都封装在发送消息的方法中。

(2)Producer在第一次向Topic发送消息时才拉取Topic的路由数据

假设后续Producer要发送消息,那么就要指定往哪个Topic发送消息。因此Producer需要知道Topic的路由数据,比如Topic有哪些MessageQueue,每个MessageQueue在哪些Broker上。如下图示:

图片

从start()方法源码可知,在Producer启动时,并不会去拉取Topic的路由数据。实际上,Producer在第一次向Topic发送消息时,才会去拉取Topic的路由数据。包括这个Topic有几个MessageQueue、每个MessageQueue在哪个Broker上。然后从中选择一个MessageQueue,接着与对应的Broker建立网络连接,最后才把消息发送过去。

(3)Producer在第一次向Broker发送消息时才与Broker建立网络连接

从start()方法源码可知,在Producer启动时,并不会和所有Broker建立网络连接。很多核心的逻辑,包括拉取Topic路由数据、选择MessageQueue、和Broker建立网络连接等,都是在Producer第一次发送消息时才进行处理的。

3.Producer是如何从拉取Topic元数据的

(1)Producer发送消息的方法

(2)Producer拉取Topic路由数据的过程

(1)Producer发送消息的方法

当调用Producer的send()方法发送消息时,最终会调用到DefaultMQProducerImpl的sendDefaultImpl()方法。

在sendDefaultImpl()方法里,开始会有一行非常关键的代码,如下所示:

public class DefaultMQProducerImpl implements MQProducerInner {
    ...
    private SendResult sendDefaultImpl(Message msg, final CommunicationMode communicationMode, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        ...
        TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
        ...
    }
    ...
}

该行代码的意思是,每次Producer发送消息时,都会先检查一下要发送消息的那个Topic的路由数据是否在本地。如果不在,才会发送请求到NameServer去拉取Topic的路由数据,然后缓存在本地。

(2)Producer拉取Topic路由数据的过程

进入tryToFindTopicPublishInfo()方法,会发现其逻辑非常简单:就是会先检查一下自己本地是否有这个Topic的路由数据的缓存,如果没有就发送网络请求到NameServer去拉取,如果有就直接返回本地Topic路由数据缓存,如下图示:

图片

那么Producer是如何发送网络请求到NameServer去拉取Topic路由数据的呢?这其实就对应了tryToFindTopicPublishInfo()方法内的一行代码,如下所示:

public class DefaultMQProducerImpl implements MQProducerInner {
    ...
    private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
        TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
        if (null == topicPublishInfo || !topicPublis
内容概要:本文详细介绍了一种基于Simulink的表贴式永磁同步电机(SPMSM)有限控制集模型预测电流控制(FCS-MPCC)仿真系统。通过构建PMSM数学模型、坐标变换、MPC控制器、SVPWM调制等模块,实现了对电机定子电流的高精度跟踪控制,具备快速动态响应和低稳态误差的特点。文中提供了完整的仿真建模步骤、关键参数设置、核心MATLAB函数代码及仿真结果分析,涵盖转速、电流、转矩和三相电流波形,验证了MPC控制策略在动态性能、稳态精度和抗负载扰动方面的优越性,并提出了参数自整定、加权代价函数、模型预测转矩控制和弱磁扩速等优化方向。; 适合人群:自动化、电气工程及其相关专业本科生、研究生,以及从事电机控制算法研究与仿真的工程技术人员;具备一定的电机原理、自动控制理论和Simulink仿真基础者更佳; 使用场景及目标:①用于永磁同步电机模型预测控制的教学演示、课程设计或毕业设计项目;②作为电机先进控制算法(如MPC、MPTC)的仿真验证平台;③支撑科研中对控制性能优化(如动态响应、抗干扰能力)的研究需求; 阅读建议:建议读者结合Simulink环境动手搭建模型,深入理解各模块间的信号流向与控制逻辑,重点掌握预测模型构建、代价函数设计与开关状态选择机制,并可通过修改电机参数或控制策略进行拓展实验,以增强实践与创新能力。
根据原作 https://pan.quark.cn/s/23d6270309e5 的源码改编 湖北省黄石市2021年中考数学试卷所包含的知识点广泛涉及了中学数学的基础领域,涵盖了实数、科学记数法、分式方程、几何体的三视图、立体几何、概率统计以及代数方程等多个方面。 接下来将对每道试题所关联的知识点进行深入剖析:1. 实数与倒数的定义:该题目旨在检验学生对倒数概念的掌握程度,即一个数a的倒数表达为1/a,因此-7的倒数可表示为-1/7。 2. 科学记数法的运用:科学记数法是一种表示极大或极小数字的方法,其形式为a×10^n,其中1≤|a|<10,n为整数。 此题要求学生运用科学记数法表示一个天文单位的距离,将1.4960亿千米转换为1.4960×10^8千米。 3. 分式方程的求解方法:考察学生解决包含分母的方程的能力,题目要求找出满足方程3/(2x-1)=1的x值,需通过消除分母的方式转化为整式方程进行解答。 4. 三视图的辨认:该题目测试学生对于几何体三视图(主视图、左视图、俯视图)的认识,需要识别出具有两个相同视图而另一个不同的几何体。 5. 立体几何与表面积的计算:题目要求学生计算由直角三角形旋转形成的圆锥的表面积,要求学生对圆锥的底面积和侧面积公式有所了解并加以运用。 6. 统计学的基础概念:题目涉及众数、平均数、极差和中位数的定义,要求学生根据提供的数据信息选择恰当的统计量。 7. 方程的整数解求解:考察学生在实际问题中进行数学建模的能力,通过建立方程来计算在特定条件下帐篷的搭建方案数量。 8. 三角学的实际应用:题目通过在直角三角形中运用三角函数来求解特定线段的长度。 利用正弦定理求解AD的长度是解答该问题的关键。 9. 几何变换的应用:题目要求学生运用三角板的旋转来求解特定点的...
Python基于改进粒子群IPSO与LSTM的短期电力负荷预测研究内容概要:本文围绕“Python基于改进粒子群IPSO与LSTM的短期电力负荷预测研究”展开,提出了一种结合改进粒子群优化算法(IPSO)与长短期记忆网络(LSTM)的混合预测模型。通过IPSO算法优化LSTM网络的关键参数(如学习率、隐层节点数等),有效提升了模型在短期电力负荷预测中的精度与收敛速度。文中详细阐述了IPSO算法的改进策略(如引入自适应惯性权重、变异机制等),增强了全局搜索能力与避免早熟收敛,并利用实际电力负荷数据进行实验验证,结果表明该IPSO-LSTM模型相较于传统LSTM、PSO-LSTM等方法在预测准确性(如MAE、RMSE指标)方面表现更优。研究为电力系统调度、能源管理提供了高精度的负荷预测技术支持。; 适合人群:具备一定Python编程基础、熟悉基本机器学习算法的高校研究生、科研人员及电力系统相关领域的技术人员,尤其适合从事负荷预测、智能优化算法应用研究的专业人士。; 使用场景及目标:①应用于短期电力负荷预测,提升电网调度的精确性与稳定性;②为优化算法(如粒子群算法)与深度学习模型(如LSTM)的融合应用提供实践案例;③可用于学术研究、毕业论文复现或电力企业智能化改造的技术参考。; 阅读建议:建议读者结合文中提到的IPSO与LSTM原理进行理论学习,重点关注参数优化机制的设计思路,并动手复现实验部分,通过对比不同模型的预测结果加深理解。同时可拓展尝试将该方法应用于其他时序预测场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值