rocketmq总结与特性实现原理

详细各个模块启动流程可以查看该篇文章:https://blog.csdn.net/u010597819/article/details/86646245

rocketmq总结与特性实现原理_第1张图片

发送消息

  1. 请求nameserver根据主题获取路由
  2. 根据路由信息选择消息队列MessageQueue
  3. 如果启用sendLatencyFaultEnable
  4. 根据上次索引(如果不存在则是随机数)递增后作为下标读取MessageQueue队列列表中对应的队列,broker有效,并且如果上次brokerName为空或者与上次brokerName相同返回队列
  5. 按照latencyFaultTolerance获取brokerName,如果存在根据brokerName获取可写队列数量,使用随机数对其取余,根据主题发布信息获取队列selectOneMessageQueue,并使用前面获取到的broker及queueId覆盖队列属性返回
  6. 直接根据上次索引递增对队列数取余获取队列返回
  7. 直接根据上次索引递增对队列数取余获取队列返回,但不能与lastBrokerName上次brokerName名称相同
  8. broker角色
  9. master处理生产者业务
  10. slave定期同步master消息至本地,支持同步与异步两种模式

对于同一个主题的消息,对于多master的broker是轮询方式发送消息至master,master-slave的broker可以支持同步或异步方式进行同步数据。
如此设计的好处是服务的水平扩展与负载均衡扩展的能力都是强大的,但是高可用方案则需要从同步与异步的实现主从刷新中进行一个权衡抉择

消费消息

我们按照无序、并发、group、集群模式(点对点模式,优化了其中的点为group抽象)、多消费者推送的方式讨论。
详细过程此文不再赘述

  1. 消费者订阅主题,可以允许多个消费者订阅同一主题,group分组可以相同,可以不同
  2. 同一个group中只有一个消费者会收到推送消息
  3. 不同group则每个group中一定有一个消费者收到推送消息
  4. 广播模式则是所有消费者均可以收到推送消息
  5. 启动消费者
  6. 按照分组group注册消费者信息
  7. 接收broker推送的消息,因为rmq是拉取模型的设计,所以需要不断的根据消息下标去拉取消息实现消息的推送
  8. 如果是广播模式,下标维护在本地文件中
  9. 如果是集群模式,下标维护在远程broker
  10. 根据消息队列MessageQueue封装为pullRequest拉取请求,请求broker拉取消息
  11. 按照group选择一个消费者拉取消费消息,所以本地启动属于同一个group的多个消费者不会加快本地消费消息的速度,模型中就可以看出group与消费者是一对一关系,但是如果是多个master的broker会增快本地消费的速度,因为三个同时生产消息,消费者一个批次消费3个master的last最后的下标处的消息,是否会略快于1个broker(master)的场景???
// 消费者与group分组是一对一关系
ConcurrentMap<String/* group */, MQConsumerInner> consumerTable = new ConcurrentHashMap<String, MQConsumerInner>();

rocketmq总结与特性实现原理_第2张图片
图片来源:https://www.jianshu.com/p/824066d70da8

Name Server

命名服务器,上图已经很明确了其作用,broker的发现服务。官方的解释为:命名服务器是一个路由信息提供者。生产者或者消费者客户端通过主题查找对应的broker列表

Name server serves as the routing information provider. 
Producer/Consumer clients look up topics to find the corresponding broker list.

可以看出命名服务器的重要性,如果重要的角色,当然必须要求是高可用的,那么命名服务器可以部署为集群,那么集群中各个命名服务器节点的数据是否需要同步,如果需要如果同步?分布式一致性如何保证?
看过源码的同学应该知道,没看过的同学可以查看文章开头列出的源码学习博文。在启动每个broker时会向所有的命名服务器发送上报信息的,并且会启动线程30秒上报一次
所以命名服务器并不是越多越好,由于是30秒上报一次信息,命名服务器是默认10秒扫描一次broker的活跃状态,所以是有可能broker宕机后broker非活跃状态有10秒的无感知

事务消息

从官方的事务实现案例中可以看到LocalTransactionState事务的状态由3种(commit、rollback、未知),了解过分布式一致性算法的同学应该对2-PC,3-PC有所了解,看到此处状态便能猜到实现采用2-PC模式,在官网中也已经给出了答案,采用2阶段提交模式实现消息的分布式一致性。当然该算法的弊端有兴趣的同学可以参考《从Paxos到Zookeeper》一书,里面给出了非常详细介绍,此处不再赘述

public enum LocalTransactionState {
    COMMIT_MESSAGE,
    ROLLBACK_MESSAGE,
    UNKNOW,
}

使用方法可以参考官方案例,实现TransactionListener接口

executeLocalTransaction方法:发送prepare(准备)消息并根据事务id缓存事务的状态,发送消息的方式为同步模式
checkLocalTransaction方法:上个方法发送prepare(准备)消息后没有得到响应,broker将会发送校验消息检查事务状态,该方法将会被调用,读取本地事务状态

  1. 客户端发送消息至broker
  2. broker预写入消息,响应预写入状态
  3. 如果成功提交commit消息,否则提交rollback消息
  4. broker接收消息进行提交或rollback消息,并删除预处理消息
  5. broker启动默认1分钟扫描一次客户端本地事务状态,进行兜底处理事务消息

消息跟踪-Message Track

创建DefaultMQProducer时指定enableMsgTrace即可启用消息跟踪,跟踪的实现使用的钩子方法,在发送消息的模板中,发送消息的前后会调用钩子方法的before与after,输出一些跟踪信息

  1. 注册钩子方法
public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic) {
    this.producerGroup = producerGroup;
    defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
    //if client open the message trace feature
    if (enableMsgTrace) {
        try {
            AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(customizedTraceTopic, rpcHook);
            dispatcher.setHostProducer(this.getDefaultMQProducerImpl());
            traceDispatcher = dispatcher;
            this.getDefaultMQProducerImpl().registerSendMessageHook(
                new SendMessageTraceHookImpl(traceDispatcher));
        } catch (Throwable e) {
            log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
        }
    }
}
  1. 调用钩子方法
// 同步调用
private SendResult sendKernelImpl(final Message msg,
                                  final MessageQueue mq,
                                  final CommunicationMode communicationMode,
                                  final SendCallback sendCallback,
                                  final TopicPublishInfo topicPublishInfo,
                                  final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    ...
            if (this.hasSendMessageHook()) {
                context = new SendMessageContext();
                context.setProducer(this);
                context.setProducerGroup(this.defaultMQProducer.getProducerGroup());
                context.setCommunicationMode(communicationMode);
                context.setBornHost(this.defaultMQProducer.getClientIP());
                context.setBrokerAddr(brokerAddr);
                context.setMessage(msg);
                context.setMq(mq);
                String isTrans = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
                if (isTrans != null && isTrans.equals("true")) {
                    context.setMsgType(MessageType.Trans_Msg_Half);
                }

                if (msg.getProperty("__STARTDELIVERTIME") != null || msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null) {
                    context.setMsgType(MessageType.Delay_Msg);
                }
                this.executeSendMessageHookBefore(context);
            }...
            SendResult sendResult = null;
            switch (communicationMode) {
				// 异步模式会在异步发送之后回调after钩子方法
                case ASYNC:
                    ...

            if (this.hasSendMessageHook()) {
                context.setSendResult(sendResult);
                this.executeSendMessageHookAfter(context);
            }

            return sendResult;
        } catch (RemotingException e) {
            if (this.hasSendMessageHook()) {
                context.setException(e);
                this.executeSendMessageHookAfter(context);
            }
            throw e;
        } catch (MQBrokerException e) {
            if (this.hasSendMessageHook()) {
                context.setException(e);
                this.executeSendMessageHookAfter(context);
            }
            throw e;
        } catch (InterruptedException e) {
            if (this.hasSendMessageHook()) {
                context.setException(e);
                this.executeSendMessageHookAfter(context);
            }
            throw e;
        } ...
}

// 异步调用
private void sendMessageAsync(
    final String addr,
    final String brokerName,
    final Message msg,
    final long timeoutMillis,
    final RemotingCommand request,
    final SendCallback sendCallback,
    final TopicPublishInfo topicPublishInfo,
    final MQClientInstance instance,
    final int retryTimesWhenSendFailed,
    final AtomicInteger times,
    final SendMessageContext context,
    final DefaultMQProducerImpl producer
) throws InterruptedException, RemotingException {
    this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
        @Override
        public void operationComplete(ResponseFuture responseFuture) {
            RemotingCommand response = responseFuture.getResponseCommand();
            if (null == sendCallback && response != null) {

                try {
                    SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response);
                    if (context != null && sendResult != null) {
                        context.setSendResult(sendResult);
                        context.getProducer().executeSendMessageHookAfter(context);
                    }
                } catch (Throwable e) {
                }

                producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false);
                return;
            }

            if (response != null) {
                try {
                    SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response);
                    assert sendResult != null;
                    if (context != null) {
                        context.setSendResult(sendResult);
                        context.getProducer().executeSendMessageHookAfter(context);
                    }
...
}

你可能感兴趣的:(java)