1、消息的发送状态
FLUSH_DISK_TIMEOUT: 没有在规定时间内完成刷盘 (刷盘策略需要为SYNC_FLUSH 才会出这个错误)
FLUSH_SLAVE_TIMEOUT: 主从模式下,broker是SYNC_MASTER, 没有在规定时间内完成主从同步
SLAVE_NOT_AVAILABLE: 从模式下,broker是SYNC_MASTER, 但是没有找到被配置成Slave的Broker
SEND_OK: 发送成功,没有发生上面的三种问题
2、重试机制
2.1、生产者 (异步和SendOneWay下配置无效)
消息重投(保证数据的高可靠性),本身内部支持重试,默认次数是2,
producer.setRetryTimesWhenSendAsyncFailed(3);
2.2、消费者
消费重试只针对集群消费方式生效,重试间隔时间配置 ,默认每条消息最多重试 16 次。
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
2.3、测试
pom:
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
</dependencies>
public class RocketMQConsts {
//=========================Hello RocketMQ==========================
public static final String HELLO_TOPIC = "helloTopic";
public static final String HELLO_GROUP_PROVIDER = "helloGroupProvider";
public static final String HELLO_TAG = "helloTag";
public static final String HELLO_KEY = "helloKey";
public static final String HELLO_NAMESERVER = "192.168.1.38:9876;192.168.1.39:9876";
public static final String HELLO_GROUP_CUSTOMER = "helloGroupCustomer";
}
生产者
public class RocketMQProducer {
public static void main(String[] args) throws Exception {
//创建producer,设置NameServer地址, 关闭rocketmq 虚拟IP
DefaultMQProducer producer = new DefaultMQProducer(RocketMQConsts.HELLO_GROUP_PROVIDER);
producer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
producer.setRetryTimesWhenSendAsyncFailed(3);
producer.setVipChannelEnabled(false);
producer.start();
//发送消息
Message msg = new Message(RocketMQConsts.HELLO_TOPIC, RocketMQConsts.HELLO_TAG,
RocketMQConsts.HELLO_KEY, ("Hello RocketMQ ").getBytes());
final SendResult send = producer.send(msg);
System.out.println("发送消息" + send);
//关闭
producer.shutdown();
}
}
消费者:
public class RocketMQCustomer {
public static void main(String[] args) {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
//配置组名 IP
consumer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
consumer.setConsumerGroup(RocketMQConsts.HELLO_GROUP_CUSTOMER);
//最后偏移量读取消息
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
try {
//订阅主题,监听主题下哪些标签
consumer.subscribe(RocketMQConsts.HELLO_TOPIC, "*");
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
//获取重试次数
MessageExt messageExt = msgs.get(0);
int times = messageExt.getReconsumeTimes();
try {
//默认一条一条消息消费
Message msg = msgs.get(0);
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msgs.get(0).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);
if (keys.equals(RocketMQConsts.HELLO_KEY)) {
throw new Exception("失败重试");
}
//返回成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (Exception e) {
if (times > 2) {
System.out.println("写入数据库,发送短信给运维。。。。。。");
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
e.printStackTrace();
//返回稍后进行投递
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
});
//开始
consumer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
}
3、发送模式
3.1、SYNC:同步,重要通知邮件、报名短信通知、营销短信系统等。
3.2、ASYNC:异步,可以支持更高的并发,回调成功触发相对应的业务,比如注册成功后通知积分系统发放优惠券(异步发送时,不能关闭发送者)。异步发送:不会重试,发送总次数等于1
demo:
public class RocketMQProducer {
public static void main(String[] args) throws Exception {
//创建producer,设置NameServer地址, 关闭rocketmq 虚拟IP
DefaultMQProducer producer = new DefaultMQProducer(RocketMQConsts.HELLO_GROUP_PROVIDER);
producer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
producer.setRetryTimesWhenSendAsyncFailed(3);
producer.setVipChannelEnabled(false);
producer.start();
//发送消息
Message msg = new Message(RocketMQConsts.HELLO_TOPIC, RocketMQConsts.HELLO_TAG,
RocketMQConsts.HELLO_KEY, ("Hello RocketMQ ").getBytes());
//异步发送
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送结果:" + sendResult);
}
@Override
public void onException(Throwable e) {
e.printStackTrace();
}
});
//关闭
//producer.shutdown();
}
}
3.3、ONEWAY : 无需要等待响应,主要是日志收集,适用于某些耗时非常短,但对可靠性要求并不高的场景, 也就是LogServer, 只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答
//建立消息
Message msg = new Message(RocketMQConsts.HELLO_TOPIC, RocketMQConsts.HELLO_TAG,
RocketMQConsts.HELLO_KEY, ("Hello RocketMQ ").getBytes());
//发送Oneway
producer.sendOneway(msg);
4、延迟队列
Producer 将消息发送到消息队列 RocketMQ 服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到 Consumer 进行消费,该消息即定时消息,目前支持固定精度的消息,常用于订单。
#索引为1-18
1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
发送者
public class RocketMQProducer {
public static void main(String[] args) throws Exception {
//创建producer,设置NameServer地址, 关闭rocketmq 虚拟IP
DefaultMQProducer producer = new DefaultMQProducer(RocketMQConsts.HELLO_GROUP_PROVIDER);
producer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
producer.setRetryTimesWhenSendAsyncFailed(3);
producer.setVipChannelEnabled(false);
producer.start();
//发送消息
Message msg = new Message(RocketMQConsts.HELLO_TOPIC, RocketMQConsts.HELLO_TAG,
RocketMQConsts.HELLO_KEY, ("Hello RocketMQ ").getBytes());
//设置延迟队列,5秒后消费者消费消息
msg.setDelayTimeLevel(2);
//异步发送
System.out.println("send msg:" + msg);
producer.send(msg);
//关闭
producer.shutdown();
}
}
消费者:
public class RocketMQCustomer {
public static void main(String[] args) {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
//配置组名 IP
consumer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
consumer.setConsumerGroup(RocketMQConsts.HELLO_GROUP_CUSTOMER);
//最后偏移量读取消息
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
try {
//订阅主题,监听主题下哪些标签
consumer.subscribe(RocketMQConsts.HELLO_TOPIC, "*");
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
try {
//默认一条一条消息消费
Message msg = msgs.get(0);
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msgs.get(0).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 ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
//返回稍后进行投递
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
});
//开始
consumer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
}
5秒后收到消息:
5、MessageQueueSelector
MessageQueueSelector,投递到Topic下指定的queue
修改send方法,同步发送:
//给队列1发送消息
SendResult send = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Integer queueNum = (Integer) arg;
return mqs.get(queueNum);
}
}, 1);
异步发送(异步发送时,producer不要关闭):
//给队列1发送消息
producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Integer queueNum = (Integer) arg;
return mqs.get(queueNum);
}
}, 1, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(sendResult);
}
@Override
public void onException(Throwable e) {
//TODO 记录日志
e.printStackTrace();
}
});
6、顺序消息
6.1 什么是顺序消息:消息的生产和消费顺序一致
1)全局顺序:topic下面全部消息都要有序(少用)
性能要求不高,所有的消息严格按照 FIFO 原则进行消息发布和消费的场景,并行度成为消息系统的瓶颈, 吞吐量不够.
在证券处理中,以人民币兑换美元为例子,在价格相同的情况下,先出价者优先处理,则可以通过全局顺序的方式按照 FIFO 的方式进行发布和消费
2)局部顺序:只要保证一组消息被顺序消费即可(RocketMQ使用)
性能要求高
电商的订单创建,同一个订单相关的创建订单消息、订单支付消息、订单退款消息、订单物流消息、订单交易成功消息 都会按照先后顺序来发布和消费
(阿里巴巴集团内部电商系统均使用局部顺序消息,既保证业务的顺序,同时又能保证业务的高性能)
3) 顺序发布:对于指定的一个 Topic,客户端将按照一定的先后顺序发送消息
顺序消费:对于指定的一个 Topic,按照一定的先后顺序接收消息,即先发送的消息一定会先被客户端接收到。
4) 注意:
* 顺序消息暂不支持广播模式
* 顺序消息不支持异步发送方式,否则将无法严格保证顺序
6.2、生产者demo,有篮球,足球,羽毛球一堆消息,按FIFO的顺序把各个种类的球类分到不同的队列中。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer type;
private String name;
}
生产者:
public class RocketMQProducer {
public static void main(String[] args) throws Exception {
//创建producer,设置NameServer地址, 关闭rocketmq 虚拟IP
DefaultMQProducer producer = new DefaultMQProducer(RocketMQConsts.HELLO_GROUP_PROVIDER);
producer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
producer.setRetryTimesWhenSendAsyncFailed(3);
producer.setVipChannelEnabled(false);
producer.start();
List<Order> orderList = getInitalList();
for (int i = 0; i < orderList.size(); i++) {
//建立消息
Message msg = new Message(RocketMQConsts.HELLO_TOPIC, RocketMQConsts.HELLO_TAG,
RocketMQConsts.HELLO_KEY, orderList.get(i).toString().getBytes());
//给指定队列发送消息
SendResult send = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Integer tempType = (Integer) arg;
int index = tempType % mqs.size();
return mqs.get(index);
}
}, orderList.get(i).getType());
System.out.println(send.getMessageQueue() + ":" + orderList.get(i));
}
}
public static List<Order> getInitalList() {
List<Order> list = new ArrayList<>();
list.add(new Order(0, "篮球1"));
list.add(new Order(1, "足球1"));
list.add(new Order(2, "羽毛球1"));
list.add(new Order(0, "篮球2"));
list.add(new Order(1, "足球2"));
list.add(new Order(2, "羽毛球2"));
list.add(new Order(0, "篮球3"));
list.add(new Order(1, "足球3"));
list.add(new Order(2, "羽毛球3"));
return list;
}
}
生产者,篮球都在队列0,并且按照1、2、3的顺序发送
6.3、消费者按顺序消费
MessageListenerOrderly
Consumer会平均分配queue的数量, 并不是简单禁止并发处理,而是为每个Consumer Quene加个锁,消费每个消息前,需要获得这个消息所在的Queue的锁,这样同个时间,同个Queue的
消息不被并发消费,但是不同Queue的消息可以并发处理。
public class RocketMQCustomer {
public static void main(String[] args) {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
//配置组名 IP
consumer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
consumer.setConsumerGroup(RocketMQConsts.HELLO_GROUP_CUSTOMER);
//最后偏移量读取消息
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
try {
//订阅主题,监听主题下哪些标签
consumer.subscribe(RocketMQConsts.HELLO_TOPIC, "*");
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
Message msg = msgs.get(0);
try {
System.out.println(Thread.currentThread().getName() + "receive msg" + new String(msg.getBody(), "utf-8"));
return ConsumeOrderlyStatus.SUCCESS;
} catch (Exception e) {
e.printStackTrace();
//等会在消费
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
}
});
//开始
consumer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
}
启动4个消费端: