来一幅图:
说明:可靠性和效率是不可兼得的,保证可靠得牺牲一部分效率。
为了保障消息成功从生产者投递到broker:
采用comfirm确认消息机制,如果Broker端接受到消息,那么就会回送相应,然后生产者会监听Broker给的应答,流程图:
实现方式:
代码如下:
生产者:
public class Producer {
public static void main(String[] args) throws Exception {
//1 创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.11.76");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2 获取C onnection
Connection connection = connectionFactory.newConnection();
//3 通过Connection创建一个新的Channel
Channel channel = connection.createChannel();
//4 指定我们的消息投递模式: 消息的确认模式
channel.confirmSelect();
String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.save";
//5 发送一条消息
String msg = "Hello RabbitMQ Send confirm message!";
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
//6 添加一个确认监听
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.err.println("-------no ack!-----------");
}
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.err.println("-------ack!-----------");
}
});
}
}
队列收到消息之后会自动ACK(或者在消费者端可以手动选择channel的NCK),那么生产者端的监听就会收到回复。
2.如果消息路由不到指定的队列,处理路由不到的问题,那么方法一就可以使用Return Listener.
流程图如下:
如果发送消息的时候,可能因为routingkey错误,或者队列不存在,或者队列名称错误导致路由失败。
使用方式:
使用mandatory参数(即发送消息时候的第三个参数设置为true)和ReturnListener,可以实现消息无法路由的时候返回给生产者。
核心代码:
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange,
String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.err.println("---------handle return----------");
System.err.println("replyCode: " + replyCode);
System.err.println("replyText: " + replyText);
System.err.println("exchange: " + exchange);
System.err.println("routingKey: " + routingKey);
System.err.println("properties: " + properties);
System.err.println("body: " + new String(body));
}
});
channel.basicPublish(exchange, routingKeyError, true, null, msg.getBytes());
此时如果消息路由不到,生产者配置的监听会拿到该消息。
方法二是采用备份交换机(alternate-exchange),无法路由的消息会发送到这个交换机上。
代码如下:
Map arguments = new HashMap();
arguments.put("alternate-exchange","ALTERNATE_EXCHANGE"); // 指定交换机的备份交换机
channel.exchangeDeclare("TEST_EXCHANGE","topic", false, false, false, arguments);
3.确保消息成功从队列投到消费者(注意是队列到消费者):
采用ACK机制,在channel操作队列和消费者时候即首先关闭autoAck,
// 手工签收 必须要关闭 autoAck = false
channel.basicConsume(queueName, false, new MyConsumer(channel));
这里我使用的是自定义消费者,自定义消费者代码:
public class MyConsumer extends DefaultConsumer {
private Channel channel ;
public MyConsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.err.println("-----------consume message----------");
System.err.println("body: " + new String(body));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if((Integer)properties.getHeaders().get("num") == 0) {
channel.basicNack(envelope.getDeliveryTag(), false, true);
} else {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
}
如果是第0条消息,那么就拒绝并将此消息重回队列尾部,如果不是第0条那么就ACK。
生产者的代码如下,每次发送消息都有num标识:
public class Producer {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.11.76");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String exchange = "test_ack_exchange";
String routingKey = "ack.save";
for(int i =0; i<5; i ++){
Map headers = new HashMap();
headers.put("num", i);
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2)
.contentEncoding("UTF-8")
.headers(headers)
.build();
String msg = "Hello RabbitMQ ACK Message " + i;
channel.basicPublish(exchange, routingKey, true, properties, msg.getBytes());
}
}
}
开启消费者和生产者,控制台打印如下:
可以看出第0条消息一直被循环消费(因为这个队列只绑定了一个消费者,此消费者设置了第0条消息重回队列,那么就循环消费)。