RabbitMQ学习_day2

消息可靠投递

要实现消息的可靠投递,也即生产者发送的消息,成功交给了交换机,然后交换机成功将消息发送给了队列。
因此要保证这2个过程成功实现,就有了以下方式来实现:

  • 消息确认: 这个是用于生产者发送消息的时候,进行消息确认,判断消息是否可以到达交换机的,所以对应的步骤如下所示:
    ① 在spring-rabbitmq-producer.xml配置文件中定义ConnectionFactory这个bean的时候,配置属性publisher-confirm的值为true,从而开启消息确认模式
    ② 在测试类中通过rabbitTemplate调用setConfirmCallback,从而设置确认回调,如果交换机没有成功收到来自生产者发送的消息,就会执行ConfirmCallback中confirm方法的代码
    ③测试类中通过生产者发送消息

  • 消息回退:当交换机无法将消息发送给队列的时候,比如routingKey不存在的时候,这时候RabbitMQ对消息有2种处理方式:(1) 因为没有队列接收消息,所以默认会丢弃这个消息 (2)开启消息回退模式,告知生产者消息无法接收的原因,然后生产者就重新发送消息。
    所以我们要实现消息回退,对应的步骤为:
    ①在spring-rabbitmq-producer.xml中定义ConnectionFactory这个bean的时候,配置属性publisher-returns=true,从而开启消息回退模式
    ②测试类中通过rabbitTemplate调用setMandatory(true)方法,从而实现在交换机没有办法将消息发送给队列的时候,就会执行ReturnCallback中的returnedMessage方法,否则如果没有这一步,那么即使设置了第③步,在交换机没有办法将消息发送给队列的时候,也不会执行ReturnCallback中returnedMessage方法,而是默认将消息丢弃。
    ③通过rabbitTemplate调用setReturnCallback方法,设置ReturnCallback
    ④发送消息

所以开启消息确认以及消息回退的测试类代码为:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class SpringProducerTest {
    
    
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 确认模式:
     * 1、在spring-rabbitmq-producer.xml中创建ConnectionFactory的时候,将
     * 属性publisher-confirms=true,从而开启确认模式
     * 2、创建回调,如果交换机没有接收到消息,就会执行这个回调,通过rabbitTemplate
     * 调用方法setConfirm即可
     * 3、发布消息
     */
    @Test
    public void testConfirm(){
    
    
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){
    
    
            /**
             *
             * @param correlationData 相关的配置信息
             * @param ack 交换机是否接收到了信息,如果true,表示接收到,否则没有接收到
             * @param cause 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    
    
                System.out.println("confirm执行中...........");
                System.out.println(correlationData);
                if(ack){
    
    
                    //接收信息成功,那么cause为null
                    System.out.println("交换机接收信息成功" + cause);
                }else{
    
    
                    //接收信息失败
                    System.out.println("交换机接收信息失败,失败原因: " + cause);
                }
            }
        });
        //交换机不存在,因此生产者无法将消息发送给交换机,因此执行ConfirmCallback中的confirm方法
        rabbitTemplate.convertAndSend("test_confirm_exchange11", "confirm", "开启确认模式~~~~");
    }

    /**
     * 回退模式:
     * 1、在spring-rabbitmq-producer.xml中创建ConnectionFactory的时候,
     * 属性publisher-returns=true,从而开启回退模式
     * 2、设置回退回调,通过RabbitTemplate调用setReturnCallback来执行
     * 但是即使队列没有接收到消息,也不会执行上面ReturnCallback的方法,因为
     * 对消息的处理有2种方式:
     *    -- 队列没有接收到消息的时候,就会丢弃这个消息(这个是默认的方式)
     *    -- 通过rabbitTeamplate调用setMandatory(true),从而在队列没有接收到
     *    消息的时候,就执行ReturnsCallback
     */
    @Test
    public void testReturns(){
    
    
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback(){
    
    
            /**
             *
             * @param message 发送的消息
             * @param replyCode  错误码
             * @param replyText 错误信息
             * @param exchange 交换机
             * @param routingKey 路由key
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
    
    
                System.out.println("return执行中...............");
                System.out.println(replyCode);
                System.out.println(replyText);
                System.out.println(exchange);
                System.out.println(routingKey);
            }
        });
        //这时候的routing Key是错误的,所以交换机没有办法将消息发送给队列,因此
        //执行ReturnCallback中的returnedMessage代码
        rabbitTemplate.convertAndSend("test_confirm_exchange", "confirm11", "returnCallback~~~~");
    }
}

对应的spring-rabbitmq-producer.xml代码为:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:rabbit="http://www.springframework.org/schema/rabbit"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
      https://www.springframework.org/schema/context/spring-context.xsd
      http://www.springframework.org/schema/rabbit
      http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
   <!--加载配置文件-->
   <context:property-placeholder location="classpath:rabbitmq.properties"/>

   <!-- 定义rabbitmq connectionFactory
        publisher-confirms表示是否开启确认模式,默认情况下为false
        publisher-returns 表示是否开启回退模式,默认情况为false,如果设置为true,
        那么在队列没有接收到信息,就会有2中处理方式,丢弃这个消息,或者执行回退
        操作,将错误信息告诉发送方
    -->
   <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                              port="${rabbitmq.port}"
                              username="${rabbitmq.username}"
                              password="${rabbitmq.password}"
                              virtual-host="${rabbitmq.virtual-host}"
                              publisher-confirms="true"
                              publisher-returns="true"
   />
   <!--定义管理交换机、队列-->
   <rabbit:admin connection-factory="connectionFactory"/>
   <rabbit:queue id="test_confirm_queue" name="test_confirm_queue" auto-declare="true"/>
   <rabbit:direct-exchange id="directExchange" name="test_confirm_exchange">
       <rabbit:bindings>
           <rabbit:binding key="confirm" queue="test_confirm_queue"></rabbit:binding>
       </rabbit:bindings>
   </rabbit:direct-exchange>
   <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
   <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

Consumer ACK

消费者确认主要有以下几种方式:
①自动确认,配置listener-container中的属性acknoledge=none,那么就是自动确认,即当消费者接收到消息之后,不管之后业务是否执行成功,都会向mq发送确认。acknoledge默认值为none,即默认是自动确认的
②手动确认,配置listener-container中的属性acknowledge=manual,那么就是手动确认,即当消费者接收到消息之后,执行业务,如果执行业务成功,将通过channel调用basicAck发送确认,否则如果执行业务失败,那么就会通过channel调用basickNack或者basicReject来拒绝签收该消息,也即这个消息没有被确认.

所以要想实现手动确认,对应的步骤:
①在spring-rabbitmq-consumer.xml中定义listener-container这个bean对象的时候,配置属性acknowledge=manual,从而开启手动确认
②定义监听器AckListener,使得这个类实现接口ChannelAwareMessageListener,然后重写方法onMessage(Message,Channel),从而一旦收到了消息,就会执行这个方法,从而执行业务逻辑,如果执行业务成功,通过Channel调用basicAck来发送确认,否则执行业务失败,那么通过Channel调用basicNack或者basicReject拒绝签收消息.
对应的spring-rabbitmq-consumer.xml代码为:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory
         publisher-confirms表示是否开启确认模式,默认情况下为false
         publisher-returns 表示是否开启回退模式,默认情况为false,如果设置为true,
         那么在队列没有接收到信息,就会有2中处理方式,丢弃这个消息,或者执行回退
         操作,将错误信息告诉发送方
     -->
    <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.example.listener"/>
    <!--配置属性acknowledge为manual,从而设置手动确认,默认值为none,表示自动确认
    -->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
        <rabbit:listener ref="ackListener" queue-names="test_confirm_queue"/>
    </rabbit:listener-container>
    <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

对应的监听器AckListener代码为:

/**
 * 消费者的确认
 * 1、在定义listener-container时,设置属性acknowledge=manual,表示手动确认
 * 默认为none,表示自动确认,即一旦消费者接收到消息之后,就会确认消息,
 * 而不管业务逻辑是否执行成功
 * 2、定义监听器AckListener,使其实现接口ChannelAwareMessageListener,重写
 * 方法onMessage(Message, Channel),通过channel调用basicAck方法来手动确认
 * 如果业务逻辑执行失败,那么需要通过channel调用basicNack或者basicReject方法来
 * 说明这个消息确认失败
 */
@Component
public class AckListener implements ChannelAwareMessageListener {
    
    
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
    
    
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        Thread.sleep(1000);
        //1、获取消息体
        System.out.println(new String(message.getBody()));
        try{
    
    
            //2、业务执行
            System.out.println("业务执行..........");
            int a = 5 / 0;
            //3、业务执行成功,那么通过channel调用basicAck手动确认
            //第二个参数multiple: 表示是否可以签收多条消息,发送多条确认
            channel.basicAck(deliveryTag, true);
        }catch (Exception e){
    
    
            //4、业务执行失败,那么不能发送确认
            /*
            第二个参数: multiple: 表示是否拒绝签收多条消息
            第三个参数: requeue: 表示如果消息没有确认,这条消息是否重回队列
            然后重新发送给消费者
             */
            System.out.println("业务失败...........");
            channel.basicNack(deliveryTag, true, true);
        }

    }
}

消费者限流

要实现消费者限流,也即消费者每次能够从mq中取出多少条消息,那么对应的步骤为:
①开启手动确认模式
②在spring-rabbitmq-consumer.xml中定义listener-container这个bean的时候,配置属性prefetch=“k”,表示消费者每次从mq中取出k条消息,只有这k条消息都收到了确认之后,消费者才可以继续接收消息
③定义监听器QosListener,使得这个类实现接口ChannelAwareMessageListener,然后重写方法onMessage(Message,Channel),从而一旦收到了消息,就会执行这个方法,从而执行业务逻辑,如果执行业务成功,通过Channel调用basicAck来发送确认,否则执行业务失败,那么通过Channel调用basicNack或者basicReject拒绝签收消息.
对应的spring-rabbitmq-consumer.xml代码为:

扫描二维码关注公众号,回复: 14703589 查看本文章
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory
         publisher-confirms表示是否开启确认模式,默认情况下为false
         publisher-returns 表示是否开启回退模式,默认情况为false,如果设置为true,
         那么在队列没有接收到信息,就会有2中处理方式,丢弃这个消息,或者执行回退
         操作,将错误信息告诉发送方
     -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
    />
    <!--扫描包com.example.listener,如果使用了注解@Component等,那么说明是个bean对象-->
    <context:component-scan base-package="com.example.listener"/>
    <!--配置属性acknowledge为manual,从而设置手动确认,默认值为none,表示自动确认
    prefetch表示消费者每次从mq中取出消息条数,并且只有消息被确认之后,才可以继续接收
    其他的消息
    -->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1">
        <rabbit:listener ref="qosListener" queue-names="test_confirm_queue"/>
    </rabbit:listener-container>
    <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

对应的QosListener代码为:

/**
 * 消费者限流机制:每个消费者一次可以从mq中取出多少条消息
 * 1、开启手动确认模式,在listener-container中配置属性acknowledge=manual,从而
 * 开启手动确认模式
 * 2、配置属性prefetch=1,表示每次消费者从mq中取出1条消息,并且只有当这一条消息
 * 被确认之后,才可以继续从mq中接收消息
 */
@Component
public class QosListener implements ChannelAwareMessageListener {
    
    
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
    
    
        Thread.sleep(1000);
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println(new String(message.getBody()));
        //channel.basicAck(deliveryTag, true);如果没有发送确认,那么接收了k条消息之后,没有办法再接收消息,因为接收到的k条消息没有发送确认
    }
}

消息过期时间TTL

消息过期主要有2种方式:
①设置队列的消息过期时间,那么这时候如果过期时间一旦到达,那么在这个队列的消息需要全部移除。要实现这种目的,只需要在spring-rabbitmq-producer.xml中定义queue的时候,设置它的参数x-message-ttl的值,单位是毫秒,类型是数值类型,否则就会容易发生类型转换错误。对应的代码为:

<rabbit:queue id="test_queue_ttl" name="test_queue_ttl">
        <rabbit:queue-arguments>
            <!--因为x-message-ttl的类型是Number,所以需要设置类型,否则就会出现类型转换异常
            x-message-ttl表示的是队列中的消息的过期时间,一旦过期,就会将这个队列中的所有消息移除
            -->
            <entry key="x-message-ttl" value="100000" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <rabbit:topic-exchange name="test_exchange_ttl" id="test_exchange_ttl">
        <rabbit:bindings>
            <rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

②单独设置某一条消息的过期时间,要想实现这个需求,那么我们在通过rabbitTemplate调用convertAndSend方法的时候,再传入一个参数MessagePostProcessor,通过这个参数来设置消息的过期时间即可。对应的代码为:

/**
 * 消息的过期时间可以由2种方式:
 * 1、队列设置过期时间,一旦过期,那么就会将这个队列
 * 种的所有消息移除,此时要实现这个目的,对应的步骤为:
 *    在spring-rabbitmq-producer.xml中定义queue的时候,通过
 *    设置它的参数rabbit:arguments中的x-message-ttl的值为特定的过期
 *    时间,单位为毫秒,但是要指定类型为Number类型,否则字符串类型的话就会发生报错
 * 2、单独设置某一条消息过期
 *    在通过rabbitTemplate调用convertAndSend的时候,传递参数MessagePostProcessor,
 *    表示消息后处理器,在这个参数中重写方法postProcessMessage,通过Message.getMessageProperties()
 *    调用方法setExpiration来设置过期时间
 *
 * 此时如果队列设置过期时间, 以及某一条消息也设置了过期时间,那么就会这一条消息
 * 的过期时间是以时间短的为准。
 *
 * 对过期的消息处理方式:
 * 如果是通过队列来指定过期时间,那么处在这个队列中的消息将会被全部移除
 *
 * 如果是某一条消息单独设置过期时间,那么当这条消息到达了过期时间,并不
 * 会立刻遍历所处队列,然后将这个消息移除,而是在这一条消息即将被消费者消费时
 * 判断这条消息是否已经过期,如果过期则将其移除,否则正常被消费者消费
 */
@Test
public void testTtl() {
    
    
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
    
    
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
    
    
            //调用getMessageProperties()调用setExpiration来设置过期时间
            message.getMessageProperties().setExpiration("5000");
            return message;
        }
    };
   for (int i = 1; i <= 10; ++i) {
    
    
        if (i == 5) {
    
    
           //第5条消息设置了过期时间为5秒
            rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.msg", "hello ttl", messagePostProcessor);
        } else {
    
    
          /*其余的消息虽然没有设置了过期时间,但是队列有设置了过期时间为100
          秒,当过期时间到达的时候,就会将这个队列中的所有消息移除
          */
            rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.msg", "hello ttl");
        }

    }
}

如果消息设置了过期时间,而这个消息所处的队列也设置了过期时间,那么这条消息的过期时间将以时间短的为准

而采用不同的方式设置TTL,对消息的处理是不一样的。如果通过队列设置过期时间,那么一旦过期时间到达,就会将队列中的所有消息移除。而通过单独设置某一条消息的过期时间,那么虽然这条消息的过期时间已经到达,但是不会立刻移除,而是在这条消息在队列头(即将被消费者消费的时候),判断这条消息是否已经过期,如果过期,那么就会将其移除,否则正常被消费者消费

猜你喜欢

转载自blog.csdn.net/weixin_46544385/article/details/128665119