rabbitmq之死信队列的应用

如果此教程对你有帮助,有钱的捧个钱场,没钱的请捧个人场!

1、背景说明:
    开发环境:sprint boot,rabbitmq,mysql,jdk1.8...
    在预生产环境中,消费者消费消息时出现了异常,后台不断写日志,一天时间日志文件超过了30G,导致服务器因磁盘空间不足而宕机!
    
2、原因分析:
    1)rabbitmq的默认配置spring.rabbitmq.listener.default-requeue-rejected=true,消息消费失败,则重新放回队列,再次消费时依然失败,又被重新放回队列,从而陷入死循环......后台不停地写日志;
    2)消费者消费消息的程序中没有用try_catch_来捕获可能出现的异常;

3、解决方案:
    方案一:统一配置
    ## 消息消费失败,则丢弃消息
    spring.rabbitmq.listener.default-requeue-rejected=false 

    方案二:添加消息消费失败监听事件
    ListenerContainerConsumerFailedEvent

    方案三:死信队列
    Dead-Letter-Exchange

   方案四:手动确认
    autoAck = true

本文主要探讨死信队列的解决方案!

4、知识点简介

DLX, Dead-Letter-Exchange。利用DLX, 当消息在一个队列中变成死信(dead message)之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX。消息变成死信一般有一下几种情况:
1)消息被拒绝(basic.reject/ basic.nack)并且requeue=false;
2)消费消息时程序出现了异常;
3)消息过期(x-message-ttl);
4)队列中有消息数量超过了最大值(x-max-length);
5)队列中的消息容量超过了队列的最大空间(x-max-length-bytes);

其他情况,作者尚未探索,欢迎读者留言!
DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性,当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列,可以监听这个队列中消息做相应的处理,这个特性可以弥补RabbitMQ 3.0以前支持的immediate参数(可以参考RabbitMQ之mandatory和immediate)的功能。
5、案例代码

    编码步骤:
    1)创建普通交换机;
    2)创建普通队列,设置队列属性x-dead-letter-exchange,与死信交换机绑定;设置队列其他属性...;
    3)绑定普通队列到普通交换机上;
    4)创建死信交换机;
    5)创建死信队列;
    6)绑定死信队列到死信交换机上,并指定死信路由键;

生产者:

/** 生产者只需向交换机中发送消息 */
public class MessageProducer {

	/* 普通交换机 */
	private static String exchangeName = "common_exchange";

	private static void noExpirationDemo_2() {
		try {
			Connection connection = MQUtils.getConnection();
			Channel channel = connection.createChannel();
			channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, false);
			for (int i = 0; i < 10; i++) {
				String msg = "hello rabbitmq " + i;
				channel.basicPublish(exchangeName, "", null, msg.getBytes());
			}
			System.out.println("消息发布成功");
			channel.close();
			connection.close();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TimeoutException e) {
			e.printStackTrace();
		}
	}

普通消息者:

/*  普通消息者 */
public class CommonConsumer {

	/* 普通队列 */
	private static String queueName = "common_queue"; 
	/* 普通交换机 */
	private static String exchangeName = "common_exchange";
	/* 死信交换机 */
	private static String dlxExchangeName = "dlx_exchange";

	public static void main(String[] args) {
		try {
			Connection connection = MQUtils.getConnection();
			Channel channel = connection.createChannel();
			Map<String, Object> arguments = new HashMap<>();
			/* 当队列消息长度大于最大长度、或者过期的等,将从队列中删除的消息推送到指定的交换机中去而不是丢弃掉 */
			arguments.put("x-dead-letter-exchange", dlxExchangeName);
			/* 统一为整个队列的所有消息设置生命周期,注意时间值类型为int,单位为毫秒; */
//			arguments.put("x-message-ttl", 5000);
			/* 限定队列的消息数量的最大值,超过指定长度将会把最早的几条删除掉 */
//			arguments.put("x-max-length", 5);
			/* 限定队列最大占用的空间大小 */
			arguments.put("x-max-length-bytes", 5);
			channel.queueDeclare(queueName, false, false, false, arguments);
			channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, false);
			channel.queueBind(queueName, exchangeName, "");
			Consumer consumer = new DefaultConsumer(channel) {

				@Override
				public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
						byte[] body) throws IOException {
					System.out.println("common consumer receive :" + new String(body));
				}
			};
			channel.basicConsume(queueName, true,consumer);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TimeoutException e) {
			e.printStackTrace();
		}
	}
}

死信消费者:

/* 创建列信队列消费者 */
public class DlxConsumer {

	/* 死信队列 */
	private static String dlxQueueName = "dlx_queue";
	/* 死信交换机 */
	private static String dlxExchangeName = "dlx_exchange";
	/* 死信路由键 */
	private static String dlxRoutingKey = "";

	public static void main(String[] args) {
		try {
			Connection connection = MQUtils.getConnection();
			Channel channel = connection.createChannel();
			channel.queueDeclare(dlxQueueName, false, false, false, null);
			channel.exchangeDeclare(dlxExchangeName, BuiltinExchangeType.FANOUT, false);
			channel.queueBind(dlxQueueName, dlxExchangeName, dlxRoutingKey);
			Consumer consumer = new DefaultConsumer(channel) {

				@Override
				public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
						byte[] body) throws IOException {
					System.out.println("dlx consumer receive :" + new String(body));
				}
			};
			channel.basicConsume(dlxQueueName,true, consumer);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TimeoutException e) {
			e.printStackTrace();
		}
	}
}

6、结果展示:

7、在springboot demo中的代码:

    /* 配置死信队列 */
	/* 死信交换机 */
	private static String dlxExchangeName = "dlx_exchange";
	/* 死信队列 */
	private static String dlxQueueName = "dlx_queue";
	/* 死信路由键 */
	private static String dlxRoutingKey = "routingkey.dead";
    /* 1、声明死信交换机 */
	@Bean(name="dlxExchange")
    public DirectExchange dlxExchange() {
        return new DirectExchange(dlxExchangeName);
    }
    
    /* 2、声明死信队列 */
	@Bean(name="dlxQueue")
    public Queue dlxQueue() {
        return new Queue(dlxQueueName);
    }
    
    /* 3、绑定死信队列与死信交换机 */
    @Bean
    Binding bindingDlxExchangeQueue(@Qualifier("dlxQueue") Queue dlxQueue, @Qualifier("dlxExchange") DirectExchange dlxExchange) {
        return BindingBuilder.bind(dlxQueue).to(dlxExchange).with(dlxRoutingKey);
    }
    
    /* 4、修改普通队列的配置,消息消费失败后将路由到死信队列中 */
    @Bean
    public Queue queue() {
    	Map<String, Object> arguments = new HashMap<>();
    	/* 当队列消息长度大于最大长度、或者过期的等,将从队列中删除的消息推送到指定的交换机中去而不是丢弃掉 */
    	arguments.put("x-dead-letter-exchange", dlxExchangeName);
        /* 将删除的消息推送到指定的交换机对应的路由键 */
        arguments.put("x-dead-letter-routing-key", dlxRoutingKey);
    	Queue queue_ = new Queue(queue,true,false,false,arguments);    	
        return queue_;
    }
展开阅读全文

没有更多推荐了,返回首页