商品下单未支付,如何取消订单?

一、业务场景

当下完订单,一般超过15分钟或者30分钟就需要对未支付的订单进行关闭。

二、解决方案
1.定时任务

通过定时任务隔一段时间扫描一次的数据,超过时间的订单,修改为取消状态

public class OrderCloseTask {

    @Scheduled(cron = "0 0/15 * * * ?")
    public void closeOrder() {

        // .. 关闭订单
    }
}

问题:

  1. 时效性差
    定时任务每15分钟扫一次,如果14:59创建了的单子,那关闭订单的时候就滞后了
  2. 性能差
    每隔一段时间就要扫描一次表数据,扫描表操作数据的时候,可能会加锁,性能比较低
2.redis pub/sub 发布订阅

下单,在redis创建一个带失效时间的key。通过key失效,监听key失效的事件,去取消订单

package com.xiaokk.house.web.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

import java.nio.charset.StandardCharsets;

/**
 * 功能:
 *
 * @author kangping
 * @date 2022-01-29 1:20 下午
 */
@Slf4j
package com.xiaokk.house.web.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

import java.nio.charset.StandardCharsets;

/**
 * 功能:
 *
 * @author kangping
 * @date 2022-01-29 1:20 下午
 */
@Slf4j
public class KeyExpiredListener extends KeyExpirationEventMessageListener {
    /**
     * Creates new {@link MessageListener} for {@code __keyevent@*__:expired} messages.
     *
     * @param listenerContainer must not be {@literal null}.
     */
    public KeyExpiredListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 失效的key
        String key = new String(message.getBody(), StandardCharsets.UTF_8);
        log.info("key = {}", key);
        // 消息来自于那个渠道,redis默认有16个库,就有16个渠道
        String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
        log.info("channel = {}", channel);
        // 监听的渠道 "__keyevent@*__:expired 默认兼容所有的数据库
        String patt = new String(pattern, StandardCharsets.UTF_8);
        log.info("patt = {}",patt);

        // 取消订单
    }
}

创建消息监听的一个容器

 @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) {
        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
        redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
        return redisMessageListenerContainer;
    }

    @Bean
    public KeyExpiredListener keyExpiredListener(RedisMessageListenerContainer redisMessageListenerContainer) {
        return new KeyExpiredListener(redisMessageListenerContainer);
    }

问题

  1. 发布到所有的订阅者
    一般订单服务会有多个,假如订单服务有3个,那3个都会去取消订单。如果想到加入分布式锁,如果抢到锁的服务挂了。取消订单就是失败了。就没有了可靠性
  2. 性能问题
    keyevent@*:expired 默认是监听的所有渠道,代表不是订单取消相关的key也会进入到这个监听方法
  3. 时效性
    redis 的key 是维护在一个字典表当中,redis是每1秒去检查几次这些key,每次检查中是抽取一定的比例的key,看这些key是否已经失效。如果key比较多,可能就有个别的key总是没有被抽取到,导致key没有及时失效。在抽取的key当中,如果超过一定量的比例都失效,redis会减少去检查的时间,导致检查得更加频繁。而woker本身是单线程,所有有性能损耗。
3.延时消息

创建订单的时候发送一个延时消息。消费的时候判断订单是否还是待支付状态,如果是,则关闭订单

优点

  1. 正常情况下,只有一个订单服务会去消费消息
  2. ask机制可以保证,至少消费一次。保证了可靠性

缺点
发送消息可能是有网络延时的,或者mq队列中消息很多,时间到了还没有被消费到。还是会有实时性问题。
解决方案:
通过业务方式去处理,在支付的时候去判断这个订单是否是已经过期,如果已经过期,在取消订单

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值