一、rabbitMQ消息可靠性

消息可靠性控制主要分为两个方面,一个是在生产端的消息控制,另一方面是消费端的消息接收确认。
1、生产端消息确认机制

消息发送有两种方式控制消息的投递可靠性模式

confirm 确认模式:此模式是消息从 producer 到 exchange 的过程控制,并会返回一个 confirmCallback
return  退回模式:此模式是消息从 exchange 到 queue 的过程控制,并会返回一个 confirmCallback

(1)confirm 确认模式
开启confirm确认模式需要以下几个步骤:

1、设置ConnectionFactory的publisher-confirms="true" 开启 确认模式
2、使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发送失败,需要处理。

代码如下:

开启确认模式:springboot中设置为

publisher-confirm-type: simple
@Configuration
public class RabbitConfig {
    private static final String EXCHANGE_NAME = "exchange_confirm";
    private static final String QUEUE_NAME = "queue_confirm";

    @Bean(QUEUE_NAME)
    public Queue queue() {
        return QueueBuilder.durable(QUEUE_NAME).build();
    }

    @Bean(EXCHANGE_NAME)
    public Exchange exchange() {
        return ExchangeBuilder.directExchange(EXCHANGE_NAME).durable(true).build();
    }
    @Bean
    public Binding binding(@Qualifier(QUEUE_NAME)Queue queue,
                           @Qualifier(EXCHANGE_NAME)Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("info").noargs();
    }
}
@SpringBootTest
class RabbitmqProviderApplicationTests {

	@Autowired
	RabbitTemplate rabbitTemplate;
	@Test
	void contextLoads() {
		rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
			@Override
			public void confirm(CorrelationData correlationData, boolean b, String s) {
				if (b){
					System.out.println("消息发送成功");
				}else{
					System.out.println("消息发送失败");
				}
			}
		});
		rabbitTemplate.convertAndSend("exchange_confirm","info","无内鬼,可以交易");
	}
参数解析
confirm(CorrelationData correlationData, boolean ack, String cause):
1、CorrelationData correlationData :相关配置信息
2、boolean ack :是否成功收到了消息。true 成功,false代表失败
3、String cause  :失败原因

(2)、return 回退模式
当消息发送给Exchange后,Exchange路由到Queue失败时才会执行 ReturnCallBack开启回退模式需要以下步骤:

1、设定参数:publisher-returns="true"
 2. 设置ReturnCallBack
 3. 设置Exchange处理消息的模式:
 		 (1). 如果消息没有路由到Queue,则丢弃消息(默认)
		 (2). 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack(需要通过设置`setMandatory`方法实现)

代码如下:
@Test
	void contextLoadReturn() {
			setMandatory必须设置,否则默认失败默认抛弃消息,不会返回
		rabbitTemplate.setMandatory(true);
		rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
			@Override
			public void returnedMessage(ReturnedMessage returnedMessage) {
				System.out.println(returnedMessage.getMessage());
				System.out.println(returnedMessage.getReplyCode());
				System.out.println(returnedMessage.getReplyText());

			}
		});
		rabbitTemplate.convertAndSend("exchange_confirm","info11","无内鬼,可以交易");
	}

ReturnedMessage为失败消息的一个封装类

public class ReturnedMessage {
    private final Message message; //消息对象
    private final int replyCode;  //错误码
    private final String replyText; //错误信息
    private final String exchange; //交换机名称
    private final String routingKey; //路由key

2、消费端信息接收确认机制
即Consumer Ack,ack指Acknowledge,确认。确认方式包含以下三种:

acknowledge="none" //默认全部所有消息消费成功,不确认
acknowledge="aotu" //根据异常信息类型自动确认
acknowledge="munaul" //手动确认

(1)不确认、自动确认与手动确认的区别:

不确认即consumer收到队列消息后就立刻确认收到,随后消息队列清除缓存的数据,但是如果consumer在处理数据逻辑是出现错误,则无法进行第二次发送数据,存在数据丢失的风险。
	手动确认是指在consumer接收数据后并完成数据操作成功后再调用basicack方法进行确认,之后消息队列再清除数据。但是当逻辑出现问题时,可以调用basicNack方法要求queue重新发送数据,有效防止了数据丢失。
	自动确认根据是否抛出异常进行确认。

(2)手动确认开启步骤如下:

1、设置acknowledge属性,设置ack方式 none:自动确认,manual:手动确认
2、如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息
3、如果出现异常,则在catch中调用 basicNack或 basicReject,拒绝消息,让MQ重新发送消息
listener:
      simple:
        acknowledge-mode: manual
      direct:
        acknowledge-mode: manual
@Component
public class MyRabbitListener {
    
    @RabbitListener(queues = "queue_confirm")
    public void lister(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.println(new String(message.getBody()));
            int i =1/0;
            System.out.println("停止");
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            System.out.println("返回");
            channel.basicNack(deliveryTag,true,true);
        }
    }

}

3、消息可靠性保障
1、数据持久化:

exchange持久化
	queue持久化
	message持久化

2、confirm确认和consumer Ack确认
3、Broker高可用

二、高级特性
1、消息限流
RabbitMQ具有削峰填谷的作用,所以在这个过程中需要进行限流,防止消息一次性进入系统的数量超过系统负载。开启限流:

1、在<rabbit:listener-container> 中配置 prefetch属性设置消费端一次拉取消息数量
2、消费端的确认模式一定为手动确认。acknowledge="manual"

代码如下:

spring:
  rabbitmq:
    password: guest
    username: guest
    port: 5672
    virtual-host: /
    host: 192.168.183.123
    listener:
      simple:
        acknowledge-mode: manual
        prefetch: 1
      direct:
        acknowledge-mode: manual
        prefetch: 1
@Component
public class MyRabbitListenerDlx {

    @RabbitListener(queues = "queue_confirm")
    public void lister(Message message, Channel channel) throws IOException, InterruptedException {
        Thread.sleep(1500);
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.println(new String(message.getBody()));
           channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            System.out.println("返回");
            channel.basicNack(deliveryTag,true,true);
        }
    }

}

注意:此处必须使用channel.basicAck(deliveryTag,true);确认签收数据,否则在consumer收到第一次发送的信息后,queue会一直等待签收后再进行下一次发送。

2、TTL消息存活时间

TTL包含两种情况,一种是队列统一过期时间,另一种是单条消息过期时间。

(1)队列过期时间

管理界面设置方法:

rabbitmq springboot confirm 配置_消息发送


代码设置:

@Bean(QUEUE_NAME)
    public Queue queue() {
        Map<String, Object> map = new HashMap<>();
        map.put("x-message-ttl",10000);
        return new Queue(QUEUE_NAME, true, false, false, map);
    }

(2)消息过期时间

@Test
	void contextLoadTTL() {
		MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
			@Override
			public Message postProcessMessage(Message message) throws AmqpException {
				message.getMessageProperties().setExpiration("15000");
				return message;
			}
		};
		rabbitTemplate.convertAndSend("exchange_confirm","info","无内鬼,可以交易",messagePostProcessor);
	}

注意:此处必须使用channel.basicAck(deliveryTag,true);在同时设置队列过期和消息过期时,过期时间以短的为准。消息过期后,只有消息在队列顶端,才会判断其是否过期,所以单条信息过期移除其实很有可能会出现延时处理。

3、死信队列
消息成为死信有三种情况:

1. 队列消息长度到达限制;
2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
3. 原队列存在消息过期设置,消息到达超时时间未被消费;

死信队列设置流程:

设置两套队列,其中一套为死信队列,将在正常队列中添加到死信队列中,正常队列用于接收provider的消息,超过队列限制后,消息转发到死信队列,死信队列只用于供给consumer消费。

rabbitmq springboot confirm 配置_System_02

@Configuration
public class RabbitConfigDLx {
    private static final String EXCHANGE_NAME = "exchange_DLX";
    private static final String EXCHANGE_NAME1 = "exchange_DLX1";
    private static final String QUEUE_NAME = "queue_DLX";
    private static final String QUEUE_NAME1 = "queue_DLX1";
   定义正常队列,注入死信队列信息,并制定限制条件
    @Bean(QUEUE_NAME)
    public Queue queue() {
        Map<String, Object> map = new HashMap<>();
        map.put("x-message-ttl",10000);
        map.put("x-max-length",10);
        map.put("x-dead-letter-exchange",EXCHANGE_NAME1);
        map.put("x-dead-letter-routing-key","dead.#");
        return new Queue(QUEUE_NAME, true, false, false, map);
    }
    
    @Bean(EXCHANGE_NAME)
    public Exchange exchange() {
        return ExchangeBuilder.directExchange(EXCHANGE_NAME).durable(true).build();
    }
	
	 @Bean
    public Binding bindingDLX(@Qualifier(QUEUE_NAME)Queue queue,
                           @Qualifier(EXCHANGE_NAME)Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("info").noargs();
    }
    定义死信队列
    @Bean(QUEUE_NAME1)
    public Queue queue1() {
        return new Queue(QUEUE_NAME1, true, false, false, null);
    }

 
    @Bean(EXCHANGE_NAME1)
    public Exchange exchange1() {
        return ExchangeBuilder.directExchange(EXCHANGE_NAME1).durable(true).build();
    }

    @Bean
    public Binding bindingDLX1(@Qualifier(QUEUE_NAME1)Queue queue,
                           @Qualifier(EXCHANGE_NAME1)Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("dead.#").noargs();
    }

}
向正常队列发送消息
	@Test
	void contextLoadDLX() {
		for (int i = 0; i < 30; i++) {
			rabbitTemplate.convertAndSend("exchange_DLX","info","无内鬼,可以交易");
		}
	}

4.延迟队列
消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。延迟队列实现有两种方式:

1.可以使用死信队列+TTL实现。
2.使用定时器:定时器定时过短会频繁调用数据库,对性能影响较大,时间过长会出现时间误差。

5、日志与监控
RabbitMQ命令:
RabbitMQ默认日志存放路径: /var/log/rabbitmq/rabbit@xxx.log,其中xxx为主机名

查看队列
# rabbitmqctl list_queues

查看exchanges
# rabbitmqctl list_exchanges

查看用户
# rabbitmqctl list_users

查看连接
# rabbitmqctl list_connections

查看消费者信息
# rabbitmqctl list_consumers

查看环境变量
# rabbitmqctl environment

查看未被确认的队列
# rabbitmqctl list_queues  name messages_unacknowledged

查看单个队列的内存使用
# rabbitmqctl list_queues name memory

查看准备就绪的队列
# rabbitmqctl list_queues name messages_ready

6、消息追踪
(1)在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况。但是要定位这些异常,在RabbitMQ中就可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪。
firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式发送到默认的exchange上(amq.rabbitmq.trace),即会发送两遍。

(2)启动管理平台消息追踪福
启用插件:

rabbitmq-plugins enable rabbitmq_tracing

在docker镜像中,直接启用可能会报错:

[root@zs zs]# rabbitmq-plugins enable rabbitmq_tracing
bash: rabbitmq-plugins: command not found...

这种情况下需要进入容器后再启用插件

[root@zs zs]# docker exec -it d6b5a5c68b15 /bin/bash
root@my-rabbit:/# rabbitmq-plugins enable rabbitmq_tracing

rabbitmq springboot confirm 配置_ide_03


7、消息补偿

rabbitmq springboot confirm 配置_消息发送_04