怎样保证消息发布的可靠性
rabbitMQ为我们提供了在生产者端来保证消息的可靠性的一系列方式
包括以下几种:
队列持久化
在声明队列的时候把druable参数设置为true 就可以保证队列的持久化
队列高可用
使用镜像队列保证队列的高可用
消息持久化
在发布消息的时候设置deliveryMode 为2
事物方式
事务的实现主要是对信道(Channel)的设置,主要的方法有三个:
-
channel.txSelect()声明启动事务模式;
-
channel.txComment()提交事务;
-
channel.txRollback()回滚事务;
在发送消息之前,需要声明 channel 为事务模式,提交或者回滚事务即可。
开启事务后,客户端和 RabbitMQ 之间的通讯交互流程: -
客户端发送给服务器 Tx.Select(开启事务模式) 服务器端返回 Tx.Select-Ok(开启事务模式 ok) 推送消息
-
客户端发送给事务提交 Tx.Commit 服务器端返回 Tx.Commit-Ok
以上就完成了事务的交互流程,如果其中任意一个环节出现问题,就会抛出 IoException,这样用户就可以拦截异常进行事务回滚,或决定要不要重
复消息。
基于事物的方式是非常可靠的,但是同时他也带来了非常大的性能问题,所以一般我们不采用。
发送方确认
由于事物方式存在比较大的性能问题,所以RabbitMQ为我们提供了另外一种更好地方案,也就是发送方确认模式,该模式对性能的影响几乎可以忽略不计。
使用发布者确认的时候,生产者把信道设置为comfirm状态。生产者和RabbitMQ之间通过一个唯一的ID进行消息的确认,
对于不可路由的消息,如果发现消息不可路由之后,会判断消有没有设置mandatory,如果设置了,辉县回调ReturnListener里面的方法,然后返回ack给confirmListener
对于可以路由的消息,如果消息设置了持久化或者有高可用镜像队列,那么mq汇总消息持久化并且同步到所有的镜像之后进行确认。
发送方确认有三种方式
1、channel.waitForConfirms()普通发送方确认模式;消息到达交换器,就会返回 true。
2、channel.waitForConfirmsOrDie()批量确认模式;使用同步方式等所有的消息发送之后才会执行后面代码,只要有一个消息未到达交换器就会
抛出 IOException 异常。
3、channel.addConfirmListener()异步监听发送方确认模式;
异步确认的代码示例:
/**
* @Title: 生产者确认(只有当消息持久化之后(包括所有镜像镜像)才会被确认)
* @MethodName:
* @param
* @Return
* @Exception
* @Description: 默认都对队列和消息做持久化
* @author: jenkin
* @date: 2020-04-11 10:04
*/
@Test
public void productConfirm(){
Connection connection = Common.getConnection();
try {
//创建一个信道,用于消息通信
Channel channel = connection.createChannel();
//声明一个持久化的direct交换器
channel.exchangeDeclare( DIRECT_EXCHANGE,"direct",true);
//声明一个队列,这里持久化参数为true,如果不持久化,那么一旦mq出故障重启就会丢失队列以及队列里面的数据
//这里exclusive为false,标识这个队列可以被多个消费者消费
//autoDelete为false,如果为true的话当队列里面没有消费者之后就会自动删除,bug前提是要被消费过之后才会去判断
//arguments这是一个map,可以声明队列的过期时间,最大空间,长度,以及超时时间
channel.queueDeclare(DIRECT_QUEUE_1,true,false,false,null);
//绑定队列和交换器的关系
//这里把DIRECT_QUEUE_1队列绑定到DIRECT_EXCHANGE 交换器上面,并且使用DIRECT_QUEUE_1作为路由键发送消息
channel.queueBind(DIRECT_QUEUE_1,DIRECT_EXCHANGE,DIRECT_QUEUE_1);
//发送方确认回调
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("handleAck====》"+deliveryTag+" multiple "+multiple);
}
//只有mq内部发生错误的时候才有执行nack
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("handleNack====》"+deliveryTag);
}
});
//添加路由失败回调 发送方确认一般要结合失败者通知使用
//如果设置了 mandatory为true,那么路由失败的时候会把会回调这个方法
channel.addReturnListener((replyCode, replyText, exchange, routingKey, properties, body) -> {
System.out.println("消息:"+new String(body, StandardCharsets.UTF_8)+" 路由失败: exchange: "+exchange+" 报文:"+replyText+" 路由键: "+routingKey);
});
//开启确认
channel.confirmSelect();
//使用信道发送消息
//mandatory 这个标识如果为true,那么如果消息无法被路由的时候就会返调用basic.Return 把消息返回给生产者,
//可以添加一个ReturnListener ,在消息被返回之后会在这里进行回调
// 反之直接丢弃
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
//消息持久化
AMQP.BasicProperties persistentTextPlain = MessageProperties.PERSISTENT_TEXT_PLAIN;
channel.basicPublish(DIRECT_EXCHANGE,DIRECT_QUEUE_1,true, persistentTextPlain,("测试消息:"+i).getBytes());
}
Thread.sleep(60000);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
备用交换器
备用交换器是在第一次声明交换器时被指定,用来提供一种预先存在的交换器,如果主交换器无法路由消息,那么消息将被路由到这个新的备用交换器。
使用方式
//声明备用队列
channel.queueDeclare(FANOUT_BACK_QUEUE,true,false,false,null);
// 声明备用交换器,当主交换器无法路由,就会使用这个交换器
channel.exchangeDeclare( FANOUT_BACK_EXCHANGE,"fanout",true,false,null);
//队列绑定,匹配所有的路右键
channel.queueBind(FANOUT_BACK_QUEUE,FANOUT_BACK_EXCHANGE,"#");
//声明一个持久化的direct交换器,把备用交换器绑定到主交换器里面
Map<String,Object> argsMap = new HashMap<String,Object>();
argsMap.put("alternate-exchange",FANOUT_BACK_EXCHANGE);
//在主交换器上面设置备用交换器
channel.exchangeDeclare( DIRECT_EXCHANGE,"direct",true,false,argsMap);
当然,在声明了备用交换器之后也需要新增一个消费者去消费备用交换器队列上面的消息