发送同步消息
发送过后会有一个返回值,也就是mq服务器接收到消息后返回的一个确认,这种方式非常安全,但是性能上并没有这么高
@Test
void simpleProducer() throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
//创建一个生产者,指定组名
DefaultMQProducer producer = new DefaultMQProducer("test-producer-group");
//连接namesrv
producer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);
//启动
producer.start();
//创建一个消息
Message message = new Message("testTopic", "hello world".getBytes());
//发送消息
SendResult send = producer.send(message);
System.out.println(send.getSendStatus());
//关闭生产者
producer.shutdown();
}
@Test
void simpleConsumer() throws Exception{
//创建一个消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("test-consumer-group");
//连接
consumer.setNamesrvAddr(MqConstant.NAME_SRV_ADDR);
//订阅一个主题 *表示所有的消息,后期会有消息过滤
consumer.subscribe("testTopic", "*");
consumer.registerMessageListener((MessageListenerConcurrently) (list, consumeConcurrentlyContext) -> {
//业务处理
System.out.println("我是消费者");
System.out.println("消费:"+new String(list.get(0).getBody()));
System.out.println("消费上下文"+consumeConcurrentlyContext);
//返回值
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
//启动
consumer.start();
System.in.read();
}
发送异步消息
使用场景:对响应时间敏感的服务,即发送端不能容忍长时间地等待Broker的响应。发送完以后会有一个异步消息通知。
@Test
@SneakyThrows
public void asyncProducer(){
DefaultMQProducer producer = new DefaultMQProducer("async-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
Message message = new Message("asyncTopic", "我是一个异步消息".getBytes());
producer.send(message,new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送成功");
}
@Override
public void onException(Throwable throwable) {
System.out.println("发送失败");
}
});
System.out.println("我先执行");
System.in.read();
}
@Test
@SneakyThrows
public void asyncConsumer(){
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("async-producer-group");
consumer.setNamesrvAddr("127.0.0.1:9876");
//订阅主题
consumer.subscribe("asyncTopic", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
// 这里执行消费的代码 默认是多线程消费
System.out.println(Thread.currentThread().getName() + "----" + list);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.in.read();
}
发送单向消息
使用场景:不关心发送结果的场景,这种方式吞吐量很大,但是存在消息丢失的风险,例如日志信息的发送。
@Test
public void OnewayProducer() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("oneway-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
Message message = new Message("onewayTopic", "这是一个单向消息".getBytes());
producer.sendOneway(message);
producer.shutdown();
System.in.read();
}
发送延时消息
比如下订单业务,提交了一个订单就可以发送一个延时消息,30min后去检查这个订单的状态,如果还是未付款就取消订单释放库存。
@Test
@SneakyThrows
public void relayProducer() {
DefaultMQProducer producer = new DefaultMQProducer("relay-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
Message message = new Message("relayTopic", "这是一个延迟消息".getBytes());
// messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
message.setDelayTimeLevel(3);
producer.send(message);
System.out.println("发送时间" + new Date());
producer.shutdown();
System.in.read();
}
发送顺序消息
RocketMQ可以严格的保证消息有序,可以分为:分区有序或者全局有序。在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。
构建一个商品信息类模拟发送顺序消息
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private int id;
private int orderNumber;
private int price;
private String description;
}
@Test
public void OrderlyProducer() throws MQClientException, IOException {
DefaultMQProducer producer = new DefaultMQProducer("orderly-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
List<Order> orders = Arrays.asList(
new Order(1, 111, 50, "下订单"),
new Order(2, 111, 50, "物流"),
new Order(3, 111, 50, "签收"),
new Order(4, 112, 100, "下订单"),
new Order(5, 112, 100, "物流"),
new Order(6, 112, 100, "拒收")
);
orders.forEach(order -> {
Message message = new Message("orderlyTopic", order.toString().getBytes());
try {
producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
//当前主题有多少个队列
int queueNumber = list.size();
//得到一个队列
Integer index = o.hashCode() % queueNumber;
// 返回选择的这个队列即可 ,那么相同的订单号 就会被放在相同的队列里 实现FIFO了
return list.get(index);
}
}, order.getOrderNumber());
} catch (Exception e) {
System.out.println("发送异常");
}
});
producer.shutdown();
System.in.read();
}
消费者采用单线程消费
@Test
@SneakyThrows
public void orderlyConsumer() {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("orderly-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("orderlyTopic", "*");
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
MessageExt messageExt = list.get(0);
System.out.println(new String(messageExt.getBody()));
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.in.read();
}
成功保证了组内有序
发送批量消息
@Test
public void testBatchProducer() throws Exception {
// 创建默认的生产者
DefaultMQProducer producer = new DefaultMQProducer("test-group");
// 设置nameServer地址
producer.setNamesrvAddr("localhost:9876");
// 启动实例
producer.start();
List<Message> msgs = Arrays.asList(
new Message("TopicTest", "我是一组消息的A消息".getBytes()),
new Message("TopicTest", "我是一组消息的B消息".getBytes()),
new Message("TopicTest", "我是一组消息的C消息".getBytes())
);
SendResult send = producer.send(msgs);
System.out.println(send);
// 关闭实例
producer.shutdown();
}
发送带标签的消息
Rocketmq提供消息过滤功能,通过tag或者key进行区分,比如带有tagA标签的被A消费,带有tagB标签的被B消费,还有在事务监听的类里面,只要是事务消息都要走同一个监听,我们也需要通过过滤才区别对待
@Test
public void testTagProducer() throws Exception {
// 创建默认的生产者
DefaultMQProducer producer = new DefaultMQProducer("test-group");
// 设置nameServer地址
producer.setNamesrvAddr("localhost:9876");
// 启动实例
producer.start();
Message msg = new Message("TopicTest","tagA", "我是一个带标记的消息".getBytes());
SendResult send = producer.send(msg);
System.out.println(send);
// 关闭实例
producer.shutdown();
}
@Test
public void testTagConsumer() throws Exception {
// 创建默认消费者组
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer-group");
// 设置nameServer地址
consumer.setNamesrvAddr("localhost:9876");
// 订阅一个主题来消费 表达式,默认是*,支持"tagA || tagB || tagC" 这样或者的写法 只要是符合任何一个标签都可以消费
consumer.subscribe("TopicTest", "tagA || tagB || tagC");
// 注册一个消费监听 MessageListenerConcurrently是并发消费
// 默认是20个线程一起消费,可以参看 consumer.setConsumeThreadMax()
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
// 这里执行消费的代码 默认是多线程消费
System.out.println(Thread.currentThread().getName() + "----" + new String(msgs.get(0).getBody()));
System.out.println(msgs.get(0).getTags());
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.in.read();
}
消息的重试机制
@Test
public void testConsumer() throws Exception {
// 创建默认消费者组
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer-group");
// 设置nameServer地址
consumer.setNamesrvAddr("localhost:9876");
// 订阅一个主题来消费 *表示没有过滤参数 表示这个主题的任何消息
consumer.subscribe("TopicTest", "*");
// 注册一个消费监听
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
try {
// 这里执行消费的代码
System.out.println(Thread.currentThread().getName() + "----" + msgs);
// 这里制造一个错误
int i = 10 / 0;
} catch (Exception e) {
// 出现问题 判断重试的次数
MessageExt messageExt = msgs.get(0);
// 获取重试的次数 失败一次消息中的失败次数会累加一次
int reconsumeTimes = messageExt.getReconsumeTimes();
if (reconsumeTimes >= 3) {
// 则把消息确认了,可以将这条消息记录到日志或者数据库 通知人工处理
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} else {
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.in.read();
}
死信队列
当一条消息初次消费失败,RocketMQ会自动进行消息重试,达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息。此时,该消息不会立刻被丢弃,而是将其发送到该消费者对应的特殊队列中,这类消息称为死信消息(Dead-Letter Message),存储死信消息的特殊队列称为死信队列(Dead-Letter Queue),死信队列是死信Topic下分区数唯一的单独队列。对应的ConsumerGroup的死信Topic名称为%DLQ%ConsumerGroupName
@Test
public void testDeadMq() throws Exception{
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("dead-group");
consumer.setNamesrvAddr("localhost:9876");
// 消费重试到达阈值以后,消息不会被投递给消费者了,而是进入了死信队列
// 队列名称 默认是 %DLQ% + 消费者组名
consumer.subscribe("%DLQ%dead-group", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
System.out.println(msgs);
// 处理消息 签收了
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.in.read();
}