死信队列
- 死信队列是RabbitMq中非常重要的一个特性,简单来说,他是对RabbitMQ未能正常消费的一种补偿机制。死信队列也是一个普通的队列,同样可以在队列上声明消费者,继续对消息进行消费处理。
- 主要配置参数
// 对应的死信交换机 x-dead-letter-exchange: mirror.dlExchange // 对应的路由Key x-dead-letter-routing-key //消息过期时间 x-message-ttl
- 何时会产生死信队列
- 消息被消费者确认拒绝,消费者把requeue参数设置为true(false),并且在消费后,向RabbitMQ返回拒绝。channel.basicReject或者channel.basicNack。
- 消息达到设置的TTL过期时间
- 消息队列达到最长被丢弃的消息
设置TTL过期的两种方式:
- 可以在申明队列的时候设置时间,也可以在发送消息的时候设置
- 通过配置策略制定
rabbitmqctl set_policy TTL ".*" '{"message-ttl":60000}' --apply- to queues
- 代码配置,在业务不是很大的情况下,我觉得在代码配置更利于阅读开发
@Bean
Queue declareTTLQueue() {
return new Queue("test.ttl.001", true, false, false, Map.of("x-message-ttl", 6000));
}
配置死信队列:实现延迟队列或补偿机制:
使用说明:声明一个死信队列(和普通队列没啥区别),在声明一个正常消费队列,此时通过可配置参数将死掉的消息转发到那个交换机上同时设置上RoutingKey值,再监听死信队列配置消费者,进行业务处理
@Bean
Queue declareTTLQueue() {
// Queue build = QueueBuilder.durable("test.ttl.001").ttl(6000).deadLetterExchange(TEST_EXCHANGE).deadLetterRoutingKey(TEST_DEAD_LETTER_ROUTE).build();
return new Queue("test.ttl.001", true, false, false, Map.of("x-message-ttl", 6000,
"x-dead-letter-exchange", TEST_EXCHANGE, "x-dead-letter-routing-key", TEST_DEAD_LETTER_ROUTE));
}
/**
* 配置死信队列
*/
@Bean
public void declareDeadLetterQueue() {
Exchange testExchange = ExchangeBuilder.directExchange(TEST_EXCHANGE).build();
amqpAdmin.declareExchange(testExchange);
Queue testQueue = QueueBuilder.durable(TEST_DEAD_LETTER_QUEUE).build();
amqpAdmin.declareQueue(testQueue);
amqpAdmin.declareBinding(BindingBuilder.bind(testQueue).to(testExchange).with(TEST_DEAD_LETTER_ROUTE).noargs());
}
如何确定一个消息是否是死信消息:
- 消息被作为死信转移到死信队列后,会在Header当中增加一些消息。在官网的详细介绍中,可以看到很多内容,比如时间、原因(rejected,expired,maxlen)、队列等。然后header中还会加上第一次成为死信的三个属性,并且这三个属性在以后的传递过程中都不会更改。
- x-first-death-reason
- x-first-death-queue
- x-first-death-exchange
Map<String, Object> headers = properties.getHeaders(); headers.forEach((key, value) -> System.out.println("header key: " + key + "; value: " + value));
通过 rabbitmq_delayed_message_exchange插件,实现延迟队列
- TTL+死信队列实现延迟队列是有局限性的,列如第一个值60秒过期,第二个值6秒,当第二个消息过期之后也不会出队。FIFO机制
- GitHub 插件地址
安装
// 查找安装位置
whereis rabbitmq
// 将插件包移动到服务 plugins 目录下,我的服务地址 /usr/lib/rabbitmq/lib/rabbitmq_server-3.9.15
// 启用延时插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
注意点:如果是集群服务,以RAM 加入的节点无法启用延时插件,安装成功后,创建Exchange时就多出了 x-delayed-message类型
代码演示:
- API 方式 -声明且发送
。
public class SendMessageDelay {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitMqUtil.getConnection();
Channel channel = connection.createChannel();
Map<String, Object> param = new HashMap<>(1);
param.put("x-delayed-type", "direct"); //声明交换机类型,并且延时
channel.exchangeDeclare("delay.exchange", "x-delayed-message",true,false,false,param);
channel.queueDeclare("delay.queue", true, false, false, null);
channel.queueBind("delay.queue", "delay.exchange", "delay");
Map<String, Object> headers = new HashMap<>(1);
headers.put("x-delay", 10000); //设置消息过期时间
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.headers(headers);
channel.basicPublish("delay.exchange", "delay", builder.build(), "这是一个集成延时插件发送的消息".getBytes(StandardCharsets.UTF_8));
channel.close();
connection.close();
}
}
- Spring boot 方式
/**
* 声明延时队列-插件版
*/
@Bean
public void declareDelayExchange() {
Exchange delayExchange = ExchangeBuilder.directExchange("test.delay").delayed().build();
amqpAdmin.declareExchange(delayExchange);
Queue testQueue = QueueBuilder.durable(TEST_DEAD_LETTER_QUEUE).build();
amqpAdmin.declareQueue(testQueue);
amqpAdmin.declareBinding(BindingBuilder.bind(testQueue).to(delayExchange)
.with(TEST_DEAD_LETTER_ROUTE).noargs());
}
/**
* 发送延时消息
*/
@Slf4j
@SpringBootTest
class WangfamilyMqApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend("test.delay", TEST_DEAD_LETTER_ROUTE, new Message("Spring boot , Hello Word".getBytes()), new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setHeader("x-delay", 6000);
return message;
}
});
}
}
/**
* 消费消息
*/
@Component
public class DelayMessageListener {
@RabbitListener(queues = TEST_DEAD_LETTER_QUEUE)
public void handle(Channel channel, String body, Message message) {
System.out.println(body);
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
try {
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
}
消费优先级与流量控制
- 消费者通过
x-priority
参数配置,来控制消息的优先消费权 - 通过
prefetch_count
参数,控制消费者未应答的消息数量,可以通过单消费者和1个未应答量来实现消息的顺序消费 - 大神文章,细节满满,值得一看
懒队列 Lazy Queue
- 懒队列会尽可能早的将消息内容保存到硬盘当中,并且只有在用户请求到时,才临时从硬盘加载到RAM内存当中。懒队列的设计目标是为了支持非常长的队列(数百万级别)。队列可能会因为一些原因变得非常长-也就是数据堆积
- 消费者服务宕机了、生产者发送消息大于消费者消费消息速度,产生数据堆积问题,影响RabbitMq性能
声明懒队列的两种方式
-
通过配置参数设置
Map<String, Object> args = new HashMap<String, Object>(); args.put("x-queue-mode", "lazy"); channel.queueDeclare("myqueue", false, false, false, args);
-
通过设置RabbitMQ 策略
rabbitmqctl set_policy Lazy "^lazy-queue$" '{"queue-mode":"default"}' --apply-to queues
注意点:懒队列适合消息量大且长期有堆积的队列,可以减少内存使用,加快消费速度。但是这是以大量消耗集群的网络及磁盘IO为代价的
消息分片存储插件-Sharding Plugin
- 通过消息分片生成伪队列消费,解决长队列问题,暂且了解,不做深入,自行探索