RocketMq 源码学习

1、Producer有哪些消息发送方式

      1)同步发送(SYNC)

             同步发送是指消息发送方发出数据后,会在收到接收方发回响应之后才发下一个数据包的通讯方式。

      2)异步发送(ASYNC)

            异步发送是指发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。 MQ 的异步发送,需要用户实现异步发送回调接口(SendCallback)。消息发送方在发送了一条消息后,不需要等待服务器响应即可返回,进行第二条消息发送。发送方通过回调接口接收服务器响应,并对响应结果进行处理。

      3)单向发送(Oneway)

            单向(Oneway)发送特点为发送方只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答。 此方式发送消息的过程耗时非常短。

      4)消息发送源码

/**

 * 发送消息核心方法,并返回发送结果

 *

 * @param msg 消息

 * @param mq 消息队列

 * @param communicationMode 通信模式

 * @param sendCallback 发送回调

 * @param topicPublishInfo Topic发布信息

 * @param timeout  发送消息请求超时时间

 * @return 发送结果

 * @throws MQClientException 当Client发生异常

 * @throws RemotingException 当请求发生异常

 * @throws MQBrokerException 当Broker发生异常

 * @throws InterruptedException 当线程被打断

 */

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 {

    // 获取 broker地址

    String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());

    if (null == brokerAddr) {

        tryToFindTopicPublishInfo(mq.getTopic());

        brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());

    }

    //

    SendMessageContext context = null;

    if (brokerAddr != null) {

        // 是否使用broker vip通道。broker会开启两个端口对外服务。

        brokerAddr = MixAll.brokerVIPChannel(this.defaultMQProducer.isSendMessageWithVIPChannel(), brokerAddr);

        byte[] prevBody = msg.getBody(); // 记录消息内容。下面逻辑可能改变消息内容,例如消息压缩。

        try {

            // 设置唯一编号

            MessageClientIDSetter.setUniqID(msg);

            // 消息压缩

            int sysFlag = 0;

            if (this.tryToCompressMessage(msg)) {

                sysFlag |= MessageSysFlag.COMPRESSED_FLAG;

            }

            // 事务

            final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);

            if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {

                sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;

            }

            // hook:发送消息校验

            if (hasCheckForbiddenHook()) {

                CheckForbiddenContext checkForbiddenContext = new CheckForbiddenContext();

                checkForbiddenContext.setNameSrvAddr(this.defaultMQProducer.getNamesrvAddr());

                checkForbiddenContext.setGroup(this.defaultMQProducer.getProducerGroup());

                checkForbiddenContext.setCommunicationMode(communicationMode);

                checkForbiddenContext.setBrokerAddr(brokerAddr);

                checkForbiddenContext.setMessage(msg);

                checkForbiddenContext.setMq(mq);

                checkForbiddenContext.setUnitMode(this.isUnitMode());

                this.executeCheckForbiddenHook(checkForbiddenContext);

            }

            // hook:发送消息前逻辑

            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);

            }

            // 构建发送消息请求

            SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();

            requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());

            requestHeader.setTopic(msg.getTopic());

            requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());

            requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());

            requestHeader.setQueueId(mq.getQueueId());

            requestHeader.setSysFlag(sysFlag);

            requestHeader.setBornTimestamp(System.currentTimeMillis());

            requestHeader.setFlag(msg.getFlag());

            requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));

            requestHeader.setReconsumeTimes(0);

            requestHeader.setUnitMode(this.isUnitMode());

            if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { // 消息重发Topic

                String reconsumeTimes = MessageAccessor.getReconsumeTime(msg);

                if (reconsumeTimes != null) {

                    requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes));

                    MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME);

                }

                String maxReconsumeTimes = MessageAccessor.getMaxReconsumeTimes(msg);

                if (maxReconsumeTimes != null) {

                    requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes));

                    MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_MAX_RECONSUME_TIMES);

                }

            }

            // 发送消息

            SendResult sendResult = null;

            switch (communicationMode) {

                case ASYNC:

                    sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(//

                        brokerAddr, // 1

                        mq.getBrokerName(), // 2

                        msg, // 3

                        requestHeader, // 4

                        timeout, // 5

                        communicationMode, // 6

                        sendCallback, // 7

                        topicPublishInfo, // 8

                        this.mQClientFactory, // 9

                        this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(), // 10

                        context, //

                        this);

                    break;

                case ONEWAY:

                case SYNC:

                    sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(

                        brokerAddr,

                        mq.getBrokerName(),

                        msg,

                        requestHeader,

                        timeout,

                        communicationMode,

                        context,

                        this);

                    break;

                default:

                    assert false;

                    break;

            }

            // hook:发送消息后逻辑

            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;

        finally {

            msg.setBody(prevBody);

        }

    }

    // broker为空抛出异常

    throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist"null);

}

      5)小结

            同步和异步的发送方式,消息不会丢失,单向发送的速度最快,但存在消息丢失的可能。

2、消息有哪些属性和限制

      1)topic

           一个应用尽可能用一个 Topic,消息子类型用 tags 来标识,tags 可以由应用自由设置。 只有发送消息设置了tags,消费方在订阅消息时,才可以利用 tags 在 broker 做消息过滤。

      2)body 消息内容

      3)properties

           拓展字段,如key、tag还有一些mq内部使用的属性,如重试次数、原始topic、消费时间等等。

      4)rocketMQ的消息内容默认最大字节数为4MB

3、Producer发送消息是如何得知发到哪个broker的

      每个应用在收发消息之前,一般会调用一次producer.start()/consumer.start()做一些初始化工作,其中包括:创建需要的实例对象,如MQClientInstance;设置定时任务,如从Nameserver中定时更新本地的Topic route info,发送心跳信息到所有的 broker,动态调整线程池的大小,把当前producer加入到指定的组中等等。

      客户端会缓存路由信息TopicPublishInfo, 同时定期从NameServer取Topic路由信息,每个Broker与NameServer集群中的所有节点建立长连接,定时注册Topic信息到所有的NameServer。

      Producer在发送消息的时候会去查询本地的topicPublishInfoTable(一个ConcurrentHashMap),如果没有命中的话就会询问NameServer得到路由信息 (RequestCode=GET_ROUTEINTO_BY_TOPIC) 如果nameserver中也没有查询到(表示该主题的消息第一次发送),那么将会发送一个default的topic进行路由查询。

4、消息发送失败如何处理

      Producer 的 send 方法本身支持内部重试,重试逻辑如下:
           1) 至多重试 3 次。
           2) 如果发送失败,则轮转到下一个 Broker。
           3) 这个方法的总耗时时间不超过 sendMsgTimeout 设置的值,默认 10s。
      所以,如果本身向 broker 发送消息产生超时异常,就不会再做重试。
      以上策略仍然不能保证消息一定发送成功,为保证消息一定成功,建议应用这样做:如果调用 send 同步方法发送失败,则尝试将消息存储到 db,由后台线程定时重试,保证消息一定到达 Broker。

5、有序消息是如何保证的?

       1)普通顺序消息

            Producer 将相关联的消息发送到相同的消息队列。需要业务层自己决定哪些消息应该顺序到达,然后发送的时候通过规则(如hash、mod)映射到同一个队列,因为没有谁比业务自己更加知道关于消息顺序的特点。这样的顺序是相对顺序,局部顺序,因为发送方只保证把这些消息顺序的发送到broker上的同一队列,但是不保证其他Producer也会发送消息到那个队列,所以需要Consumer在拉到消息后做一些过滤。官方发送顺序消息的例子:

public class OrderedProducer {

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

        //Instantiate with a producer group name.

        MQProducer producer = new DefaultMQProducer("example_group_name");

        //Launch the instance.

        producer.start();

        String[] tags = new String[] {"TagA""TagB""TagC""TagD""TagE"};

        for (int i = 0; i < 100; i++) {

            int orderId = i % 10;

            //Create a message instance, specifying topic, tag and message body.

            Message msg = new Message("TopicTestjjj", tags[i % tags.length], "KEY" + i,

                    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));

            SendResult sendResult = producer.send(msg, new MessageQueueSelector() {

            @Override

            public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {

                Integer id = (Integer) arg;

                int index = id % mqs.size();

                return mqs.get(index);

            }

            }, orderId);

            System.out.printf("%s%n", sendResult);

        }

        //server shutdown

        producer.shutdown();

    }

}

       2)完全严格顺序 :在普通顺序消息的基础上, Consumer 严格顺序消费。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、rocketmq入门到精通视频教程目录大纲 001-001_RocketMQ_简介 002-002_RocketMQ_核心概念详解 003-003_RocketMQ_集群构建模型详解(一) 004-004_RocketMQ_集群构建模型详解(二) 005-005_RocketMQ_双主模式集群环境搭建 006-006_RocketMQ_控制台使用讲解 007-007_RocketMQ_Broker配置文件详解 008-008_RocketMQ_helloworld示例讲解 009-009_RocketMQ_整体架构概述详解 010-010_RocketMQ_Producer_API详解 011-011_RocketMQ_Producer_顺序消费机制详解 012-012_RocketMQ_Producer_事务消息机制详解 013-013_RocketMQ_Consumer_Push和Pull模式及使用详解 014-014_RocketMQ_Consumer_配置参数详解 015-015_RocketMQ_Consumer_重试策略详解 016-016_RocketMQ_Consumer_幂等去重策略详解 017-017_RocketMQ_消息模式 及使用讲解 018-018_RocketMQ_双主双从集群环境搭建与使用详解 019-019_RocketMQ_FilterServer机制及使用详解 020-020_RocketMQ_管理员命令 二、rocketmq实战视频教程目录大纲 01_rocketmq_实战项目介绍 02_rocketMQ实战项目设计(一) 03_rocketMQ实战项目设计(二) 04_rocketMQ实战-环境搭建(一) 05_rocketMQ实战-环境搭建(二) 06_rocketMQ实战-生产者与spring结合 07_rocketMQ实战-消费者与spring结合 08_rocketMQ实战-数据库模型设计 09_rocketMQ实战-数据库DAO代码生成 10_rocketMQ实战-远程RPC接口设计与实现(一) 11_rocketMQ实战-远程RPC接口设计与实现(二) 12_rocketMQ实战-远程RPC接口设计与实现(三) 13_rocketMQ实战-下单流程(一) 14_rocketMQ实战-下单流程(二) 15_rocketMQ实战-下单流程(三) 16_rocketMQ实战-下单流程(四) 17_rocketMQ实战-下单流程(五) 18_rocketMQ实战-下单流程(六) 19_rocketMQ实战-下单流程(七) 20_rocketMQ实战-下单流程(八)-商品库存 21_rocketMQ实战-下单流程(九)-商品库存 22_rocketMQ实战-下单流程(十)-支付模块 23_rocketMQ实战-整体联调

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值