一.TTL (消息的过期时间)
1.两种设置方式:
(1)在生产者中通过队列属性设置消息过期时间
所有队列中的消息超过时间未被消费时,都会过期。
Map<String, Object> argss = new HashMap<String, Object>();
argss.put("x-message-ttl",6000);
//在生产者中声明队列
channel.queueDeclare("队列名", false, false, false, argss);
(2)在生产者中消息属性设置消息的过期时间
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2) // 持久化消息
.contentEncoding("UTF-8")
.expiration("10000") // TTL
.build();
channel.basicPublish("", "队列名", properties, msg.getBytes());
如果同时指定了 Message TTL 和 Queue TTL,则小的那个时间生效。
二.死信队列
1.什么情况下消息会变成死信?
1)消息被消费者拒绝并且未设置重回队列:(NACK || Reject ) && requeue== false
2)消息过期
3)队列达到最大长度,超过了最大消息数或字节数,最先入队的消息会被发送到死信交换机。
2.死信队列如何使用?
1)生产者发送消息通过原交换机到原队列。
2)声明原队列,并指定原队列的死信交换机。队列中的过期消息,因为没有消费者,会变成死信进入到死信交换机中。
3)声明死信交换机、死信队列,并通过#绑定(无条件路由)
4)消费者监听死信队列。
生产者
String msg = "Hello world, Rabbit MQ, DLX MSG";
// 设置属性,消息10秒钟过期
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2) // 持久化消息
.contentEncoding("UTF-8")
.expiration("10000") // TTL
.build();
// 发送消息
channel.basicPublish("", "TEST_QUEUE", properties, msg.getBytes());
消费者
// 设置队列的死信交换机
Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("x-dead-letter-exchange","DEAD_EXCHANGE");
// arguments.put("x-expires",9000L); // 设置队列的TTL
// arguments.put("x-max-length", 4); // 如果设置了队列的最大长度
// 声明队列(默认交换机AMQP default,Direct)
channel.queueDeclare("TEST_QUEUE", false, false, false, arguments);
// 声明死信交换机
channel.exchangeDeclare("DEAD_EXCHANGE","topic", false, false, false, null);
// 声明死信队列
channel.queueDeclare("DEAD_QUEUE", false, false, false, null);
// 绑定,此处路由设置为 #
channel.queueBind("DEAD_QUEUE","DEAD_EXCHANGE","#");
//监听死信队列,并消费
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("Received message : '"+ new Date() + msg + "'");
}
};
channel.basicConsume("DEAD_QUEUE", true, consumer);
3.使用死信队列就可以实现消息延时需考虑到以下问题:
1)通过设置队列实现消息过期,在面对多种过期时间时需创建多个交换机
2)通过设置设置消息实现消息过期,由于队列中先进先出,可能造成消息阻塞。(前一天消息还未过期,后一条消息已过期,但由于第一条还未出队,所以无法投递)
3)可能存在一定的时间误差
如果使用场景需避免以上问题,可考虑下载延迟队列插件。
三.延迟队列
1.下载安装
1)下载地址:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases
2)将文件复制到你RabbitMQ的安装目录下的plugins文件夹下
3)在\sbin目录下运行命令,启用插件: rabbitmq-plugins enable rabbitmq_delayed_message_exchange ;停用插件:rabbitmq-plugins disable rabbitmq_delayed_message_exchange
2.使用(可类比死信队列)
1)用x-delayed-message声明延时交换机,然后声明一个队列,将队列与交换机绑定
@Bean("delayExchange")
public TopicExchange exchange() {
Map<String, Object> argss = new HashMap<String, Object>();
argss.put("x-delayed-type", "direct");
return new TopicExchange("DELAY_EXCHANGE", true, false, argss);
}
@Bean("delayQueue")
public Queue deadLetterQueue() {
return new Queue("DELAY_QUEUE", true, false, false, new HashMap<>());
}
@Bean
public Binding bindingDead(@Qualifier("delayQueue") Queue queue, @Qualifier("delayExchange") TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("#"); // 无条件路由
}
2)生产者设置消息过期,并发送到延时交换机
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());
3)消费者通过通道从延时交换机正常获取消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
System.out.println("收到消息:[" + msg + "]\n接收时间:" +sf.format(new Date()));
}
};
// 开始获取消息
// String queue, boolean autoAck, Consumer callback
channel.basicConsume("DELAY_QUEUE", true, consumer);
四.服务端流控
队列有两个控制长度的属性:x-max-length:队列中最大存储最大消息数,超过这个数量,队头的消息会被丢弃。x-max-length-bytes:队列中存储的最大消息容量,超过这个容量,队头的消息会被丢弃。通过设置队列长度在消息堆积的情况下会删除先入队的消息。但如何在消息的产生速度远大于消费速度的情况下实现服务限流呢。
1.内存控制
RabbitMQ 会在启动时检测机器的物理内存数值。默认当 MQ 占用 40% 以上内存时,MQ 会主动抛出一个内存警告并阻塞所有连接。可以通过修改rabbitmq.config 文件来调整内存阈值,默认值是 0.4,如下所示: {vm_memory_high_watermark, 0.4}。如果设置成0,则所有消息都不能发布。
2.磁盘控制
通过磁盘来控制消息的发布。当磁盘空间低于指定的值时(默认50MB),触发流控措施。
disk_free_limit.relative = 3.0
disk_free_limit.absolute = 2GB
五.消费端限流
默认情况下,如果不进行配置,RabbitMQ会尽可能快速地把队列中的消息发送消费者,在消费者处理消息的能力有限:消费者数量太少,或者单条消息的处理时间过长。我们需要在一定数量的消息消费完之前,不再推送消息过来,就要对消费端进行流量限制措施,设置消费端最大的可缓存消息数目,超过这个数值的消息不被确认,RabbitMQ会停止投递新的消息给消费者。
第一种:基于channel设置
channel.basicQos(2); // 如果超过 2 条消息没有发送 ACK,当前消费者不再接受队列消息
channel.basicConsume(QUEUE_NAME, false, consumer);
第二种:Spring Boot 配置:
spring.rabbitmq.listener.simple.prefetch=2