当一个消息发送出去之后,我们需要知道它是否发送成功。在RabbitMQ中提供了:
1)Confirm callback:确认消息是否到达broker。
2) Return callback :当消息到达broker后,exchange 路由消息的时候,如果根据 routing key 无法找到一个匹配的队列,则触发此回调(在发送消息时候需要指定 mandatory = true)
import com.rabbitmq.client.*;
import java.io.IOException;
public class MqClient {
private static int tryNum = 1000;
public static void topicQueueTest() throws Exception{
String EXCHANGE_NAME = "topic_exchange";
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.91");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
channel.addConfirmListener(new RbmqConfirmListener());//Confirm events 消息发送到broker后触发回调
channel.addReturnListener(new RbmqReturnListener());//无法找到一个匹配的队列的时候触发
channel.addShutdownListener(new RbmqShutdownListener());
channel.exchangeDeclare(EXCHANGE_NAME, "topic", true);
AMQP.BasicProperties props = setSendPros();
channel.basicPublish(EXCHANGE_NAME,"quick.orange.rabbit", true,props, "topic:quick.orange.rabbit".getBytes());
channel.basicPublish(EXCHANGE_NAME,"lazy.orange.elephant", true,props, "topic:lazy.orange.elephant".getBytes());
channel.basicPublish(EXCHANGE_NAME, "lazy.brown.fox", true, props, "topic:lazy.brown.fox".getBytes());
channel.basicPublish(EXCHANGE_NAME, "a.b.rabbit", true, props, "topic:a.b.rabbit".getBytes());
while( tryNum-- > 0) { Thread.sleep(1000); }
channel.close();
connection.close();
}
private static AMQP.BasicProperties setSendPros() {
return MessageProperties.PERSISTENT_TEXT_PLAIN;
}
public static void main(String[] args) throws Exception {
//routeQueueTest();
//fanoutQueueTest();
topicQueueTest();
}
}
class RbmqConfirmListener implements ConfirmListener {
//消息正确到达broker
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println(System.currentTimeMillis() + " RbmqConfirmListener::handleAck messageID:" + deliveryTag + " multiple:" + multiple);
}
//消息丢失
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println(System.currentTimeMillis() + " RbmqConfirmListener::handleNack messageID:" + deliveryTag + " multiple:" + multiple);
}
}
class RbmqReturnListener implements ReturnListener {
//无法找到一个匹配的队列时调用
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(System.currentTimeMillis() + " RbmqReturnListener::handleReturn replyCode:" + replyCode + " replyText:" + replyText +
" exchange:" + exchange + " routingKey:" + routingKey);
}
}
///
import com.rabbitmq.client.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class MqServer {
public static void topicConsumer() throws Exception {
final String EXCHANGE_NAME = "topic_exchange";
final String QUEUE_NAME = "topic_queue_1";
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("10.129.2.91");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false,null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "*.orange.*");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "lazy.#");
channel.basicQos(1);//告诉服务器,在我们没有确认当前消息完成之前,不要给我们发生消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(20);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("消费者1收到内容是:" + new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息
}
};
//注册消费者,参数2.代表我们收到消息后需要手动告诉服务器:我收到消息了
channel.basicConsume(QUEUE_NAME, false, consumer);
new BufferedReader(new InputStreamReader(System.in)).readLine();
channel.close();
connection.close();
}
public static void main(String[] argv) throws Exception {
topicConsumer();
}
}
运行(只启动发送者且确保队列不存在):
1550823065618 RbmqReturnListener::handleReturn replyCode:312 replyText:NO_ROUTE exchange:topic_exchange routingKey:quick.orange.rabbit
1550823065618 RbmqConfirmListener::handleAck messageID:1 multiple:false
1550823065618 RbmqReturnListener::handleReturn replyCode:312 replyText:NO_ROUTE exchange:topic_exchange routingKey:lazy.orange.elephant
1550823065618 RbmqConfirmListener::handleAck messageID:2 multiple:false
1550823065618 RbmqReturnListener::handleReturn replyCode:312 replyText:NO_ROUTE exchange:topic_exchange routingKey:lazy.brown.fox
1550823065618 RbmqConfirmListener::handleAck messageID:3 multiple:false
1550823065618 RbmqReturnListener::handleReturn replyCode:312 replyText:NO_ROUTE exchange:topic_exchange routingKey:a.b.rabbit
1550823065618 RbmqConfirmListener::handleAck messageID:4 multiple:false
---------------------
先启动消费者,再运行生产者:
1550823349393 RbmqReturnListener::handleReturn replyCode:312 replyText:NO_ROUTE exchange:topic_exchange routingKey:a.b.rabbit
1550823349409 RbmqConfirmListener::handleAck messageID:4 multiple:false
1550823349409 RbmqConfirmListener::handleAck messageID:2 multiple:true
1550823349409 RbmqConfirmListener::handleAck messageID:3 multiple:false
消费者1收到内容是:topic:quick.orange.rabbit
消费者1收到内容是:topic:lazy.orange.elephant
消费者1收到内容是:topic:lazy.brown.fox
消息通过ACK 确认是否被消费者正确接收。每个 Message 都要被确认(acknowledged),可以手动去 ACK 或自动 ACK
自动ACK(默认): 在消息发送给消费者后立即确认并从队列中删除消息。如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。
手动确认:消费者调用ack,nack,reject几种方法进行确认。手动确认可以在业务失败后进行一些操作,如果消息未被 ACK 且连接断开后则会将消息发生给其他消费者。
在上面的例子中:
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("消费者1收到内容是:" + new String(body));
channel.basicReject(envelope.getDeliveryTag(), true);//拒绝消息,这时服务器会重发消息 //channel.basicNack(envelope.getDeliveryTag(), false, true);//批量拒绝消息
//channel.basicAck(envelope.getDeliveryTag(), false);//消息消费成功,服务器把消息从队列中删除
//参数2,为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息
}
};
//在注册消费者,参数2.代表我们收到消息后需要手动ACK
channel.basicConsume(QUEUE_NAME, false, consumer);
运行:
1550825188423 消费者1收到内容是:topic:quick.orange.rabbit
1550825189608 消费者1收到内容是:topic:quick.orange.rabbit
1550825190794 消费者1收到内容是:topic:quick.orange.rabbit
1550825191979 消费者1收到内容是:topic:quick.orange.rabbit
消费者会一直收到相同的重传消息。
(此时只有一个消费者监听该队列,则有发生死循环的风险,多消费端也会造成资源的极大浪费,这个在开发过程中一定要避免的)。
Overview | Messages | Message rates | +/- | ||||||
---|---|---|---|---|---|---|---|---|---|
Name | Features | State | Ready | Unacked | Total | incoming | deliver / get | ack | |
fanout_queue_1 | D | idle | 0 | 0 | 0 | ||||
route_queue_1 | D | idle | 2 | 0 | 2 | ||||
topic_queue_1 | D | running | 2 | 1 | 3 | 0.00/s | 1.0/s | 0.00/s |
对于RabbitMQ服务器端而言,队列中的消息分成了两部分:
一部分是等待投递给消费者的消息;
一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息。
如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者。
消费者通过ACK 机制中的nack(批量拒绝)/reject (enqueue参数),让服务器是否发起重传。
为了保证RabbitMQ在退出,服务重启或者crash等异常情况下,也不会丢失消息,我们可以将Queue,Exchange,Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。当然还是会有一些小概率事件会导致消息丢失。
channel.exchangeDeclare(EXCHANGE_NAME, "topic", true);
channel.queueDeclare(QUEUE_NAME, true, false, false,null);
队列是可以被持久化,但是里面的消息是否为持久化那还要看消息的持久化设置。当消息在一个队列中变成死信(dead message)之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX( Dead-Letter-Exchange)。消息变成死信有以下几种情况:
消息被拒绝(basic.reject 或basic.nack)并且requeue=false
消息TTL过期(Time-To-Live 过期时间)
队列达到最大长度
DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定.实际上就是设置某个队列的属性,当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列,可以监听这个队列中消息做相应的处理.
channel.exchangeDeclare("dlx.exchange.name", "direct"); Mapargs = new HashMap<>(); args.put("x-max-length", 10240); args.put("x-dead-letter-exchange", "dlx.exchange.name"); channel.queueDeclare(QUEUE_NAME, true, false, false,args);