1.声明

当前内容用于本人学习和复习之用,当前内容主要包括发布者确认消息和消息不可达目的地的时候的消息的返回

当前内容来源:RabbitMQ官方

当前内容主要包括:

  1. 消息不可达目的地的时候的消息返回设定
  2. 使用生产者的消息确认机制(主要是用于确认消息是否发送到exchange上面)

2.官方的Publisher Confirms

Networks can fail in less-than-obvious ways and detecting some failures takes time. Therefore a client that’s written a protocol frame or a set of frames (e.g. a published message) to its socket cannot assume that the message has reached the server and was successfully processed. It could have been lost along the way or its delivery can be significantly delayed.

网络导致的消息发送失败是不明显的,需要一段时间去检查。因此,将协议帧或一组帧(例如,已发布的消息)写入其套接字的客户端不能假定该消息已到达服务器并已成功处理。一路上可能会丢失它,或者会严重延迟其交付。

就是说已发布的消息可能在到达服务器的时候出现丢失的问题(这个问题可能是网络的原因)

Using standard AMQP 0-9-1, the only way to guarantee that a message isn’t lost is by using transactions – make the channel transactional then for each message or set of messages publish, commit. In this case, transactions are unnecessarily heavyweight and decrease throughput by a factor of 250. To remedy this, a confirmation mechanism was introduced. It mimics the consumer acknowledgements mechanism already present in the protocol.

使用标准的AMQP 0-9-1,使用事务是唯一一个保证消息不会丢失的方法,使用句柄事务对于每一个消息或者一堆消息发布,提交。在这种情况下,事务将会不必要的增大,并且吞吐量下降了250倍。为此,引入了一个确认呢机制。它模仿了已近存在的消费者确认的协议

就是说:要保证消息不丢失的方式就是使用事务,但是这种情况会导致吞吐量下降250倍,是模仿消费者确认实现的

To enable confirms, a client sends the confirm.select method. Depending on whether no-wait was set or not, the broker may respond with a confirm.select-ok. Once the confirm.select method is used on a channel, it is said to be in confirm mode. A transactional channel cannot be put into confirm mode and once a channel is in confirm mode, it cannot be made transactional.

启用确认,一个客户端需要发送confirm.select方法。如果设置了no-wait那么这个代理者将会响应confirm.select-ok,否者不返回。select方法通常倍使用在句柄上。这是一个确认模式。一个事务句柄不能放在确认模式里面,一旦一个句柄是确认模式,那么就不能使用事务

就是说,通过confirm.select方法启用确认,通过是否设置no-wait,那么代理者将是否返回confirm.select-ok。并且确认模式和事务模式向冲突的

Once a channel is in confirm mode, both the broker and the client count messages (counting starts at 1 on the first confirm.select). The broker then confirms messages as it handles them by sending a basic.ack on the same channel. The delivery-tag field contains the sequence number of the confirmed message. The broker may also set the multiple field in basic.ack to indicate that all messages up to and including the one with the sequence number have been handled

channel在确认模式的时候,这个代理者和客户端都会计数(计数从1开始在第一使用confirm.select),代理者确认呢消息并处理它通过返回一个basic.ack在相同的句柄上。确认消息的delivery-tag字段将包含一个序列数字。这个代理者可能也有将字段multiple写在在basic.ack上,以指示所有消息(包括序列号的消息)已经处理

3.创建一个简单的Demo用于实现发布者确认(使用confirmSelect实现)

1.想要实现消息确认(可以使用将当前的消息队列和exchange之间解绑造成消息不可达目的地)

java rabbitmq 消费者 崩溃_System

2.编写消息消费者(该例子带有消息返回处理,消息确认监听)

/**
 * @description 消息确认机制:消息生产者确认机制
 * @author hy
 * @date 2020-05-15
 */
public class ProviderMessageAcknowledgementsTest {
	private static final String queueName = "hello";

	public static void main(String[] args) {
		RabbitMqUtils mqUtils = new RabbitMqUtils();
		Connection connection = null;
		try {
			connection = mqUtils.getConnection();
			final Channel chan = connection.createChannel();
			// 回调方式应答,就是服务器发送消息,然后直接在函数内应答
			// chan.queueDeclare(queueName, true, false, false, null);
			System.out.println("添加确认监听===>");

			chan.addConfirmListener((deliveryTag, multiple) -> {
				System.out.println("进入确认回调====================START=================");
				// 发现这个flag自增
				System.out.println("ackCallback==>deliveryTag=" + deliveryTag + ",multiple=" + multiple);
				System.out.println("进入确认回调=====================END==================");
			}, (deliveryTag, multiple) -> {
				System.out.println("进入nack回调");
				System.out.println("nackCallback==>deliveryTag=" + deliveryTag + ",multiple=" + multiple);
			});

			System.out.println("添加返回监听===>");
			chan.addReturnListener((x) -> {
				System.out.println("进入返回回调================START===================");
				System.out.println("返回文本=>"+x.getReplyText());
				System.out.println("返回code=>"+x.getReplyCode());
				try {
					System.out.println("返回文本=>"+new String(x.getBody(),"utf-8"));
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("进入返回回调==================END===================");
			});

			System.out.println("开始执行selectOk====>");
			SelectOk confirmSelect = chan.confirmSelect();// 表示启用消息生产者的确认机制

			System.out.println("selectOk的执行结果===>" + confirmSelect);
			System.out.println("开始发送消息===>");
			BasicProperties.Builder builder = new BasicProperties.Builder();
			for (int i = 0; i < 5; i++) {
				 // mandatory 表示目的地不可达的时候,是否将消息返回,如果为false,则认为丢弃数据,如果为true,则进入return回调
				chan.basicPublish("test", "", false, builder.deliveryMode(2).build(), ("你好世界===>" + i).getBytes());
				long nextPublishSeqNo = chan.getNextPublishSeqNo();
				try {
					// waitForConfirms必须在confirmSelect的后面否则报错
					boolean waitForConfirms = chan.waitForConfirms();//等待确认,没确认直接返回false,确认返回true
					System.out.println("是否确认===>" + nextPublishSeqNo + ",确认是否发送成功=" + waitForConfirms);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				System.out.println("序列==>" + nextPublishSeqNo);
			}

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}

}

3.测试发送只有exchange(没有bindings任何的queue)

java rabbitmq 消费者 崩溃_服务器_02

4.修改mandatory=true,表示消息不可达的消息返回(再次执行)

java rabbitmq 消费者 崩溃_句柄_03


出现了消息返回回调(这里我们发现如果当消息不可达的时候要么exchange直接丢弃消息要么返回消息给生产者)5.开始将test绑定到hello队列

java rabbitmq 消费者 崩溃_句柄_04


java rabbitmq 消费者 崩溃_System_05


6.再次启动消息生产者

java rabbitmq 消费者 崩溃_句柄_06


7.创建并启动消费者

public class ConsumerMessageAcknowledgementsTest {
	private static final String queueName = "hello";

	public static void main(String[] args) {
		RabbitMqUtils mqUtils = new RabbitMqUtils();
		Connection connection = null;
		try {
			connection = mqUtils.getConnection();
			final Channel chan = connection.createChannel();
			// 回调方式应答,就是服务器发送消息,然后直接在函数内应答
			chan.queueDeclare(queueName, true, false, false, null);
			
			  chan.basicConsume("hello", false, "a-consumer-tag", new DefaultConsumer(chan)
			  {
			  
			  @Override public void handleDelivery(String consumerTag, Envelope envelope,
			  AMQP.BasicProperties properties, byte[] body) throws IOException {
			  System.out.println("消费者开始处理消息===>" + new String(body, "utf-8")); // 获取传递标记
			  long deliveryTag = envelope.getDeliveryTag(); //应用程序发送回确认之后(使用basic.ack方法),队列已经消费成功 //
			  chan.basicAck(deliveryTag, false);
			  System.out.println("消息执行成功===>通知服务器消费成功"); } });
			 

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

消费成功

8.直接启动消息生产者(同时存在消息消费者)

java rabbitmq 消费者 崩溃_System_07

9.测试注释这句话(chan.waitForConfirms();),发现一个不同点

java rabbitmq 消费者 崩溃_System_08

通过查看api发现waitForConfirms返回的结果为:是否所有的消息都是ack'd,这就是为什么前面这个地方都是true的原因,这个是等待确认,所以产生的回调消息比较多需要一条一条的确认,而注释掉那么回调的消息并不是很多

通过查看api发现:addConfirmListener的第一就是表示代理者接收成功的回调,第二个就是代理者消息丢失的回调

4.总结

1.开启消息返回监听需要在发送前添加返回监听,还需要设置mandatory为true(false表示代理者丢弃)

2.可以添加addConfirmListener确认监听

3.如果开启了waitForConfirms()那么就会导致多条回调确认消息的产生,因为这个是一条一条确认的

以上纯属个人见解,如有问题请联系本人!