文章目录
1. Confirm 确认消息
- 概念:主要保证broker能够肯定收到消息;当Broker收到消息的时候,会给生产者回复一个应答。
- 案例
public class RabbitMQConfirmConsumer {
public static void main(String[] args) throws Exception {
// 1.创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
// 2.根据connectionFactory获取Connection
Connection connection = connectionFactory.newConnection();
// 3.通过Connection创建一个Channel
Channel channel = connection.createChannel();
// 4.声明一个交换机, 队列以及队列的绑定.
String exchangeName = "test_confirm_exchange";
String queueName = "confirm_queue";
String routingKey = "confirm.save";
channel.queueDeclare(queueName, true, false, false, null);
channel.exchangeDeclare(exchangeName, "topic", true);
channel.queueBind(queueName, exchangeName, routingKey);
// 5.创建消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 6.设置channel
channel.basicConsume(queueName, true, consumer);
while (true) {
// 7.获取信息
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String body = new String(delivery.getBody());
System.out.println("消费端: " + body);
}
}
}
public class RabbitMQConfirmProduct {
public static void main(String[] args) throws Exception {
// 1.创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
// 2.根据connectionFactory获取Connection
Connection connection = connectionFactory.newConnection();
// 3.通过Connection创建一个Channel
Channel channel = connection.createChannel();
// 4.指定消息投递模式: 消息确认模式
channel.confirmSelect();
// 4.通过channel发生数据
String message = "Hello world!, RabbitMQ!";
String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.save";
IntStream.range(0, 5).forEach(item -> { try {
channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
} catch (IOException e) { e.printStackTrace();
} });
// 5.监听Broker的返回值
channel.addConfirmListener(new ConfirmListener() {
/**
* 正确返回
* @param deliveryTag: message的唯一ID.
* @param multiple: 是否批量.
* @throws IOException
*/
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
}
/**
* 错误返回
* @param deliveryTag: message的唯一ID.
* @param multiple: 是否批量.
* @throws IOException
*/
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
}
});
// 6.不能关闭连接, 因为addConfirmListener时异步的不会阻塞程序执行.
// channel.close();
// connection.close();
}
}
2. Return 消息机制
- 概念:
- Return Listener:用于处理一些不可路由的消息!
- 通常使用Exchange和Routing Key把消息发送到某个队列中去,然后消费端监听队列,消费队列消息。
- 而有时,生产端发送消息时,指定的Exchange或是Routing Key找不到时,这时就需要使用Return Listener监听这种不可达的消息,然后进行相应的处理。
- 重要参数:Mandatory:为true的时候,监听器会接受这些不可达的消息,然后进行相应的后续处理;若设置为false,则Broker直接删除不可达的消息。
- 案例:
public class ReturnListenerProduct {
public static void main(String[] args) throws Exception {
// 1.创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
// 2.根据connectionFactory获取Connection
Connection connection = connectionFactory.newConnection();
// 3.通过Connection创建一个Channel
Channel channel = connection.createChannel();
// 4.通过channel发送数据
String message = "Hello world!, RabbitMQ!";
String exchangeName = "test_return_exchange";
String routingKey = "return.save";
String routingErrorKey = "abc.save";
IntStream.range(0, 5).forEach(item -> {
try {
// 第三个参数即为Mandatory, 为true的时候, 会调用addReturnListener事件.
channel.basicPublish(exchangeName, routingKey, true, null, message.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
});
// 5.监听Broker的返回值
channel.addReturnListener((replyCode, replyText, exchange, routingKey1, properties, body) -> { System.out.println("Return handle");
// 可以把返回的这些数据, 输出日志, 获取传输到监控台, 能够及时发现错误.
});
// 6.不能关闭连接, 因为addConfirmListener时异步的不会阻塞程序执行.
// channel.close();
// connection.close();
}}
public class ReturnListenerConsumer {
public static void main(String[] args) throws Exception {
// 1.创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
// 2.根据connectionFactory获取Connection
Connection connection = connectionFactory.newConnection();
// 3.通过Connection创建一个Channel
Channel channel = connection.createChannel();
// 4.声明交换机、队列, 以及绑定队列.
String exchangeName = "test_return_exchange";
String routingKey = "return.save";
String queueName = "test001";
channel.exchangeDeclare(exchangeName, "topic", true);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey, null);
// 5.创建消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 6.设置channel
channel.basicConsume(queueName, true, consumer);
while (true) { // 7.获取信息
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String body = new String(delivery.getBody());
System.out.println("消费端: " + body);
} }
}
3. 消费端自动监听
- 概述:不能使用上面代码中的while(true); 这样会一直阻塞住程序往下执行, 且不管有没有数据都往Broker去获取消息,很不优雅。
- 解决方法:只需要consumer去继承一个
DefaultConsumer
类即可。 - 案例:
// 设置channel, 自定义消费者监听
channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
/**
* 监听返回.
* @param consumerTag: rabbitMQ生成的一串标记.
* @param envelope: 包含(消息的唯一ID, Exchange_Name, Queue_Name)
* @param properties: 信息头.
* @param body: 信息体.
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body);
}});
4. 消费端限流
- RabbitMQ提供一直qos(服务质量保障)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过Consumer或者Channel设置Qos值)未被确认前,不进行消费新的消息。
- 在consumer端使用
void BasicQos(unit prefetchSize, ushort prefetchCount, bool global)
; BasicQos
参数详解:只有在不自动ACK的时候, 才起效。- prefetchSize:消息大小限制。0则代表不做限制。
- prefetchCount:一次能够处理多少条数据;实际中设置为1为佳;
- global:表示这个限流策略在什么上应用的;true:在Channel上;false:在Consumer上;实际中设置为false,在consumer上使用限流。
- 案例:若消费端一直没有ACK, 则Broker会一直阻塞其它的消息发送给改消费端。
// 处理限流第一步
channel.basicQos(0, 1, false);
// 处理限流第二步: 设置autoAck设置为false.
channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
/**
* 监听返回.
* @param consumerTag: rabbitMQ生成的一串标记.
* @param envelope: 包含(消息的唯一ID, Exchange_Name, Queue_Name)
* @param properties: 信息头.
* @param body: 信息体.
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body);
// 处理限流第三步: 手动设置ACK;
channel.basicAck(envelope.getDeliveryTag(), false);
}});
5. 消费端ACK与重回队列
-
消费端的手工ACK和NACK
- 手工ACK:是设置了autoACK为false,需要consumer调用
channel.basicAck(envelope.getDeliveryTag(), false);
手工ACK,确认这条消息收到啦。 - NACK:表示ACK失败,需要生产端再次发送消息。
- 手工ACK:是设置了autoACK为false,需要consumer调用
-
消费端的重回队列
- 消费端的重回队列是为了没有处理成功的消息,把消息重新回递给Broker。
- 一般在实际应用中,都会关闭重回队列,使用补偿机制。
- 重回队列的消息,会被直接方法队尾。
-
案列:
channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
/**
* 监听返回.
* @param consumerTag: rabbitMQ生成的一串标记.
* @param envelope: 包含(消息的唯一ID, Exchange_Name, Queue_Name)
* @param properties: 信息头.
* @param body: 信息体.
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body);
/**
* NACK和重回队列
* 1. 使用的是{@link Channel#basicNack(long deliveryTag, boolean multiple, boolean requeue)} api.
* 2. 这个api第三个参数 requeue表示是否重回队列.
*/
channel.basicNack(envelope.getDeliveryTag(), false, false);
//手动设置ACK;
channel.basicAck(envelope.getDeliveryTag(), false);
}});
6. TTL队列/消息
- 概述:TTL队列/消息即是队列和消息可以设置过期时间。
- 区别:
- TTL队列:设置队列的过期时间,到了过期时间,会清空队列,然后删除队列。
- TTL消息:到期了会自动删除消息。通过AMQP.properties来设置TTL。
7. 死信队列(DLX-Dead letter exchange)
- 概述:利用DLX,当消息在一个队列中变为死信(没有客户端去消费的数据)之后,它能重新publish到另一个Exchange,这个Exchange就是DLX。
- 消息变为死信的情况:
- 消息被拒绝(basic.reject/basic.nack)并且不重回队列(requeue=false)。
- 消息TTL过期。
- 队列达到了最大长度时,再过来的消息,会直接放入到死信队列中。
- 死信队列设置:
- 设置死信队列的exchange和queue,然后进行绑定:
- Exchange:dlx.exchange;
- queue: dlx.queue;
- Routing key:#;只要路由到了这个Exchange,即不管任何的Routing key都直接放入到这个DLX队列中。
- 正常队列该如何和DLX绑定呢?
- 正常声明Exchange、queue、routing key绑定等等,但是需要在队列上加上一个参数arguments.put(“x-dead-letter-exchange”, “dlx.exchange”)
- 这样当队列出现死信的时候,就会自动把死信路由到死信队列中。
- 设置死信队列的exchange和queue,然后进行绑定:
- 案例:
// 4.正常的声明交换机、队列, 以及绑定队列.
String exchangeName = "test_dlx_exchange";
String routingKey = "dlx.save";
String queueName = "test001";
channel.exchangeDeclare(exchangeName, "topic", true);
// 5.给正常Queue设置, 私信的Exchange. 必须给queueDeclare的arguments设置私信队列配置.
String DLXExchange = "dlx.exchange";
Map<String, Object> arguments = new HashMap<>(1);
arguments.put("x-dead-letter-exchange", DLXExchange); // 这个key是固定的, 不允许被修改.
channel.queueDeclare(queueName, true, false, false, arguments);
channel.queueBind(queueName, exchangeName, routingKey, null);
// 6. 声明私信Exchange和Queue.
channel.exchangeDeclare(DLXExchange, "topic", true);
channel.queueDeclare("DLX_queue", true, false, false, null);
// '#'代表任何的死信, 只要路由到了这个Exchange的时候, 都会被路由到这个queue中.
channel.queueBind("DLX_queue", DLXExchange, "#");