消息发送方式
同步消息
消息中间件接收到生产者发送消息的请求之后,先将数据存储到内存中,会持久化到磁盘中。当持久化完成之后,消息中间件返回消息给消息生产者。发送同步消息可以保证,消息是存储到了消息中间件之后才执行后续的代码。
异步消息
发送完异步消息,接着执行后续的代码逻辑(业务方法都提交了),消息中间件可能存储不成功,通知我们失败,有可能出现数据不一致的功能,进行业务反向补偿。
对时间响应比较敏感的业务场景,可以使用异步消息。
一次性消费
只管发,并不关心是否存储到消息中间件,性能比较高
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;
});