RocketMQ学习笔记(二)

消息发送方式

同步消息

消息中间件接收到生产者发送消息的请求之后,先将数据存储到内存中,会持久化到磁盘中。当持久化完成之后,消息中间件返回消息给消息生产者。发送同步消息可以保证,消息是存储到了消息中间件之后才执行后续的代码。

异步消息

发送完异步消息,接着执行后续的代码逻辑(业务方法都提交了),消息中间件可能存储不成功,通知我们失败,有可能出现数据不一致的功能,进行业务反向补偿。

对时间响应比较敏感的业务场景,可以使用异步消息。

一次性消费

只管发,并不关心是否存储到消息中间件,性能比较高

RocketMQ消息如何保证不出现丢失情况

RocketMQ的内部对数据进行持久化

  • 同步刷盘(SYNC_FLUSH):接收到用户的消息请求,把数据持久化到磁盘才认为是结束了
  • 异步刷盘(默认 ASYNC_FLUSH):接收到用户的消息请求,将数据写入操作系统的PageCache缓存中,此时就通知消息生产者已经存储成功了,操作系统会有线程将数据持久化到磁盘中。

如果在写入PageCache成功,返回消息给生产者,这个时候系统崩溃了,就会出现丢数据的情况

如果要保证消息不出现丢失的情况,需要在配置文件中修改刷盘方式为同步刷盘

消息消费模式

集群模式

消息只能给集群中的某一个服务器所消费到,不会出现某个消息给两个消费者消费到。

广播模式

设置

consumer.setMessageModel(MessageModel.BROADCASTING)

广播模式下,消息会发送给集群中的所有机器,所有机器都能消费消息

MessageQueue的存储结构

RocketMQ默认读写队列是4个,队列会自动分片处理,源码内部维护index索引,index++ % 队列长度,等到存储数据的索引位置。

RocketMQ内部有4个队列,主要是为了降低并发度,提供Broker消息处理能力,那为什么是4个而不是8个、16个呢?大部分的服务器都是2核或者4核的,内部在工作的时候实际工作的是2个核心线程或者4个核心线程,队列多了意义并不大,如果队列个数大于核心数,肯定队列是空闲的。

延时消息

现在RocketMQ并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18

1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

生产者

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;

import java.util.Date;

public class SimpleProducer {
    public static void main(String[] args) throws Exception{
        //定义消费的生产者对象
        DefaultMQProducer producer = new DefaultMQProducer("helloProducerGroup");
        //定义nameServer地址
        producer.setNamesrvAddr("127.0.0.1:9876");
        //自动连接(从nameServer拉取broker地址,并且建立连接)
        producer.start();
        //定义消息发送的目的地Topic
        String topic = "helloTopic";
        Message message = new Message(topic,("发送延时消息,发送时间:"+new Date()).getBytes());
        //设置消息延时级别
        message.setDelayTimeLevel(3);
        producer.sendOneway(message);
        //关闭连接
        producer.shutdown();
    }
}

消费者

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.Date;

public class SimpleConsumer {
    public static void main(String[] args) throws Exception {
        //定义消息的消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("helloConsumerGroup");
        //定义nameServer地址
        consumer.setNamesrvAddr("127.0.0.1:9876");
        //定义消费的主题
        String topic = "helloTopic";
        //监听该主题消息
        consumer.subscribe(topic,"*");
        //设置消息监听器,服务器把消息推送给我们,消费消息
        consumer.setMessageListener((MessageListenerConcurrently) (list, consumeConcurrentlyContext) -> {
            for(MessageExt messageExt:list){
                System.out.println("处理的线程:"+Thread.currentThread()+",消息内容:"+new String(messageExt.getBody())+",消费时间:"+new Date());
            }
            //告诉消息中间件,消息处理的情况
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });
        consumer.start();
    }
}

消息过滤

Tag方式

生产者

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;

public class TagFilterProducer {
    public static void main(String[] args) throws Exception{
        //定义消费的生产者对象
        DefaultMQProducer producer = new DefaultMQProducer("tagFilterProducerGroup");
        //定义nameServer地址
        producer.setNamesrvAddr("127.0.0.1:9876");
        //自动连接(从nameServer拉取broker地址,并且建立连接)
        producer.start();
        //定义消息发送的目的地Topic
        String topic = "tagFilterTopic";
        Message msg1 = new Message(topic,"Tag1",("消息1").getBytes());
        Message msg2 = new Message(topic,"Tag2",("消息2").getBytes());
        Message msg3 = new Message(topic,"Tag3",("消息3").getBytes());
        producer.sendOneway(msg1);
        producer.sendOneway(msg2);
        producer.sendOneway(msg3);
        //关闭连接
        producer.shutdown();
    }
}

消费者

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

public class TagFilterConsumer {
    public static void main(String[] args) throws Exception {
        //定义消息的消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("tagFilterConsumerGroup");
        //定义nameServer地址
        consumer.setNamesrvAddr("127.0.0.1:9876");
        //定义消费的主题
        String topic = "tagFilterTopic";
        //监听该主题消息
        consumer.subscribe(topic,"TagA || TagC");
        //设置消息监听器,服务器把消息推送给我们,消费消息
        consumer.setMessageListener((MessageListenerConcurrently) (list, consumeConcurrentlyContext) -> {
            for(MessageExt messageExt:list){
                System.out.println("处理的线程:"+Thread.currentThread()+",消息内容:"+new String(messageExt.getBody()));
            }
            //告诉消息中间件,消息处理的情况
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });
        consumer.start();
    }
}

SQL92方式

默认是关闭的,需要启动

broker.conf

...
enablePropertyFilter = true

生产者

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;

public class SqlFilterProducer {
    public static void main(String[] args) throws Exception{
        //定义消费的生产者对象
        DefaultMQProducer producer = new DefaultMQProducer("sqlFilterProducerGroup");
        //定义nameServer地址
        producer.setNamesrvAddr("127.0.0.1:9876");
        //自动连接(从nameServer拉取broker地址,并且建立连接)
        producer.start();
        //定义消息发送的目的地Topic
        String topic = "sqlFilterTopic";
        Message msg1 = new Message(topic,("User1").getBytes());
        msg1.putUserProperty("age","18");
        msg1.putUserProperty("weight","45");
        Message msg2 = new Message(topic,("User2").getBytes());
        msg2.putUserProperty("age","35");
        msg2.putUserProperty("weight","70");
        Message msg3 = new Message(topic,("User3").getBytes());
        msg3.putUserProperty("age","55");
        msg3.putUserProperty("weight","65");
        producer.sendOneway(msg1);
        producer.sendOneway(msg2);
        producer.sendOneway(msg3);
        //关闭连接
        producer.shutdown();
    }
}

消费者

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

public class SqlFilterConsumer {
    public static void main(String[] args) throws Exception {
        //定义消息的消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("sqlFilterConsumerGroup");
        //定义nameServer地址
        consumer.setNamesrvAddr("127.0.0.1:9876");
        //定义消费的主题
        String topic = "sqlFilterTopic";
        //监听该主题消息
        consumer.subscribe(topic, MessageSelector.bySql(" age > 18 and weight < 60"));
        //设置消息监听器,服务器把消息推送给我们,消费消息
        consumer.setMessageListener((MessageListenerConcurrently) (list, consumeConcurrentlyContext) -> {
            for(MessageExt messageExt:list){
                System.out.println("处理的线程:"+Thread.currentThread()+",消息内容:"+new String(messageExt.getBody()));
            }
            //告诉消息中间件,消息处理的情况
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });
        consumer.start();
    }
}

顺序消息

顺序消费:发送的顺序和消费的顺序是一致的。

  • 如果是单个队列,可以采取单线程的方式消费消息,就可以保证顺序消费的功能
  • 但是消费者在集群的情况下,可能也会出现多线程的消费情况。由于消息中间件共有4个队列,发送的时候会轮询的在队列中储存的消息,不同队列被分片到不同的机器上,这时候没办法保证顺序消费的功能
  • 把需要顺序消费的消息,放入到同一个队列中,一个队列只能和一个消费者的机器绑定,只需要消费是单线程的消费消息就可以保证顺序消费了

消息选择器

MessageQueueSelector selector = new MessageQueueSelector(){
    public MessageQueue select(List<MessageQueue> mqs, Message msg,Object arg){
        //业务处理
        int index = 1;
        return mqs.get(index);
    }
}


producer.sendOneway(msg,selector,id);

单线程消费

consumer.setMessageListener((MessageListenerOrderly) (list, consumeOrderlyContext) -> {
    for(MessageExt messageExt:list){
        System.out.println("处理的线程:"+Thread.currentThread()+",消息内容:"+new String(messageExt.getBody()));
    }
    return ConsumeOrderlyStatus.SUCCESS;
});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值