生产者常见核心配置:
从Producer投递消息到Broker,在这个过程中由于网络问题,不能将消息投递到Broker,消息投递失败。或者消息消费时,出现异常,导致消息消费失败。所以需要生产和消费消息重试。
生产者Producer重试
1. 消息重投(保证数据的高可靠性),本身内部支持重试,默认次数是2
2. 如果网络情况比较差,或者跨集群则建议改多几次
消费端重试
1. 原因:消息处理异常、broker端到consumer端各种问题,如果网络原因闪断,消息处理失败,ACK返回失败等等问题。
2. 注意:
- 重试问题隔时间配置,默认每条消息最多重试16次
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
- 超过重试次数人工补偿
- 消费端去重
- 一条消息无论重试多少次、这些重试消息的MessageID,key不会改变
- 消费重试只针对集群消费方式生效;广播方式不提供失败重试特效,即消费失败后,失败消息不再重试,继续消费新的消息
官方文档实例:https://rocketmq.apache.org/docs/simple-example/
producer.send(message, new SendCallback(){
onSuccess(){}
onException(){}
})
注意:官方例子:如果异步发送消息,调用producer.shutdown()后会失败
异步发送:不会重试,发送总次数等于1
SYNC:同步
应用场景:重要邮件通知、报名短信通知、营销短息系统等
ASYNC:异步
应用场景:对RT时间敏感,可以支持更高的并发,回调成功触发相对应的业务,比如注册成功后通知积分系统发放优惠券
ONEWAY:无需要等待响应
使用场景:主要是日志收集,适用于某些耗时非常短,但对可靠性要求并不高的场景,也就是LogServer,只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答
producer.getProducer().sendOneway(message);
汇总对比:
发送方式 | 发送TPS | 发送结果返回 | 可靠性 |
---|---|---|---|
同步发送 | 快 | 有 | 不丢失 |
异步发送 | 快 | 有 | 不丢失 |
单向发送 | 最快 | 无 | 可能丢失 |
Producer将消息发送到消息队列RocketMQ服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到Consumer进行消费,该消息即定时消息,目前支持固定精度的消息
代码:rocketmq-store > MessgeStoreConfig.java 属性 messageDelayLevel
"1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
简介:生产消息使用MessageQueueSelector投递到Topic下指定的queue,一个topic下默认有4个queue队列。
应用场景:顺序消费消息,分摊负载
支持同步和异步发送
同步方法 : public SendResult send(Message msg, MessageQueueSelector selector, Object arg)
异步方法 : public void send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback)
选择的queue数量必须小于配置的,否则会出错
什么是顺序消息:消息的生产和消费顺序一致
全局顺序:topic下面全部消息顺序一致(少用)
局部顺序:只要保证一组消息被顺序消费即可(RocketMQ使用)
顺序发布:对于指定的一个Topic,客户端将按照一定的先后顺序发送消息
顺序消费:对于指定的一个Topic,按照一定的先后顺序接收消息,即先发送的消息一定会先被客户端接收到
注意:
生产端保证发送消息有序,且发送到同一个队列的同个queue里面,同一条queue里面,RocketMQ的确是能保证FIFO的
例子:订单的顺序流程是:创建、付款、物流、完成,订单号相同的消息会被先后发送到一个队列中,
根据MessageQueueSelector里面自定义策略,根据同个业务id放置到同个queue里面,如订单号取模运算再放到selector中,同一个模的值都会投递到同一条queue
public MessageQueue select(List mqs, Message msg, Object
arg) {
//如果订单号是字符串,则进行hash,得到一个hash值
Long id = (Long) arg;
long index = id % mqs.size();
return mqs.get((int)index);
}
消费端要在保证消费同个topic里的同个队列,不应该用MessageListenerConcurrently, 应该使用MessageListenerOrderly,自带单线程消费消息,不能再Consumer端再使用多线程去消费,消费端分配到queue数量是固定的,集群消费会锁住当前正在消费的队列集合的消息,所以会保证顺序消费
package com.pj.boot.bean;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
public class ProductOrder implements Serializable {
private long orderId;
private String type;
public ProductOrder(){}
public ProductOrder(long orderId, String type) {
this.orderId = orderId;
this.type = type;
}
public static List<ProductOrder> getOrderList() {
List<ProductOrder> list = new ArrayList<>();
list.add(new ProductOrder(111L, "创建订单"));
list.add(new ProductOrder(222L, "创建订单"));
list.add(new ProductOrder(111L, "支付订单"));
list.add(new ProductOrder(222L, "支付订单"));
list.add(new ProductOrder(111L, "完成订单"));
list.add(new ProductOrder(333L, "创建订单"));
list.add(new ProductOrder(222L, "完成订单"));
list.add(new ProductOrder(333L, "支付订单"));
list.add(new ProductOrder(333L, "完成订单"));
return list;
}
@Override
public String toString() {
return "ProductOrder{" +
"orderId=" + orderId +
", type='" + type + '\'' +
'}';
}
}
List<ProductOrder> list = ProductOrder.getOrderList();
for (int i = 0; i < list.size(); i++) {
ProductOrder order = list.get(i);
// 生产时建议再加一个key值
Message message = new Message(JmsConfig.ORDERLY_TOPIC,"taga",order.getOrderId()+"", order.toString().getBytes());
SendResult sendResult = payProducer.getProducer().send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
// 使同一个订单发送到同一个queue里头去
Long id = (Long) o;
long index = id % list.size();
return list.get((int) index);
}
}, order.getOrderId());
System.out.println(sendResult);
}
package com.pj.boot.jms;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.util.List;
@Component
public class PayOrderlyConsumer {
private DefaultMQPushConsumer consumer;
private String consumerGroup = "pay_orderly_consumer_group";
public PayOrderlyConsumer() throws MQClientException {
consumer = new DefaultMQPushConsumer(consumerGroup);
consumer.setNamesrvAddr(JmsConfig.NAME_SERVER);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
consumer.subscribe(JmsConfig.ORDERLY_TOPIC, "*");
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
MessageExt msg = list.get(0);
try {
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msg.getBody()));
String topic = msg.getTopic();
String body = new String(msg.getBody(), "utf-8");
String tags = msg.getTags();
String keys = msg.getKeys();
System.out.println("topic=" + topic + ", tags=" + tags + ", keys=" + keys + ", msg=" + body);
return ConsumeOrderlyStatus.SUCCESS;
} catch (Exception e) {
e.printStackTrace();
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
}
});
consumer.start();
System.out.println("consumer start ...");
}
}