使用rabbitmq模拟下单后,30分钟未支付,取消订单,回滚库存。

使用rabbitmq模拟下单后,30分钟未支付,取消订单,回滚库存。

这是使用rabbitmq的延迟队列来实现。不过rabbitmq并没有延迟队列这种模式,所以,需要通过TTL+死信队列来实现。

下面,为了方便理解,首先认识一下ttl和死信队列。

下面代码都是基于springboot2.3.4.RELEASE版本,由于生产者和消费者pom.xml和配置文件相同,所以,在下面先给出pom.xml和配置文件。

pom.xml

<parent>
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-parentartifactId>
	<version>2.3.4.RELEASEversion>
parent>

<dependencies>
	<dependency>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-amqpartifactId>
	dependency>
	<dependency>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-testartifactId>
	dependency>
dependencies>

配置文件

spring:
  rabbitmq:
    host: 192.168.211.131
    port: 5672
    username: test
    password: test
    virtual-host: /test

1.TTL

首先,讲一下什么是TTL。全称:Time To Live,用于设置过期时间。

在创建队列时,如果设置了过期时间,那么到时间消息就会自动丢弃或者是发送到死信队列里。

管控台中设置队列TTL

使用rabbitmq模拟下单后,30分钟未支付,取消订单,回滚库存。_第1张图片

代码实现:只需配置生产者,因为消息会过期,所以消费者也监听不到消息

1.1生产者

springboot入口

@SpringBootApplication
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

配置rabbibtmq

@Configuration
public class RabbitmqConfig {
    /**
     * ttl交换机
     */
    public static final String TTL_EXCHANGE = "springboot_ttl_exchange";
    /**
     * ttl队列
     */
    private static final String TTL_QUEUE = "springboot_ttl_queue";

    /**
     * 声明ttl交换机
     * @return ttl交换机
     */
    @Bean
    public Exchange ttlExchange() {
        return ExchangeBuilder.topicExchange(TTL_EXCHANGE).build();
    }

    /**
     * 声明ttl队列
     * @return ttl队列
     */
    @Bean
    public Queue ttlQueue() {
        return QueueBuilder.durable(TTL_QUEUE)
                //该队列过期时间,单位毫秒
                .ttl(10000)
                .build();
    }

    /**
     * 绑定ttl队列到ttl交换机
     * @param queue ttl队列
     * @param exchange ttl交换机
     */
    @Bean
    public Binding bindingTtl(@Qualifier("ttlQueue") Queue queue,
                              @Qualifier("ttlExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("ttl.#").noargs();
    }
}

测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ProviderApplication.class)
public class RabbitmqTest {

    @Autowired
    private RabbitTemplate template;

    @Test
    public void test1() {
        template.convertAndSend(RabbitmqConfig.TTL_EXCHANGE,"ttl.test","这是ttl消息");
    }
}

测试

在这里插入图片描述

10s之后,自动丢弃

在这里插入图片描述

2.死信队列

下面介绍一下死信队列。

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。

使用rabbitmq模拟下单后,30分钟未支付,取消订单,回滚库存。_第2张图片

消息成为死信的三种情况(面试常问)

  1. 消息过期前,没有被消费
  2. 队列长度达到限制
  3. 消费者拒收【basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false】

代码实现

2.1生产者

springboot入口

@SpringBootApplication
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

rabbitmq配置类

@Configuration
public class RabbitmqConfig {
    /**
     * 正常交换机
     */
    public static final String TEST_DLX_EXCHANGE = "test_dlx_exchange";
    /**
     * 正常队列
     */
    private static final String TEST_DLX_QUEUE = "test_dlx_queue";
    /**
     * 死信交换机
     */
    public static final String DLX_EXCHANGE = "springboot_dlx_exchange";
    /**
     * 死信队列
     */
    private static final String DLX_QUEUE = "springboot_dlx_queue";

    /**
     * 声明正常交换机,用于测试死信队列
     */
    @Bean
    public Exchange testDlxExchange() {
        return ExchangeBuilder.topicExchange(TEST_DLX_EXCHANGE).build();
    }

    /**
     * 声明正常队列,用于测试死信队列
     */
    @Bean
    public Queue testDlxQueue() {
        return QueueBuilder.durable(TEST_DLX_QUEUE)
                //该队列过期时间,单位毫秒
                .ttl(10000)
                //绑定死信交换机
                .deadLetterExchange(DLX_EXCHANGE)
                //设置死信交换机routingKey
                .deadLetterRoutingKey("dlx.haha")
                //设置队列最大消息数量为10
                .maxLength(10)
                .build();
    }

    /**
     * 声明死信交换机
     * @return 死信交换机
     */
    @Bean
    public Exchange dlxExchange() {
        return ExchangeBuilder.topicExchange(DLX_EXCHANGE).build();
    }

    /**
     * 声明死信队列
     * @return 死信队列
     */
    @Bean
    public Queue dlxQueue() {
        return QueueBuilder.durable(DLX_QUEUE).build();
    }

    /**
     * 绑定测试队列到测试交换机
     * @param queue 测试队列
     * @param exchange 测试交换机
     */
    @Bean
    public Binding bindingTtl(@Qualifier("testDlxQueue") Queue queue,
                              @Qualifier("testDlxExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("test.dlx.#").noargs();
    }

    /**
     * 绑定死信队列到死信交换机
     * @param queue 死信队列
     * @param exchange 死信交换机
     */
    @Bean
    public Binding bindingDlx(@Qualifier("dlxQueue") Queue queue,
                              @Qualifier("dlxExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
    }
}

测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ProviderApplication.class)
public class RabbitmqTest {

    @Autowired
    private RabbitTemplate template;

    /**
     * 测试过期时间
     *      消息一过期,立刻进入死信队列中
     */
    @Test
    public void testDlx1() {
        template.convertAndSend("test_dlx_exchange", "test.dlx.haha", "测试队列的过期时间");
    }

    /**
     * 测试死信队列的长度
     */
    @Test
    public void testDlx2() {
        //有10条会进入正常队列中,有10条会立刻进入死信队列,剩下的10条会在10s后进入死信队列
        for (int i = 0; i < 20; i++) {
            template.convertAndSend("test_dlx_exchange", "test.dlx.haha", "测试队列的长度");
        }
    }

    /**
     * 测试消费者拒收
     */
    @Test
    public void testDlx3() {
        template.convertAndSend("test_dlx_exchange", "test.dlx.haha", "测试队列的的消费者拒收");
    }
}

2.2消费者

springboot入口

@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

配置类【因为要测试拒收,所以消费者必须要手动确认,配置稍微麻烦一点】

@Configuration
public class RabbitmqListenerConfig {

    @Autowired
    private CachingConnectionFactory cachingConnectionFactory;

    @Autowired
    private RabbitmqListener listener;

    @Bean
    public SimpleMessageListenerContainer listener() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(cachingConnectionFactory);
        //设置最小并发数量(可不配)
        container.setConcurrentConsumers(1);
        //设置最大并发数量(可不配)
        container.setMaxConcurrentConsumers(10);
        //设置单位时间内消费多少条记录(可不配)
        container.setPrefetchCount(10);
        //设置确认方式,默认自动
        //自动确认:NONE
		//手动确认:MANUAL
		//根据异常情况确认:AUTO
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        //监听队列的名称。监听的是正常队列的名称,不要写成死信队列了
        container.setQueueNames("test_dlx_queue");
        //设置消息监听类
        container.setMessageListener(listener);
        return container;
    }
}
@Component
public class RabbitmqListener implements ChannelAwareMessageListener {

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        //获取消息id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("处理业务。。。");
        //拒接确认,将消息发送到死信队列
        channel.basicNack(deliveryTag, true, false);
    }
}

测试一:测试过期时间【10s后自动进入死信队列】

在这里插入图片描述

在这里插入图片描述

测试二:测试死信队列的长度。有10条会进入正常队列中,有10条会立刻进入死信队列,剩下的10条会在10s进入死信队列

在这里插入图片描述

在这里插入图片描述

测试三:测试消费者拒收

在这里插入图片描述

在这里插入图片描述

三个测试,一共为22条消息进入死信队列。

3.实现延迟队列

使用延迟队列,模拟下单后,30分钟未支付,取消订单,回滚库存。【30分钟太长了,这里模拟10s】

3.1生产者

配置类

@Configuration
public class RabbitmqConfig {
    /**
     * ttl交换机
     */
    public static final String TTL_EXCHANGE = "springboot_ttl_exchange";
    /**
     * ttl队列
     */
    private static final String TTL_QUEUE = "springboot_ttl_queue";
    /**
     * 死信交换机
     */
    public static final String DLX_EXCHANGE = "springboot_dlx_exchange";
    /**
     * 死信队列
     */
    private static final String DLX_QUEUE = "springboot_dlx_queue";

    /**
     * 声明ttl交换机
     *
     * @return ttl交换机
     */
    @Bean
    public Exchange ttlExchange() {
        return ExchangeBuilder.topicExchange(TTL_EXCHANGE).build();
    }

    /**
     * 声明ttl队列,同时绑定死信交换机
     *
     * @return ttl队列
     */
    @Bean
    public Queue ttlQueue() {
        return QueueBuilder.durable(TTL_QUEUE)
                //该队列过期时间,单位毫秒
                .ttl(10000)
                //绑定死信交换机
                .deadLetterExchange(DLX_EXCHANGE)
                //设置死信交换机routingKey
                .deadLetterRoutingKey("dlx.haha")
                //设置队列最大消息数量为10
                .maxLength(10)
                .build();
    }

    /**
     * 声明死信交换机
     *
     * @return 死信交换机
     */
    @Bean
    public Exchange dlxExchange() {
        return ExchangeBuilder.topicExchange(DLX_EXCHANGE).build();
    }

    /**
     * 声明死信队列
     *
     * @return 死信队列
     */
    @Bean
    public Queue dlxQueue() {
        return QueueBuilder.durable(DLX_QUEUE).build();
    }

    /**
     * 绑定ttl队列到ttl交换机
     *
     * @param queue    ttl队列
     * @param exchange ttl交换机
     */
    @Bean
    public Binding bindingTtl(@Qualifier("ttlQueue") Queue queue,
                              @Qualifier("ttlExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("ttl.#").noargs();
    }

    /**
     * 绑定死信队列到死信交换机
     *
     * @param queue    死信队列
     * @param exchange 死信交换机
     */
    @Bean
    public Binding bindingDlx(@Qualifier("dlxQueue") Queue queue,
                              @Qualifier("dlxExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
    }
}

测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ProviderApplication.class)
public class RabbitmqTest {

    @Autowired
    private RabbitTemplate template;

    @Test
    public void test1() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        template.convertAndSend(RabbitmqConfig.TTL_EXCHANGE,"ttl.test","这是生成订单时间:" + sdf.format(new Date()));
    }
}

3.2消费者

springboot入口

@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

配置类

@Component
public class RabbitmqListener {

    @RabbitListener(queues = "springboot_dlx_queue")
    public void listener(Message message) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(new String(message.getBody(), StandardCharsets.UTF_8));
        System.out.println("消费消息时间:" + sdf.format(new Date()));
    }
}

测试

队列中的消息10s后进入死信队列中,然后被消费者消费。

在这里插入图片描述

在这里插入图片描述

获取死信队列中的消息后,去判断订单状态,如果用户支付了,就什么都不做,如果用户没有支付,就取消订单,回滚库存。

使用rabbitmq模拟下单后,30分钟未支付,取消订单,回滚库存。_第3张图片

你可能感兴趣的:(java,rabbitmq,java,spring,boot)