本文参考地址: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.生产者启动流程(原创):

rabitmq生产者设置qos leavel rocketmq生产者_发送消息

rabitmq生产者设置qos leavel rocketmq生产者_发送消息_02

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的特性,因此可以做到顺序生产、顺序消费

rabitmq生产者设置qos leavel rocketmq生产者_中间件底层技术_03

(2)延时消息

支持定时消息,但是不支持任意时间精度,仅支持特定的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.消息发送方式