死信队列
简介
死信队列用于处理处理失败或者过期的消息。可以让我们对处理失败或者过期的消息进行二次处理,比如重新发送给其他的消费者进行处理、记录日志等。这样可以提高消息的可靠性和处理的灵活性。
一般有以下几种情况会导致死信数据产生:
- 消息过期
在RabbitMQ中,我们可以对消息设置过期时间。使长时间未被消费的数据进入死信队列,减少对资源的占用。①通过x-massage-ttl
设置队列上全部消息的过期时间;②通过expirtation
对单条数据的过期时间进行精准控制。需要注意的是,如果同时设置了这两个值的话,消息过期时间会以最小值为准。 - 消息被拒绝
消费者可以通过以下代码对某条消息拒绝处理
// requeue:是否重新入队列,true:是;false:直接丢弃,相当于告诉队列可以直接删除掉
// TODO 如果只有这一个消费者,requeue 为true 的时候会造成消息重复消费
channel.basicReject(envelope.getDeliveryTag(),false);
如果requeue=false,消息会被转到与当前队列绑定的死信交换机上。
4. 队列满了
队列可以通过两个参数来设置消息容量:①x-max-length
定义了队列消息数量;②x-max-length-bytes
定义了以字节为单位的消息量。当到达设置量时,先入队列的消息会被丢弃,进入绑定的死信队列。
使用方式
死信队列和普通队列一样,都需要绑定交换机。
//死信交换机
channel.exchangeDeclare("DEAD_EXCHANGE", BuiltinExchangeType.DIRECT,true,false,null);
//死信队列名称,是否持久化,是否排他,是的自动删除,参数配置Map
channel.queueDeclare("QUESE_DEAD",true,false,false,null);
//绑定
channel.queueBind("QUESE_DEAD","DEAD_EXCHANGE","DEAD");
定义好之后,对应的普通队列要通过x-dead-letter-exchange
绑定死信交换机
Map<String,Object> props=new HashMap<>();
//死信交换机
props.put("x-dead-letter-exchange","DEAD_EXCHANGE");
//死信队列路由,可选
props.put("x-dead-letter-routing-key","DEAD");
//props.put("x-max-length","100");
//队列名称,是否持久化,是否排他,是的自动删除,参数配置Map
channel.queueDeclare("QUESE_TWO",true,false,false,props);
使用场景
RabbitMQ死信队列(Dead Letter Queue)是用于处理发生异常或无法正常处理的消息的队列。当消息无法被消费者处理时,可以将这些消息发送到死信队列中,进而进行后续处理。
以下是一些使用场景:
-
消息超时:当消息在一定时间内没有被消费者消费,可以将这些消息发送到死信队列,以便进行后续处理,例如重新发送、记录日志等。
-
消费者错误:当消费者在处理消息时发生错误,无法正常处理消息,可以将这些消息发送到死信队列,以便进行后续的问题排查和处理。
-
消息重试次数达到上限:当消息在经过一定次数的重试后仍然无法被消费者处理,可以将这些消息发送到死信队列,并记录相关信息,例如错误原因、重试次数等。
-
消息过期:当消息的过期时间到达,但仍然没有被消费者处理,可以将这些消息发送到死信队列,以便进行后续处理,例如进行回滚操作、发送通知等。
通过使用死信队列,可以提高消息处理的可靠性和可恢复性,对于一些异常情况和问题处理起到了重要作用。
延迟队列
延迟队列用于延迟处理消息。通过使用延迟队列,可以实现一些延迟处理的功能,例如延迟任务、消息重试等。延迟队列可以提高系统的容错性和可靠性,并且能够避免瞬时高峰对系统的影响。
与死信队列不同,RabbitMQ本身不支持延迟队列,需要通过插件进行启用。
- 下载插件
wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/v3.12.0/rabbitmq_delayed_message_exchange-3.12.0.ez
- 在服务端执行
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
- 重启rabbitmq
systemctl restart rabbitmq-server
这时我们再登录rabbit管理界面可以看的Exchange类型中出现了一个x-delayed-massage
。
- 创建延迟交换机及队列
// 声明x-delayed-message类型的exchange
Map<String, Object> argss = new HashMap<String, Object>();
argss.put("x-delayed-type", "direct");
channel.exchangeDeclare("DELAY_EXCHANGE", "x-delayed-message", false, false, argss);
// 声明队列
channel.queueDeclare("DELAY_QUEUE", false,false,false,null);
// 绑定交换机与队列
channel.queueBind("DELAY_QUEUE", "DELAY_EXCHANGE", "DELAY_KEY");
在声明交换机时,我们将x-delayed-type
参数设置为direct。
它的作用是告诉交换器在路由消息、创建绑定等时我们希望它具有哪种行为。也可以设置为topic, fanout,或者其他插件提供的自定义交换类型。
- 发送延迟消息
// 延时投递,比如延时1分钟
Date now = new Date();
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, +1);// 1分钟后投递
Date delayTime = calendar.getTime();
// 定时投递,把这个值替换delayTime即可
// Date exactDealyTime = new Date("2024/02/26,12:30:00");
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String msg = "发送时间:" + sf.format(now) + ",需要投递时间:" + sf.format(delayTime);
// 延迟的间隔时间,目标时刻减去当前时刻
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("x-delay", delayTime.getTime() - now.getTime());
AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder()
.headers(headers);
channel.basicPublish("DELAY_EXCHANGE", "DELAY_KEY", props.build(),
msg.getBytes());
这里要注意,x-delay
的参数是目标时刻减去当前时刻。
流量控制
RabbitMQ 作为一个高性能的消息中间件,具有强大的消息处理能力。然而,如果生成者发送过快,消费者无法及时处理或者消费速度过慢,就会导致消息堆积和系统性能下降,这时,我们就需要通过浏览控制来进行限制消息生产速度或消费者消费速度,平衡系统负载、避免资源浪费,防止消费者被过多消息压垮,导致消费端的性能下降或宕机。
在死信队列章节,我们了解到,x-max-length
和x-max-length-bytes
可以控制消息队列的大小,但需要注意的是,这两个参数设置的结果是数据量到达设定值后,先入队的数据会被移除,从一定程度上来说,并不满足流量控制的目的。
因此我们可以通过disk_free_limit
限制磁盘空间(以字节为单位)。一旦可用磁盘空间达到这个下限,就会触发磁盘警报。如果定义了相对占比(relative),绝对设置将被忽略。
如:disk_free_limit.absolute = 50000 磁盘下限绝对值(3.6以上版本支持使用内存单位);或disk_free_limit.relative = 0.3。
或者,通过vm_memory_high_watermark
设置内存的上限阈值,当RabbitMQ使用的内存到达阈值,RabbitMQ会将内存中的消息写入到磁盘或根据配置的策略,丢弃一些消息,来释放内存空间。与disk_free_limit
一样,也可以设置相对占比和绝对值。
vm_memory_high_watermark:内存的上限阈值
disk_free_limit:磁盘空间最低限值
设置方式
-
进入配置文件所在目录
-
进入编辑模式,输入并保存以下内容
-
效果
从管理界面,我们可以明显看到Mermory和Disk spac下面水位值的变化
before:
after:
通过上面两个参数,我们对服务端进行了流量控制。而当消息过多,消费者处理不过来时,我们可以通过①增加消费者;②进行设置,让消费者在未处理完一定量消息时不再接收消息。
具体方式是通过设置prefetch count来限制消息的发送。消费者在获得prefetchCount条数据后,如果仍没有向Broker返回确认处理,服务端将不再发送消息给消费者。
在RabbitMQ中,默认的prefetch count是0,表示不进行任何限制,消费者可以一次性获取所有的消息。可以通过设置channel.basicQos方法来手动指定prefetch count的值,例如:
channel.basicQos(prefetchCount);
需要注意的一点是,这个方法,需要消费者应答方式为手工应答。