消费模式
pull模式
消费者主动从消息中间件中拉取消息。
pull模式每次接收消息都需要拉取一下队列的信息,由于是拉取的,实时性较差,不能及时有效获取最新的信息,能有效降低内存消耗。
push模式(底层也是pull模式)
消息中间件主动将消息推送给消费者。 客户端需要做好流控。
push模式接收消息是最有效的一种消息处理方式。当我们使用该模式时,我们的消费端只要启动后,就相当于使用了订阅模式,只要生产不断推送信息,消费端就会持续接收信息
如何选择
一般场景下,上游消息生产量小、或者均速、选择push。
秒杀场景下,可以选择pull模式。
消息发送模式
普通消息
发送同步消息
消息发送后会获得一个返回值,mq服务器接收到消息后返回的一个确认,非常安全,但是性能低,而且在 mq集群中,所有的从机都复制了消息以后才会返回,针对重要的消息可以选择这个模式
发送异步消息
异步发送是指发送方发出一条消息后,不等服务端返回响应,接着发送下一条消息的通讯方式.发送异步消息需要实现异步接口 sendCallback。 对实现比较敏感
使用
public void asyncProducer() throws MQClientException, RemotingException, InterruptedException, IOException {
DefaultMQProducer defaultMQProducer = new DefaultMQProducer("async-producer-group");
defaultMQProducer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);
defaultMQProducer.start();
Message msg = new Message("asyncTopic", "一个异步消息的发送".getBytes(StandardCharsets.UTF_8));
for (int i = 0; i < 100; i++) {
defaultMQProducer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送成功");
}
@Override
public void onException(Throwable throwable) {
System.out.println("发送失败" + throwable.getMessage());
}
});
}
System.out.println("主线程执行");
// 收到异步消息
System.in.read();
}
发送成功的回调函数
发送单项消息
不关心发送结果的场景,吞吐量很大,但是存在消息丢失的风险。 例如 日志信息的发送。
发送方只负责发送消息,不等待服务端返回响应且没有回调函数触发,即只发送请求不等待应答。此方式发送消息的过程耗时非常短,一般在微秒级别。
三种对比
延迟消息
在分布式定时调度触发、任务超时处理等场景,需要实现精准、可靠的延时事件触发。使用 RocketMQ 的延时消息可以简化定时调度任务的开发逻辑,实现高性能、可扩展、高可靠的定时触发能力。
代码 demo。
/**
* 推迟生产
*/
@Test
public void putOffProducer() throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
DefaultMQProducer defaultMQProducer = new DefaultMQProducer("delay-producer-group");
defaultMQProducer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);
defaultMQProducer.start();
Message message = new Message("orderMsTopic","延迟消息".getBytes(StandardCharsets.UTF_8));
// 10s 后收到 在配置文件中可以去指定 等级对应的时间
message.setDelayTimeLevel(3);
defaultMQProducer.send(message);
System.out.println("发送时间" + new Date() );
defaultMQProducer.shutdown();
}
/**
* 延迟消费者
* 第一次测试 ---> 比较慢 可能在加载一些东西
* 发送时间 Mon Jun 12 15:27:12 CST 2023
* 收到时间 Mon Jun 12 15:27:49 CST 2023
* =====================================
* 第二次就快了
* =====================================
* 发送时间 Mon Jun 12 15:30:20 CST 2023
* 收到时间 Mon Jun 12 15:30:30 CST 2023
*/
@Test
public void msConsumer() throws MQClientException, IOException {
DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer("delay-consumer-group");
defaultMQPushConsumer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);
defaultMQPushConsumer.subscribe("orderMsTopic","*");
defaultMQPushConsumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
System.out.println("收到消息了" + new Date());
byte[] body = list.get(0).getBody();
String s = new String(body);
System.out.println(s);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
defaultMQPushConsumer.start();
System.in.read();
}
批量消息
这里调用非常简单,将消息打包成
Collection<Message> msgs传入方法中即可,需要注意的是批量消息的大小不能超过 1MiB(否则需要自行分割),其次同一批 batch 中 topic 必须相同。
发送到的是同一个队列,并没有通过轮询方式发送。
@Test
public void batchProducer() throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
DefaultMQProducer defaultMQProducer = new DefaultMQProducer("batch-producer-group");
defaultMQProducer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);
defaultMQProducer.start();
ArrayList<Message> list = new ArrayList<>();
String topic = "batchTest" ;
for (char a = 'a' ; a < 'c' ; a ++) {
list.add(new Message(topic,("我是一组消息中的" + a + "消息").getBytes(StandardCharsets.UTF_8)));
}
SendResult send = defaultMQProducer.send(list);
System.out.println(send.getSendStatus());
defaultMQProducer.shutdown();
}
@Test
public void msConsumer() throws MQClientException, IOException {
DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer("batch-consumer-group");
defaultMQPushConsumer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);
defaultMQPushConsumer.subscribe("batchTest","*");
defaultMQPushConsumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
System.out.println("收到消息了" + new Date());
System.out.println("收到消息的数组大小" + list.size());
byte[] body = list.get(0).getBody();
String s = new String(body);
System.out.println(s);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
defaultMQPushConsumer.start();
System.in.read();
}
顺序消息的发送
先看一下发送一个普通消息的情况图。在四个队列中,负载均衡的发。
并发模式的消息接收
......
* 我是第77个消息
* 我是第43个消息
* 我是第59个消息
* 我是第79个消息
* 我是第83个消息
......
消息 sharding key 分区发送
private List<Msg> list = Arrays.asList(
new Msg(1,1,"下单"),
new Msg(2,2,"下单"),
new Msg(1,1,"短信"),
new Msg(2,2,"短信"),
new Msg(1,1,"物流"),
new Msg(2,2,"物流")
);
@Test
public void orderProducer() throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
DefaultMQProducer producer = new DefaultMQProducer("order-producer-group");
producer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);
producer.start();
// 发送顺序 消息 , 发送时确保有序,并且要发到同一个队列里面去。
list.forEach(msg -> {
Message orderTopic = new Message("orderTopic", msg.toString().getBytes(StandardCharsets.UTF_8));
try {
producer.send(orderTopic, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
//int idx = o.hashCode() % list.size();
return list.get(idx);
}
},msg.getOrderId());
} catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) {
e.printStackTrace();
}
});
producer.shutdown();
System.out.println("发送完毕");
}
并发顺序模式接收
Msg(id=1, orderId=1, desc=下单)
Msg(id=2, orderId=2, desc=下单)
Msg(id=1, orderId=1, desc=短信)
Msg(id=2, orderId=2, desc=短信)
Msg(id=1, orderId=1, desc=物流)
Msg(id=2, orderId=2, desc=物流)
demo 代码
@Test
public void orderConsumer() throws MQClientException, IOException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order-consumer-group");
consumer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);
consumer.subscribe("orderTopic","*") ;
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
System.out.println("消息上下文" + consumeOrderlyContext);
System.out.println("当前消息" + list.get(0));
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.in.read();
}