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

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

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

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

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

pom.xml

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.3.4.RELEASE</version>
</parent>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-amqp</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
	</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

在这里插入图片描述

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

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。

在这里插入图片描述

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

  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后进入死信队列中,然后被消费者消费。

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

  • 9
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用RabbitMQ实现订单下单支付场景中,可以通过以下步骤来实现: 1. 用户下单后,订单系统将订单信息落库,并将订单信息发送到RabbitMQ的消息队列中。 2. 支付系统监听RabbitMQ的消息队列,一旦接收到订单信息,开始处理支付逻辑。 3. 支付系统根据订单信息进行支付操作,如果支付成功,则更新订单支付状态为已支付,并发送支付成功的回调消息到RabbitMQ的消息队列中。 4. 订单系统监听支付成功的回调消息,一旦接收到支付成功的回调消息,更新订单状态为已支付。 5. 如果支付系统在一定时间内收到支付成功的回调消息,则认为支付失败,订单系统可以根据需要进行相应的处理,例如关闭订单或释放库存。 通过使用RabbitMQ的消息队列,订单系统和支付系统可以解耦,实现异步处理,提高系统的可靠性和稳定性。同时,可以根据实际需求设置合适的超时时间和重试机制,以确保订单支付的准确性和及时性。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* [RabbitMQ实现订单超时设计思路、以及在订单过期临界点支付成功如何处理](https://blog.csdn.net/hkl_Forever/article/details/128216981)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [RabbitMQ的应用场景](https://blog.csdn.net/weixin_54721305/article/details/123668635)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值