1. 消息发送方式

1.1 同步发送

发送异步消息会丢失 异步消息传递工作流程_消息发送

简单来说,同步发送就是指 producer 发送消息后,会在接收到 broker 响应后才继续发下一条消息的通信方式。

由于这种同步发送的方式确保了消息的可靠性,同时也能及时得到消息发送的结果,故而适合一些发送比较重要的消息场景,比如说重要的通知邮件、营销短信等等。在实际应用中,这种同步发送的方式还是用得比较多的。

1.2 异步发送

发送异步消息会丢失 异步消息传递工作流程_发送异步消息会丢失_02

接着就是异步发送,异步发送是指 producer 发出一条消息后,不需要等待 broker 响应,就接着发送下一条消息的通信方式。需要注意的是,不等待 broker 响应,并不意味着 broker 不响应,而是通过回调接口来接收 broker 的响应。所以要记住一点,异步发送同样可以对消息的响应结果进行处理。

由于异步发送不需要等待 broker 的响应,故在一些比较注重 RT(响应时间)的场景就会比较适用。比如,在一些视频上传的场景,我们知道视频上传之后需要进行转码,如果使用同步发送的方式来通知启动转码服务,那么就需要等待转码完成才能发回转码结果的响应,由于转码时间往往较长,很容易造成响应超时。此时,如果使用的是异步发送通知转码服务,那么就可以等转码完成后,再通过回调接口来接收转码结果的响应了。

producer.send(message,new SendCallback() {
			
			public void onSuccess(SendResult sendResult) {
				// TODO Auto-generated method stub
				System.out.println("ok");
			}
			
			public void onException(Throwable e) {
				// TODO Auto-generated method stub
				e.printStackTrace();
				System.out.println("err");
			}
		});

1.3 单向发送

发送异步消息会丢失 异步消息传递工作流程_发送异步消息会丢失_03

单向发送,见名知意,就是一种单方向通信方式,也就是说 producer 只负责发送消息,不等待 broker 发回响应结果,而且也没有回调函数触发,这也就意味着 producer 只发送请求不等待响应结果。

由于单向发送只是简单地发送消息,不需要等待响应,也没有回调接口触发,故发送消息所耗费的时间非常短,同时也意味着消息不可靠。所以这种单向发送比较适用于那些耗时要求非常短,但对可靠性要求并不高的场景,比如说日志收集。

1.4 批量消息发送

可以多条消息打包一起发送,减少网络传输次数提高效率。

producer.send(Collection c)方法可以接受一个集合 实现批量发送

public SendResult send(
        Collection<Message> msgs) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        return this.defaultMQProducerImpl.send(batch(msgs));
    }
  • 批量消息要求必须要具有同一topic、相同消息配置
  • 不支持延时消息
  • 建议一个批量消息最好不要超过1MB大小
  • 如果不确定是否超过限制,可以手动计算大小分批发送

1.5 总结

下面通过一张表格,简单总结一下同步发送、异步发送和单向发送的特点。

发送方式

发送TPS

发送结果响应

可靠性

同步发送



不丢失

异步发送



不丢失

单向发送


没有

可能丢失

可以看到,从发送 TPS 来看,由于单向发送不需要等待响应也没有回调接口触发,发送速度非常快,一般都是微秒级的,在消息体大小一样的情况下,其发送 TPS 最大。而同步发送,需要等待响应结果的返回,受网络状况的影响较大,故发送 TPS 就比较小。异步发送不等待响应结果,发送消息时几乎不受网络的影响,故相比同步发送来说,其发送 TPS 要大得多。

关于可靠性,大家需要牢记前面提过的,异步发送并不意味着消息不可靠,异步发送也是会接收到响应结果,也能对响应结果进行处理。即使发送失败,也可以通过一些补偿手段进行消息重发。和同步发送比起来,异步发送的发送 TPS 更大,更适合那些调用链路较长的一些场景。在实际使用中,同步发送和异步发送都是较为常用的两种方式,大家要视具体业务场景进行合理地选择。

2. 消息类型

2.1 普通消息

普通消息也叫做无序消息,简单来说就是没有顺序的消息,producer 只管发送消息,consumer 只管接收消息,至于消息和消息之间的顺序并没有保证,可能先发送的消息先消费,也可能先发送的消息后消费。

举个简单例子,producer 依次发送 order id 为 1、2、3 的消息到 broker,consumer 接到的消息顺序有可能是 1、2、3,也有可能是 2、1、3 等情况,这就是普通消息。

因为不需要保证消息的顺序,所以消息可以大规模并发地发送和消费,吞吐量很高,适合大部分场景。

2.1.1 代码示例

生产者:

public class Producer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
 
        //声明并初始化一个producer
        //需要一个producer group名字作为构造方法的参数,这里为concurrent_producer
        DefaultMQProducer producer = new DefaultMQProducer("concurrent_producer");
 
        //设置NameServer地址,此处应改为实际NameServer地址,多个地址之间用;分隔
        //NameServer的地址必须有,但是也可以通过环境变量的方式设置,不一定非得写死在代码里
        producer.setNamesrvAddr("10.1.54.121:9876;10.1.54.122:9876");
 
        //调用start()方法启动一个producer实例
        producer.start();
 
        //发送10条消息到Topic为TopicTest,tag为TagA,消息内容为“Hello RocketMQ”拼接上i的值
        for (int i = 0; i < 10; i++) {
            try {
                Message msg = new Message("TopicTestConcurrent",// topic
                        "TagA",// tag
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)// body
                );
 
                //调用producer的send()方法发送消息
                //这里调用的是同步的方式,所以会有返回结果,同时默认发送的也是普通消息
                SendResult sendResult = producer.send(msg);
 
                //打印返回结果,可以看到消息发送的状态以及一些相关信息
                System.out.println(sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
 
        //发送完消息之后,调用shutdown()方法关闭producer
        producer.shutdown();
    }
}

消费者:

public class Consumer {
 
    public static void main(String[] args) throws InterruptedException, MQClientException {
 
        //声明并初始化一个consumer
        //需要一个consumer group名字作为构造方法的参数,这里为concurrent_consumer
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("concurrent_consumer");
 
        //同样也要设置NameServer地址
        consumer.setNamesrvAddr("10.1.54.121:9876;10.1.54.122:9876");
 
        //这里设置的是一个consumer的消费策略
        //CONSUME_FROM_LAST_OFFSET 默认策略,从该队列最尾开始消费,即跳过历史消息
        //CONSUME_FROM_FIRST_OFFSET 从队列最开始开始消费,即历史消息(还储存在broker的)全部消费一遍
        //CONSUME_FROM_TIMESTAMP 从某个时间点开始消费,和setConsumeTimestamp()配合使用,默认是半个小时以前
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
 
        //设置consumer所订阅的Topic和Tag,*代表全部的Tag
        consumer.subscribe("TopicTestConcurrent", "*");
 
        //设置一个Listener,主要进行消息的逻辑处理
        //注意这里使用的是MessageListenerConcurrently这个接口
        consumer.registerMessageListener(new MessageListenerConcurrently() {
 
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
 
                System.out.println(Thread.currentThread().getName() + " Receive New Messages: " + msgs);
 
                //返回消费状态
                //CONSUME_SUCCESS 消费成功
                //RECONSUME_LATER 消费失败,需要稍后重新消费
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
 
        //调用start()方法启动consumer
        consumer.start();
 
        System.out.println("Consumer Started.");
    }
}

2.2 有序消息

有序消息就是按照一定的先后顺序的消息类型。

举个例子来说,producer 依次发送 order id 为 1、2、3 的消息到 broker,consumer 接到的消息顺序也就是 1、2、3 ,而不会出现普通消息那样的 2、1、3 等情况。

那么有序消息是如何保证的呢?我们都知道消息首先由 producer 到 broker,再从 broker 到 consumer,分这两步走。那么要保证消息的有序,势必这两步都是要保证有序的,即要保证消息是按有序发送到 broker,broker 也是有序将消息投递给 consumer,两个条件必须同时满足,缺一不可。
进一步还可以将有序消息分成

  • 全局有序消息
  • 局部有序消息

之前我们讲过,topic 只是消息的逻辑分类,内部实现其实是由 queue 组成。当 producer 把消息发送到某个 topic 时,默认是会消息发送到具体的 queue 上。

2.2.1 全局有序

发送异步消息会丢失 异步消息传递工作流程_System_04

举个例子,producer 发送 order id 为 1、2、3、4 的四条消息到 topicA 上,假设 topicA 的 queue 数为 3 个(queue0、queue1、queue2),那么消息的分布可能就是这种情况,id 为 1 的在 queue0,id 为 2 的在 queue1,id 为 3 的在 queue2,id 为 4 的在 queue0。同样的,consumer 消费时也是按 queue 去消费,这时候就可能出现先消费 1、4,再消费 2、3,和我们的预期不符。那么我们如何实现 1、2、3、4 的消费顺序呢?道理其实很简单,只需要把订单 topic 的 queue 数改为 1,如此一来,只要 producer 按照 1、2、3、4 的顺序去发送消息,那么 consumer 自然也就按照 1、2、3、4 的顺序去消费,这就是全局有序消息。

由于一个 topic 只有一个 queue ,即使我们有多个 producer 实例和 consumer 实例也很难提高消息吞吐量。就好比过独木桥,大家只能一个挨着一个过去,效率低下。

那么有没有吞吐量和有序之间折中的方案呢?其实是有的,就是局部有序消息。

2.2.2 局部有序

发送异步消息会丢失 异步消息传递工作流程_消息发送_05

我们知道订单消息可以再细分为订单创建、订单付款、订单完成等消息,这些消息都有相同的 order id。同时,也只有按照订单创建、订单付款、订单完成的顺序去消费才符合业务逻辑。但是不同 order id 的消息是可以并行的,不会影响到业务。这时候就常见做法就是将 order id 进行处理,将 order id 相同的消息发送到 topicB 的同一个 queue,假设我们 topicB 有 2 个 queue,那么我们可以简单的对 id 取余,奇数的发往 queue0,偶数的发往 queue1,消费者按照 queue 去消费时,就能保证 queue0 里面的消息有序消费,queue1 里面的消息有序消费。

由于一个 topic 可以有多个 queue,所以在性能比全局有序高得多。假设 queue 数是 n,理论上性能就是全局有序的 n 倍,当然 consumer 也要跟着增加才行。在实际情况中,这种局部有序消息是会比全局有序消息用的更多。

2.2.3 示例代码

生产者:

public class Producer {
    public static void main(String[] args) throws UnsupportedEncodingException {
        try {
            // 声明并初始化一个producer
            // 需要一个producer group名字作为构造方法的参数,这里为ordered_producer
            DefaultMQProducer orderedProducer = new DefaultMQProducer("ordered_producer");
 
            // 设置NameServer地址,此处应改为实际NameServer地址,多个地址之间用;分隔
            //NameServer的地址必须有,但是也可以通过环境变量的方式设置,不一定非得写死在代码里
            orderedProducer.setNamesrvAddr("10.1.54.121:9876;10.1.54.122:9876");
 
            // 调用start()方法启动一个producer实例
            orderedProducer.start();
 
            // 自定义一个tag数组
            String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"};
 
            // 发送10条消息到Topic为TopicTestOrdered,tag为tags数组按顺序取值,
            // key值为“KEY”拼接上i的值,消息内容为“Hello RocketMQ”拼接上i的值
            for (int i = 0; i < 10; i++) {
 
                int orderId = i % 10;
                Message msg =
                        new Message("TopicTestOrdered", tags[i % tags.length], "KEY" + i,
                                ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
 
                SendResult sendResult = orderedProducer.send(msg, new MessageQueueSelector() {
 
                    // 选择发送消息的队列
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
 
                        // arg的值其实就是orderId
                        Integer id = (Integer) arg;
 
                        // mqs是队列集合,也就是topic所对应的所有队列
                        int index = id % mqs.size();
 
                        // 这里根据前面的id对队列集合大小求余来返回所对应的队列
                        return mqs.get(index);
                    }
                }, orderId);
 
                System.out.println(sendResult);
            }
 
            orderedProducer.shutdown();
        } catch (MQClientException e) {
            e.printStackTrace();
        } catch (RemotingException e) {
            e.printStackTrace();
        } catch (MQBrokerException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

至于是要实现全局有序,还是局部有序,在此示例代码中,就取决于 TopicTestOrdered 这个 Topic 的队列数了。

消费者:

public class Consumer {
 
    public static void main(String[] args) throws MQClientException {
 
        //声明并初始化一个consumer
        //需要一个consumer group名字作为构造方法的参数,这里为concurrent_consumer
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ordered_consumer");
 
        //同样也要设置NameServer地址
        consumer.setNamesrvAddr("10.1.54.121:9876;10.1.54.122:9876");
 
        //这里设置的是一个consumer的消费策略
        //CONSUME_FROM_LAST_OFFSET 默认策略,从该队列最尾开始消费,即跳过历史消息
        //CONSUME_FROM_FIRST_OFFSET 从队列最开始开始消费,即历史消息(还储存在broker的)全部消费一遍
        //CONSUME_FROM_TIMESTAMP 从某个时间点开始消费,和setConsumeTimestamp()配合使用,默认是半个小时以前
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
 
        //设置consumer所订阅的Topic和Tag
        consumer.subscribe("TopicTestOrdered", "TagA || TagC || TagD");
 
        //设置一个Listener,主要进行消息的逻辑处理
        //注意这里使用的是MessageListenerOrderly这个接口
        consumer.registerMessageListener(new MessageListenerOrderly() {
 
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
 
                System.out.println(Thread.currentThread().getName() + " Receive New Messages: " + msgs);
 
                //返回消费状态
                //SUCCESS 消费成功
                //SUSPEND_CURRENT_QUEUE_A_MOMENT 消费失败,暂停当前队列的消费
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });
 
        //调用start()方法启动consumer
        consumer.start();
 
        System.out.println("Consumer Started.");
    }
}

2.3 延时消息

延时消息,简单来说就是当 producer 将消息发送到 broker 后,会延时一定时间后才投递给 consumer 进行消费。

RcoketMQ的延时等级为:1s,5s,10s,30s,1m,2m,3m,4m,5m,6m,7m,8m,9m,10m,20m,30m,1h,2h。level=0,表示不延时。level=1,表示 1 级延时,对应延时 1s。level=2 表示 2 级延时,对应5s,以此类推。

这种消息一般适用于消息生产和消费之间有时间窗口要求的场景。比如说我们网购时,下单之后是有一个支付时间,超过这个时间未支付,系统就应该自动关闭该笔订单。那么在订单创建的时候就会就需要发送一条延时消息(延时15分钟)后投递给 consumer,consumer 接收消息后再对订单的支付状态进行判断是否关闭订单。

设置延时非常简单,只需要在Message设置对应的延时级别即可:

Message msg = new Message("TopicTest",// topic
                        "TagA",// tag
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)// body
                );
                // 这里设置需要延时的等级即可
                msg.setDelayTimeLevel(3);
                SendResult sendResult = producer.send(msg);

延迟等级对应的时间:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,这个可以通过修改broke.conf文件来进行
配置,配置项=messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,时间单位支持:s、m、h、d,分别表示秒、分、时、天;
不是特殊的业务场景,不建议去修改,因为源码中有些地方也用到了这些延迟等级,改了可能会影响RocketMQ的正常运行

第一步:Producer拿着topic-c去NameServer获取路由信息(能拿到对应broker的ip:port和目标queue)

第二步:Producer将消息的Topic手动替换为系统的延迟Topic(SCHEDULE_TOPIC_XXXX),根据延迟等级,放到对应的延迟队列中,并备份原来的Topic和queue

第三步:broker端接收到消息请求,将消息发送到系统队列SCHEDULE_TOPIC_XXXX中对应的延迟等级的queue中

第四步:broker会定时去扫描延迟队列中的数据,到了触发时间会将消息投递到对应的topic和queue中

2.4 事务消息

RocketMQ支持分布式最终一致性事务,我们先看发送事务消息的代码

TransactionMQProducer producer = new TransactionMQProducer("transaction-group");
producer.setNamesrvAddr("127.0.0.1:9876");
//设置事务监听器
producer.setTransactionListener(new TransactionListener() {

  					/**
             * 执行本地事务,如果执行成功返回COMMIT_MESSAGE
             * broker会将消息发送出去,
             * 本地实物执行失败的话,broker会将消息删除
             * @param message
             * @param o
             * @return
             */
  @Override
  public LocalTransactionState executeLocalTransaction(Message message, Object o) {
    System.out.println("------------执行本地事务-------------");
    System.out.println("message:"+new String(message.getBody()));
    System.out.println("messageId:"+message.getTransactionId());

    try {
      //执行本地事务代码
      System.out.println("try code exec");
    } catch (Exception e) {
      //回滚事务
      return LocalTransactionState.ROLLBACK_MESSAGE;
    }
    //提交事务
    return LocalTransactionState.ROLLBACK_MESSAGE;
  }

  				/**
             * broker长时间没收到确认信息
             * 会回调接口来查看本地事务的执行情况
             * @param messageExt
             * @return
             */
  @Override
  public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
    //broker长时间没收到本地事务返回的状态,会主动回调询问事务状态 
    System.out.println("--------------------Broker执行回调检查本地事务状态-----------------------");
    System.out.println("message:"+new String(messageExt.getBody()));
    System.out.println("messageId:"+new String(messageExt.getTransactionId()));
    //回滚信息
    //return LocalTransactionState.ROLLBACK_MESSAGE;
    //等一会
    //return LocalTransactionState.UNKNOW;
    //事务执行成功
    return LocalTransactionState.COMMIT_MESSAGE;

  }
});
//启动producer
producer.start();
//发送消息(半消息)
TransactionSendResult sendResult = producer.sendMessageInTransaction(new Message("transaction-topic", "测试!这是事务消息".getBytes()), null);

System.out.println(sendResult);
//这里可能有异步回调 所以这里睡15s
TimeUnit.SECONDS.sleep(15);
producer.shutdown();

这里很容易发现,这里发送消息使用的是sendMessageInTransaction(),这是专门用来发送事务消息的,producer还注册了一个事务监听器。我们说一下事务消息发送的逻辑,大家有个概念,后面笔者会写一篇深入讲解事务消息的文章。

第一步:设置监听器以后,调用事务消息发送的方法,并不会将消息投递到消息真正的topic中,和延迟消息一样,会发送到系统默认的半消息Topic(RMQ_SYS_TRANS_HALF_TOPIC)中。

第二步:半消息发送完以后,会回调到executeLocalTransaction()这个方法中,我们执行本地事务,

  • 本地事务成功:返回LocalTransactionState.COMMIT_MESSAGE,然后将消息从半消息队列中取出来,放到消息本身的Topic队列中。
  • 本地事务失败或异常:返回LocalTransactionState.ROLLBACK_MESSAGE,Broker收到该状态,会将消息删除掉

第三步: 如果一分钟内,Broker还未收到本地事务返回的状态,Broker开始发起询问请求,也就是回调到checkLocalTransaction(),根据方法中判断本地事务是否执行成功。

  • 事务失败:返回LocalTransactionState.ROLLBACK_MESSAGE,Broker收到该状态后,会将半消息删除掉
  • 不确定:返回LocalTransactionState.UNKNOW,Broker收到该状态后,Broker会默认6s询问一次,最多询问15次
  • 事务成功:返回LocalTransactionState.COMMIT_MESSAGE,Broker收到该状态会把消息从半消息队列移到消息本身的Topic的队列

3. 消息过滤

消息由Topic分组以后,还可以在Topic的基础上再分,假如订单服务,下单和退款都往一个Topic下发消息,Consumer监听了该Topic,收到消息以后,分不清哪条消息是下单的,哪条消息是退款的。当然我们可以在消息Body里面添加参数来标识消息是订单还是退款,这样我们在Consumer收到消息以后,需要去判断消息体的参数,才能知道具体消息该走哪套处理逻辑,RocketMQ对消息做了一个过滤的解决方案

3.1 Tag

我们先看一段代码

final DefaultMQProducer mqProducer = new DefaultMQProducer("test-group");
mqProducer.setNamesrvAddr("127.0.0.1:9876");
//启动producer
mqProducer.start();
//添加过滤条件
Message syncMessage = new Message("testMsg","tag-a", "sync: hello world".getBytes());
//同步发送
SendResult result = mqProducer.send(syncMessage);
System.out.println("同步发送成功:"+result);

这条消息会在Message的properties属性里面添加一个TAGS=tag-a

发送异步消息会丢失 异步消息传递工作流程_消息发送_06

这样消费组在监听的时候,也只需要加过滤条件就能取到哪些想要的消息。如以下Consumer代码

DefaultMQPushConsumer mqPushConsumer = new DefaultMQPushConsumer("oneGroup");
mqPushConsumer.setNamesrvAddr("127.0.0.1:9876");
//并发消费
mqPushConsumer.registerMessageListener(new MessageListenerConcurrently() {
  @Override
  public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
    for (MessageExt msg : msgs) {
      System.out.println(new String(msg.getBody()));
    }
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
  }
});
//监听所有消息使用符号*  多个条件使用|| eg: tag-a || tag-b
mqPushConsumer.subscribe("testMsg","tag-a");
mqPushConsumer.start();
TimeUnit.SECONDS.sleep(5);
mqPushConsumer.shutdown();

这样,消费组只会收到testMsg下所有带有tag-a标签的消息。笔者画一个图方便读者理解

发送异步消息会丢失 异步消息传递工作流程_发送消息_07

3.2 SQL过滤

RocketMQ除了支持Tag来过滤消息,还支持更复杂的过滤方式 ,不过这样过滤方式默认是未开启的,需要在broker.conf文件中添加该属性

enablePropertyFilter=true

先看Producer代码

final DefaultMQProducer mqProducer = new DefaultMQProducer("test-group");
mqProducer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
//发送50条消息,每条消息设置一个num属性,consumer可以根据这个属性来进行过滤
for (int i = 1; i <= 50; i++) {
  Message message = new Message("testMsg","key"+i,("batch message no:"+i).getBytes());
  message.putUserProperty("num",String.valueOf(i));
  producer.send(message);
}

Producer发向testMsg的Topic中发送50条消息,消息内容会把当前是第几条消息标识出来,并未每条消息添加了一个自定义属性num,num的值就是Consumer来过滤的条件值。

支持过滤的语法:

  1. 数字比较, 像 >>=<<=BETWEEN=;
  2. 字符比较, 像 =<>IN;
  3. IS NULL 或者 IS NOT NULL;
  4. 逻辑运算ANDORNOT;

Consumer端代码

DefaultMQPushConsumer mqPushConsumer = new DefaultMQPushConsumer("oneGroup");
mqPushConsumer.setNamesrvAddr("127.0.0.1:9876");
//过滤器
MessageSelector selector = MessageSelector.bySql("num > 16 and num < 30");
consumer.subscribe("testMsg",selector);
//注册消息监听器
consumer.registerMessageListener(new MessageListenerConcurrently() {
  @Override
  public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
    for (MessageExt msg : msgs) {
      System.out.println("customer received: " +new String(msg.getBody()));
    }
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
  }
});
consumer.start();
TimeUnit.SECONDS.sleep(10);

Consumer端监听了testMsg这个Topic,并添加了一个sql表达式的消息选择器,条件为num大于 16 小于 30,有了这个选择器,Consumer就只能收到num 大于16和小于30之间的消息。

4. 消息重投机制

只有同步发送才会进行重投机制,并且还要打开下面这个属性才行,默认失败重新投递2次

//打开失败重新投递
mqProducer.setRetryAnotherBrokerWhenNotStoreOK(true);

4.1 offset

消息发送以后都是存储在Message Queue,Message Queue是一个无限长的数组,offset就是它的下标,一条消息存到Message Queue中,该Message Queue的offset就要累加1,消费时就是通过offset来快速定位到具体的消息。

发送异步消息会丢失 异步消息传递工作流程_System_08

对应的控制界面上的数据

发送异步消息会丢失 异步消息传递工作流程_发送消息_09