RabbitMQ 消息确认机制ACK

  • ack机制保证的是broker和消费者之间的可靠性
  • ack表示的是消费端收到消息后的确认方式,有三种确认方式
  • 自动确认:acknowledge="none"(默认)
  • 手动确认:acknowledge="manual"
  • 根据异常情况确认:acknowledge="auto"(这种方式使用麻烦,不作讲解)
  • 自动确认的解释
  • 当消息一旦被 Consumer 接收到,则自动确认收到,并将相应消息从 RabbitMQ 的消息缓存中移除
  • 手动确认的解释
  • 在实际业务处理中,很可能消费端收到消息后业务处理出现异常,那么该消息就会丢失
  • 如果设置了手动确认方式,则需要在业务处理成功后,调用 channel.basicAck() 手动签收;如果出现异常,则调用 channel.basicNack()方法,让 broker 自动重新发送消息给消费端

代码实现

  1. spring-rabbitmq-consumer.xml
    配置文件中的监听器容器标签中定义消息的确认方式
<!--加载配置文件-->
<!--配置文件中定义了端口、用户名、密码等信息-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>

<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                           port="${rabbitmq.port}"
                           username="${rabbitmq.username}"
                           password="${rabbitmq.password}"
                           virtual-host="${rabbitmq.virtual-host}"/>

<!--扫描监听器-->
<context:component-scan base-package="com.itheima.listener" />

<!--定义监听器容器-->

<!--acknowledge属性设置消息的确认方式,manual为手 动确认方式-->

<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
    <!--创建的监听器的类名是AckListener-->
    <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"/>
</rabbit:listener-container>
  1. 创建引导类,保证消费者可以持续监听某队列
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {
    @Test
    public void test(){
        while (true){
            
        }
    }
}
  1. 创建监听器AckListener.java
    监听器类实现 ChannelAwareMessageListener 接口,默认的自动签收是实现 MessageListener 接口
@Component
public class AckListener implements ChannelAwareMessageListener {

    //每读取一条消息就会执行一次此方法,message表示封装后的每次读取到的消息
    
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {

        // 得到消息的deliveryTag,作为参数传递到basicAck方法中
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {

            // 对message操作,处理业务逻辑

            int i = 3/0; //手动制造错误

            // 如果没有错误,则执行此步,表示成功签收
            // 手动签收,第一个参数表示消息的deliveryTag,第二个参数表示是否允许多条消息同时被签收
            channel.basicAck(deliveryTag, true);
        } catch (Exception e) {

            //执行到此步说明出现了错误,拒绝签收,重新发送消息

            /**
             * 前两个参数和basicAck方法一致
             * 第三个参数指的是requeue,如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
             * 第三个参数如果设置为false,则消息不会返回到原队列
             */
            channel.basicNack(deliveryTag, true, true);
            
            //由于消费者持续监听,且消息持续不断的重新发送,此段代码会一直循环执行,直到处理了错误
        }
    }
}