RabbitMQ进阶知识

RabbitMQ进阶知识


1、消费端的确认

 channel.basicConsume(QUEUE_NAME,false,consumer);

将autoAck设置为false,当消费端收到消息之后,队列不会立即删除这条消息,只有当消费端进行手动确认后才会进行删除即调用:这样可以允许一条消息的处理时间可以很长。

 channel.basicAck(envelope.getDeliveryTag(),true);

当消费者设置为手动签收,但在处理消息的过程中,连接断开了,此时这些消息会发送给其它客户端。

2、消费端的拒绝

当消费端接收到消息后,允许拒绝这条消息,并且可以设置被拒绝的消息是否重新进入队列中。

void basicReject(long deliveryTag, boolean requeue) throws IOException;

若requeue设置为false,则这条消息会被删除,若为true,则不会被删除,且会再次发过来。

void basicNack(long deliveryTag, boolean multiple, boolean requeue)

multiple 设置为true进行批量拒绝。

注:当requeue为false的时候,这些消息会进入死信队列。

3、消息何去何从 mandatory参数

void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)

生产者发送消息的时候,设置mandatory为true,当消息发送给交换机,但没有合适的队列接收,此时在生产者端注册一个监听器,可以接收到发送失败的消息。

channel.addReturnListener(new ReturnListener() {
    
    
    @Override
    public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) {
    
    
        System.out.println("replyCode :" + replyCode);
        System.out.println("replyText :" + replyText);
        System.out.println("exchange :" + exchange);
        System.out.println("routingKey :" + routingKey);
        System.out.println("properties :" + properties);
        System.out.println("body :" + new String(body));
    }
});

当mandatory为false的时候,无法路由的消息会被直接丢弃。

4、备份交换器

为一个交换器,绑定一个备胎交换器,当发送到第一个交换器的消息因为rountingKey原因无法路由的时候,就会发送到备份交换器,这条消息的rountingKey保持不变。消息被发送到备份交换器和生产者将消息直接发送到备份交换器的本质是一样的,当备份交换器和mandatory共存时,备份交换器优先级更高。

// 声明备份交换器
channel.exchangeDeclare("beifen","fanout",true,true,false,null);
channel.queueDeclare("beifen",false,false,false,null);
channel.queueBind("beifen","beifen","abc");
Map<String,Object> map = new HashMap<>();
map.put("alternate-exchange","beifen");
// 绑定备份交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, true,false,map);
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "first");

5、消息的过期时间TTL

可以为消息指定一个过期时间,将消息发送到队列中后,经过过期时间,还没有被消费则这条消息就会成为一条过期消息,有两种处理方式:没有死信队列的话,直接丢弃,有死信队列的话,该消息会进入死信队列中。

AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
    .deliveryMode(2)
    .contentEncoding("UTF-8")
    .expiration("10000")  // ms ---> 10s
    .build();

channel.basicPublish(EXCHANGE_NAME,"ttl",properties,message.getBytes());

过期时间的单位为ms。

也可以为整个队列设置过期时间,这样队列中的所有消息的过期时间都是一样的,两者共存时,哪个短哪个优先级高。

为队列设置过期时间,时间到了,消息会立即被抹去,但为消息单独设置过期时间,即使消息到了也不会被抹去,这个被抹去的时机是在消费者进行消费的时候进行判断的,若过期抹除,未过期继续消费。

Map<String,Object> arguments = new HashMap<>();
// 队列设置过期时间,队列中所有消息的过期时间
arguments.put("x-message-ttl",5000);
channel.queueDeclare(QUEUE_NAME,false,false,false,arguments);

TTL = 0 表示要么可以直接投递给消费者,要么直接过期。

6、死信队列

当消息成为死信消息之后就会被发送到死信交换器,进而被发送到与死信交换器绑定的死信队列中。

一个消息成为死信消息有以下几种情况:

  • 消息过期
  • 队列达到最大长度
  • 消息被拒绝且不重新入队
arguments.put("x-dead-letter-exchange", "dead_exchange");
// 死信
channel.queueDeclare("dead_queue", false, false, false, null);
channel.exchangeDeclare("dead_exchange", "fanout", false, false, null);
channel.queueBind("dead_queue","dead_exchange","ttl");
// 业务队列与死信交换机相关联在一起
channel.queueDeclare("work_queue", false, false, false, arguments);
channel.exchangeDeclare("work_exchage", BuiltinExchangeType.DIRECT, false, false, null);
channel.queueBind("work_queue", "work_exchage", "ttl");
// 为该条消息设置过期时间

7、延迟队列

延迟队列中存储的是延迟消息,延迟消息是指消息被发送后,并不是立即被消费,而是等待特定时机,消费者才拿到这个消息进行消费。

使用死信队列+ TTL来实现延迟队列的功能,消费者此时订阅的是死信队列。

8、持久化

RabbitMQ中可以设置交换器,队列、消息的持久化。

消息自身的持久化需要依赖队列,因为消息在队列中存着,唯有消息和队列都设置持久化,RabbitMQ重启后,消息才不会丢失。

消息的持久化会消耗性能,需要在可靠性和吞吐量之间做一个权衡。

9、事务机制

当消息发送出去后,还没有到达队列,Broker就宕机了此时即使有持久化机制,这条消息都会丢失,为了解决这个问题,引入事务机制,发送之前开启事务,然后发送消息,发送之后提交事务,如果发生异常可以进行回滚。事务机制非常消耗RabbitMQ的性能,因此又提出了发送方确认机制。为什么会慢呢?因为事务是同步机制,消息发送过后,唯有等到消息被确认才可以继续发送下一条。

10,发送端确认机制

基于事务的性能问题,RabbitMQ团队为我们拿出了更好的方案,即采用**发送方确认模式,**该模式比事务更轻量,性能影响几乎可以忽略不计。

原理:生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的id开始从1 开始。

可路由的消息,要等到消息被投递到对应的队列后,broker会发送一个确认给生产者,这就使得生产者知道消息已经发送到正确的目的队列了。如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker回传给生产者的确认消息中delivery-tag域包含了确认消息的序列号。

如果RabbitMQ因为自身内部错误导致消息丢失【消息发送不到交换器】,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息决定下一步的处理。

确认机制一般和mandatory搭配使用,前者指的是发送不到交换机通知失败,后者指的是发送不到队列通知失败。

11、消息分发

当RabbitMQ队列有多个消费者的时候,默认采用轮询的方式进行消息分发,但由于机器性能及消息本身等原因,可能导致某些消费者空闲,某些消费者负载过重。才是需要使用到channel的basicQos方法进行限制。设置一个上限,当发送的消息达到上限后,不会再发送消息给这个消费者,唯有当消费者对之前的消息进行确认之后,才会接着发送。

channel.basicQos(0,2,false);
com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
    
    
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
                               AMQP.BasicProperties properties, byte[] body) {
    
    
        String msg = new String(body, "UTF-8");
        System.out.println("Received message : '" + msg + "'");
        channel.basicAck(envelope.getDeliveryTag(),false);
    }
};
channel.basicConsume(QUEUE_NAME,false,consumer);

12、消息的顺序性

RabbitMQ不能保证消息的顺序消费。哪些情况会导致消息乱序呢??

  • 事务机制,重发导致乱序
  • confirm机制,重发导致乱序
  • 消息的优先级
  • 不同过期时间的消息
  • 消费端设置requeue

可以为消息设置一个序列号来解决这个问题,和业务强相关。

13、消息传输保障

  • 消息成功发到队列
  • 消费端成功消费

为了保证消息的可靠性传输,发送端,开启失败通知Confirm,mandatory这些参数,开启消息的持久化机制,消费端,开启自动确认机制。这样虽然极大概率保证消息的可靠性传输,但会引起消息的重传问题,进而导致重复消费,此时需要考虑消费端的幂等性。

猜你喜欢

转载自blog.csdn.net/weixin_43213517/article/details/106409223