1.发送端确认机制
- 为什么需要发送端确认机制?
消息发送后,发送端不知道RabbitMQ是否真的收到了消息
- 什么是发送端确认机制?
消息发送后,若RabbitMQ收到消息,则会给发送端一个应答,
发送端接收应答,确认消息发送成功
- 三种确认机制
-
1.单条同步确认
-
1.1 配置channel,开启确认模式
channel.confirmSelect();
-
1.2 每发送一条消息后,调用方法waitForConfirms()方法,等待确认,返回true则代表RabbitMQ收到了消息
channel.waitForConfirms();
-
-
2.多条同步确认
-
2.1 配置channel,开启确认模式
channel.confirmSelect();
-
2.2 发送多条消息后,调用waitForConfirms()方法,等待确认,返回true则代表RabbitMQ收到了发送的全部消息,但返回false不代表全部失败,有可能是部分消息发送失败,无法知道哪些消息发送失败了!
-
-
3.异步确认
-
3.1 配置channel,开启确认模式
channel.confirmSelect();
-
3.2 在channel上添加监听 addConfirmListener,发送消息后,则会回调该方法,通知是否发送成功,但RabbitMQ异步确认有可能是单条,有可能是多条
-
异步确认代码
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.10.233");
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
channel.confirmSelect();
ConfirmListener confirmListener = new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// 确认成功
// deliveryTag:在确认前多少条消息
// multiple:true代表确认多条消息,false代表确认单条消息
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
// 确认失败
}
};
channel.addConfirmListener(confirmListener);
String msgToSend = "消息内容";
// 发消息
channel.basicPublish("exchange.test.msg",
"key.msg",
null, msgToSend.getBytes(StandardCharsets.UTF_8));
}
:: 推荐使用单条同步确认 ::
2.消息返回机制
- 为什么需要消息返回机制?
发送端消息发出后,不知道该消息是否被正确路由
- 什么是消息返回机制
发送端消息发出后,RabbitMQ会对消息进行路由,
如果没有发现目标队列,则会回调 ReturnListener 通知发送方
- 开启消息返回机制
设置 Mandatory 为true,
若设置为false,RabbitMQ则会直接丢弃无法路由的消息
basicPublish 有三个不同参数的方法:
public void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException {
this.delegate.basicPublish(exchange, routingKey, props, body);
}
public void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body) throws IOException {
this.delegate.basicPublish(exchange, routingKey, mandatory, props, body);
}
public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException {
this.delegate.basicPublish(exchange, routingKey, mandatory, immediate, props, body);
}
我们调用有 boolean mandatory 这个参数的即可,传参为true,大致代码如下:
第一种,new ReturnListener()
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replayCode, String replayText, String exchange, String routingKey, AMQP.BasicProperties basicProperties, byte[] body) throws IOException {
// 消息无法被路由
}
});
String msgToSend = objectMapper.writeValueAsString(orderMessage);
// 发消息
channel.basicPublish("exchange.order.restaurant",
"key.restaurant", true,
null, msgToSend.getBytes(StandardCharsets.UTF_8));
第二种,new ReturnCallback()
channel.addReturnListener(new ReturnCallback() {
@Override
public void handle(Return returnMessage) {
}
});
两种方法没有区别,只是 new ReturnCallback() 将返回的数据封装在了 Return 类里,返回的数据都是一样的。
返回的数据有 replayCode、replayText、exchange、routingKey、basicProperties、body
3.消费端确认机制
默认情况下,消费端接收到消息后,会自动确认(ACK)
- 消费端ACK类型
-
- 自动ACK:消费端收到消息后,会自动签收消息
-
- 手动ACK:不会自动签收消息,需要代码中显式进行签收
- 手动ACK类型
-
- 单条手动ACK:multiple=false
-
- 多条手动ACK:multiple=true
推荐单条手动ACK
DeliverCallback deliverCallback = (consumerTag, message) -> {
String messageBody = new String(message.getBody());
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
4.消费端限流机制
- 为什么需要限流
业务高峰时期,消息过多,接收端处理不了,导致服务崩溃
开启限流机制,限制消息推送速度,保证服务稳定
- QoS(服务质量保证)
QoS功能保证了在一定数目的消息未被确认前,不消费新的消息
QoS前提:不使用自动确认
具体参数设置
prefetchCount:针对一个消费端最多推送多少未确认消息
global:true:针对整个消费端限流;false:针对当前channel
prefetchSize:单个消息大小限制,一般为0
RabbitMQ暂未实现global和prefetchSize
// 开启限流
channel.basicQos(10);
channel.basicConsume("queue.msg", false, deliverCallback, consumerTag -> {});
5.消息过期机制
默认情况下,消息进入队列,会一直存在,直到被消费
RabbitMQ的过期时间称为TTL(Time to Live)
分为消息TTL(单条消息过期时间) 和 队列TTL(队列中所有消息的过期时间)
不推荐单独使用TTL,因为这会导致直接删除消息
如何设置合适的TTL
TTL应该要长于服务平均重启的时间,并且长于业务高峰期时间
使用方法
- 设置单条消息TTL
// expiration 单位为毫秒
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("15000").build();
channel.basicPublish("exchange.test.msg", "key.msg", properties, messageToSend.getBytes());
- 设置队列TTL
Map<String, Object> args = new HashMap<>(16);
args.put("x-message-ttl", 15000);
channel.queueDeclare("queue.test",
true, false, false, args);
如果当前队列已经存在,则需要先删除重新进行创建才能设置过期时间,不然执行会报错!
6.死信队列
设置了过期时间的消息,过期后会被直接丢弃,无法对系统异常发出警报。
死信队列的作用就是收集过期的消息
什么是死信队列
队列设置了DLX属性(Dead-Letter-Exchange)
生产消息 -> Exchange -> Queue -> DL Exchange -> DL Queue -> 异常监听处理
什么情况下会变成死信
- 消息被拒绝并且requeue=false(不重回队列)
- 消息过期
- 队列达到最大长度
设置方法
- 1.设置转发、接收死信的交换机和队列
- Exchange:dlx.exchange
- Queue:dlx.queue
- RoutingKey:#
- 2.给死信队列设置参数
- x-dead-letter-exchange=dlx.exchange
// 创建接收死信的交换机
channel.exchangeDeclare("exchange.dlx", BuiltinExchangeType.TOPIC,
true, false, null);
// 创建接收死信的队列
channel.queueDeclare("queue.dlx", true, false, false, null);
// 进行绑定
channel.queueBind("queue.dlx", "exchange.dlx", "*");
// 设置死信队列
Map<String, Object> args = new HashMap<>(16);
args.put("x-dead-letter-exchange", "exchange.dlx");
channel.queueDeclare("queue.restaurant", true, false, false, args);