一、生产者如何保证消息可靠发送给rabbitmq
消息生产者可以通过采用事务transaction或者发送方确认模式两种保证消息发送到rabbitmq。
1、事务模式
transaction机制就是说:发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。然而,这种方式有个缺点:吞吐量下降;不建议使用
String message = "Hello RabbitMQ:";
try {
channel.txSelect();
for (int i = 0; i < 5; i++) {
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, (message + i).getBytes("UTF-8"));
}
channel.txCommit();
} catch (Exception e) {
channel.txRollback();
}
2、发送者确认模式
确认模式就是将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认ACK给生产者(包含消息唯一ID)。 如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(not acknowledged,未确认)消息。发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。
确认机制中可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
事务机制和confirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息rabbitmq接收了之后会异步回调你一个接口通知你这个消息接收到了
java实现
java实现中通过方法channel.confirmSelect()启动确认模式,并通过waitForConfirms()确认是否发送成功
factory.setUsername("guest");
factory.setPassword("guest");
factory.setHost("127.0.0.1");
factory.setVirtualHost("/");
factory.setPort(5672);
/**
* 创建连接
*/
Connection connection = factory.newConnection();
/**
* 创建信道
*/
Channel channel = connection.createChannel();
//启动生产者确认模式
channel.confirmSelect();
/**
* 声明一个type=topic持久化的 非自动删除的交换器
* 第三个参数表示是否持久化
*/
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, null);
/**
* 声明一个持久化 非排他的 非自动删除的队列,第二个参数表示是否持久化
*/
channel.queueDeclare(QUEUR_NAME, true, false, false, null);
/**
* 将交换器与队列通过路由键绑定
*/
bindingKey = "#." + bindingKey + ".#";
channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
/**
* 发送一条持 久化消息
* MessageProperties.PERSISTENT_TEXT_PLAIN表示
*/
String message="Hello RabbitMq!";
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN , message.getBytes());
//通过waitforconfirms确认是否发送成功
if (channel.waitForConfirms()) {
System.out.println("发送成功");
}else{
//进行消息重发
}
/**
* 关闭资源
*/
channel.close();
connection.close();
二、consumer如何确保信息可靠使用并且rabbitmq删除对应消息
接收方消息确认机制来确保消息被消费:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要根据bizId去重)
在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列;在消息消费时,要求消息体中必须要有一个bizId(对于同一业务全局唯一,如支付ID、订单ID、帖子ID等)作为去重和幂等的依据,避免同一条消息被重复消费
channel.basicAck(tag, true)、channel.basicNack(tag, false, true);
- 消息ACK,通知RabbitMQ消息已被处理,可以从内存删除。如果消费者因宕机或链接失败等原因没有发送ACK(不同于ActiveMQ,在RabbitMQ里,消息没有过期的概念),则RabbitMQ会将消息重新发送给其他监听在队列的下一个消费者。
channel.basicConsume(queuename, noAck=false, consumer);
三、消息和队列的持久化。定义队列时可以指定队列的持久化属性(问:持久化队列如何删除?)
channel.queueDeclare(queuename, durable=true, false, false, null);
发送消息时可以指定消息持久化属性:
channel.basicPublish(exchangeName, routingKey,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
这样,即使RabbitMQ服务器重启,也不会丢失队列和消息。
四、如何避免消息重复投递或重复消费?
在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重的依据(消息投递失败并重传),避免重复的消息进入队列;
在消息消费时,要求消息体中必须要有一个bizId(对于同一业务全局唯一,如支付ID、订单ID、帖子ID等)作为去重的依据,避免同一条消息被重复消费。