RocketMQ学习笔记(四)——生产者、消息发送

本文参考地址:RocketMQ官方中文文档

                        丁威《RocketMQ技术内幕》

                        杨开元《RocketMQ实战与原理解析》

1.生产者:

负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器

RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息,单向发送不需要

 2.不同类型的生产者

由于生产者在向broker消息队列中写入消息时,根据不同的业务场景采用了不同的写入策略,从而产生出不同类型的生产者类型,其中包含同步、异步、单向、延迟、事务等类型,但最核心也是默认的生产者类是DefaultMQProducer

使用(橙色步骤必不可少):

        ①通过消息组名创建生产者实例

        DefaultMQProducer producer = new DefaultMQProducer("group_name");

        ②为了帮助JVM区分不同的Producer,需要设置InstanceName(不设置为"DEFAULT")

        produce.setInstanceName("instanceName");

        ③设置发送失败重试次数,防止网络异常消息丢失

        producer.setRetryTimesWhenSendFailed(5);

        ④设置NameServer管理服务地址(可以是集群)

        producer.setNameserverAddr("ip1:9876;ip2:9876......");

        ⑤启动Producer实例

        producer.start();

        ⑥组装消息

        public Message(

                String topic,  //消息主题

                String tags,  //过滤消息TAG

                String keys,  //消息索引键,MQ可以通过这个key快速定位本条消息

                int flag,  //消息标记,不做处理

                byte[] body,  //自定义消息体,即消息内容

                boolean waitStoreMsgOK  //消息发送时是否需要等待消息存储完成时再返回

        )

        ⑦发送消息

        SendResult sendResult = producer.send(message);  //同步发送

        producer.send(msg, new SendCallback(){   //成功或失败的处理方式

                @Override
                public void onSuccess(SendResult sendResult) {
                        System.out.printf("%-10d OK %s %n", index,
                            sendResult.getMsgId());
                }
                @Override
                public void onException(Throwable e) {
                        System.out.printf("%-10d Exception %s %n", index, e);
                        e.printStackTrace();
                }

        });  //异步,设置回调函数

        producer.send(message);  //单向发送,不关心发送结果

        ⑧关闭Producer实例

        producer.shutdown();

3.生产者启动流程(原创):

4.消息发送方式:

(1)同步 生产者给MQ发送消息后,需等待Broker的响应结果,多用于重要的消息通知,短信通知

public class SyncProducer {
	public static void main(String[] args) throws Exception {
    	// 实例化消息生产者Producer
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
    	// 设置NameServer的地址
    	producer.setNamesrvAddr("localhost:9876");
    	// 启动Producer实例
        producer.start();
    	for (int i = 0; i < 100; i++) {
    	    // 创建消息,并指定Topic,Tag和消息体
    	    Message msg = new Message("TopicTest" /* Topic */,
        	"TagA" /* Tag */,
        	("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
        	);
        	// 发送消息到一个Broker
            SendResult sendResult = producer.send(msg);
            // 通过sendResult返回消息是否成功送达
            System.out.printf("%s%n", sendResult);
    	}
    	// 如果不再发送消息,关闭Producer实例。
    	producer.shutdown();
    }
}

(2)异步 生产者在给MQ发送消息时,会指定一个回调函数(在新的线程中执行),然后自身线程立即返回,不会同步阻塞等待,然后结束,多用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应

public class AsyncProducer {
	public static void main(String[] args) throws Exception {
    	// 实例化消息生产者Producer
        DefaultMQProducer producer = new             
        DefaultMQProducer("please_rename_unique_group_name");
    	// 设置NameServer的地址
        producer.setNamesrvAddr("localhost:9876");
    	// 启动Producer实例
        producer.start();
        producer.setRetryTimesWhenSendAsyncFailed(0);
	    int messageCount = 100;
        // 根据消息数量实例化倒计时计算器
	    final CountDownLatch2 countDownLatch = new CountDownLatch2(messageCount);
    	for (int i = 0; i < messageCount; i++) {
                final int index = i;
            	// 创建消息,并指定Topic,Tag和消息体
                Message msg = new Message("TopicTest",
                    "TagA",
                    "OrderID188",
                    "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
                // SendCallback接收异步返回结果的回调
                producer.send(msg, new SendCallback() {
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        System.out.printf("%-10d OK %s %n", index,
                            sendResult.getMsgId());
                    }
                    @Override
                    public void onException(Throwable e) {
      	              System.out.printf("%-10d Exception %s %n", index, e);
      	              e.printStackTrace();
                    }
            	});
    	}
	    // 等待5s
	    countDownLatch.await(5, TimeUnit.SECONDS);
    	// 如果不再发送消息,关闭Producer实例。
    	producer.shutdown();
    }
}

(3)单向 生产者给MQ发送消息后不会阻塞等待、也不会指定回调函数,只管发;多用于不特别关心发送结果的场景,例如日志发送

public class OnewayProducer {
    public static void main(String[] args) throws Exception{
        // 实例化消息生产者Producer
        DefaultMQProducer producer = new     
        DefaultMQProducer("please_rename_unique_group_name");
        // 设置NameServer的地址
        producer.setNamesrvAddr("localhost:9876");
        // 启动Producer实例
        producer.start();
        for (int i = 0; i < 100; i++) {
            // 创建消息,并指定Topic,Tag和消息体
            Message msg = new Message("TopicTest" /* Topic */,
                "TagA" /* Tag */,
                ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            // 发送单向消息,没有任何返回结果
            producer.sendOneway(msg);
        }
        // 如果不再发送消息,关闭Producer实例。
        producer.shutdown();
    }
}

5.消息类型:

(1)顺序消息

        消息有序指的是一类消息消费时,能按照发送的顺序来消费,RocketMQ可以严格的保证消息有序。

        顺序消息分为全局顺序消息与分区顺序消息

        ①全局顺序是指某个Topic下的所有消息都要保证顺序

                全局顺序对于指定的一个 Topic,所有消息按照严格的先入先出(FIFO)的顺序进行发布和消费。

                适用场景:性能要求不高,所有的消息严格按照 FIFO 原则进行消息发布和消费的场景

        ②部分顺序消息只要保证每一组消息被顺序消费即可
                分区顺序对于指定的一个 Topic,所有消息根据 sharding key 进行区块分区。 同一个分区内的消息按照严格的 FIFO 顺序进行发布和消费。 Sharding key 是顺序消息中用来区分不同分区的关键字段,和普通消息的 Key 是完全不同的概念。

                适用场景:性能要求高,以 sharding key 作为分区字段,在同一个区块中严格的按照 FIFO 原则进行消息发布和消费的场景

 实现:

        同组任务可以通过唯一的参数id(比如订单id),和Message集合长度取余,拿到index队列,只要保证传入参数id不变,就能保证同一组消息发送到同一个消息队列中,加之消息队列可以保证FIFO的特性,因此可以做到顺序生产、顺序消费

(2)延时消息

        RocketMQ 支持定时消息,但是不支持任意时间精度,仅支持特定的level从1s到2h分别对应着等级1到18;

        消息消费失败后也会进入延时消息队列,消息发送时间与设置的延时等级和重试次数有关

        消息延时等级:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

        使用:

        public class ScheduledMessageProducer {
           public static void main(String[] args) throws Exception {
              // 实例化一个生产者来产生延时消息
              DefaultMQProducer producer = new         DefaultMQProducer("ExampleProducerGroup");
              // 启动生产者
              producer.start();
              int totalMessagesToSend = 100;
              for (int i = 0; i < totalMessagesToSend; i++) {
                  Message message = new Message("TestTopic", ("Hello scheduled message " +         i).getBytes());

      //设置延时等级3,消息将在10s之后发送(现在只支持固定的几个时间,详看delayTimeLevel)
                  message.setDelayTimeLevel(3);
                  // 发送消息
                  producer.send(message);
              }
               // 关闭生产者
              producer.shutdown();
          }
        }

6.消息发送基本流程: 

(1)消息验证

先确保生产者处于运行状态,再确定消息是否符合相应的规范
规范要求:
    主题名称、消息体不能为空
    消息长度不等于0,且不允许超过最大允许长度(maxMessageSize = 1024*1024*4)

(2)寻找路由

消息发送前,先获取主题的路由信息,获取后才知道消息要发送到哪个具体Broker节点

        private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
                // 获取缓存中topic的路由信息
                TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
                if (null == topicPublishInfo || !topicPublishInfo.ok()) {
                    // 缓存中不存在路由信息,那么就去NameServer中查询
                    this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
                    this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
                    topicPublishInfo = this.topicPublishInfoTable.get(topic);
                }
                if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
                    // 存在topic路由信息,则直接返回路由信息
                    return topicPublishInfo;
                } else {
                    this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true,         this.defaultMQProducer);
                    topicPublishInfo = this.topicPublishInfoTable.get(topic);
                    return topicPublishInfo;
                }
       }

(3)选择队列

public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
        if (this.sendLatencyFaultEnable) {
            try {
                int index = tpInfo.getSendWhichQueue().incrementAndGet();
                for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
                    int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                    if (pos < 0)
                        pos = 0;
                    MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
                    if (latencyFaultTolerance.isAvailable(mq.getBrokerName()))
                        return mq;
                }
                final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
                int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
                if (writeQueueNums > 0) {
                    final MessageQueue mq = tpInfo.selectOneMessageQueue();
                    if (notBestBroker != null) {
                        mq.setBrokerName(notBestBroker);
                        mq.setQueueId(tpInfo.getSendWhichQueue().incrementAndGet() % writeQueueNums);
                    }
                    return mq;
                } else {
                    latencyFaultTolerance.remove(notBestBroker);
                }
            } catch (Exception e) {
                log.error("Error occurred when selecting message queue", e);
            }
            return tpInfo.selectOneMessageQueue();
        }
        return tpInfo.selectOneMessageQueue(lastBrokerName);
}

(4)消息发送

此处见——>4.消息发送方式

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值