Springboot+Redis实现过期键通知(订单超时取消方案总结) - Go语言中文社区

Springboot+Redis实现过期键通知(订单超时取消方案总结)


背景

需求需要实现 订单15分钟超时未支付自动关闭

调研实现方案

  1. 基于java DelayQueue
    缺点: 单机、不能持久化、宕机任务丢失等等;
    优点:不依赖任何三方,仅java原生api即可
  2. 定时任务全表扫描
    缺点:需要全表扫描,任务设置轮询时间就是最大延迟时间,对数据库有一定压力,仅适合数据量少的业务场景
    优点: 实现简单,仅需要任务调度即可
  3. redis过期消息通知
    缺点:
    1. 开启键通知会对redis有额外的开销
    2. 键通知暂时redis并不保证消息必达,redis客户端断开连接所有key丢失
    3. 消费速度不可自控,如果一瞬间QPS非常高,接收到的通知会非常密集,消费不过来,
      如果用线程池消费,大部分的待消费任务会放入到阻塞队列 一旦服务宕机,阻塞队列消息全部丢失
  4. mq提供的延时队列
    优点:消息0丢失,可抗高并发
    缺点:需要额外引入mq中间件,提高系统复杂性和mq高可用维护性

延时队列简单实现

  • 依赖
<dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
         <version>1.18.12</version>
</dependency>
<dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.2</version>
</dependency>

order

{
    //订单编号
    private String orderNO;
    //过期时间
    private Date time;
    //订单状态
    private String orderStatus;

    // 设置延时时间
    public long getDelay(TimeUnit unit) {
        long l = unit.convert(time.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        return l;
    }

    public int compareTo(Delayed o) {
        //根据取消时间来比较,如果取消时间小的,就会优先被队列提取出来
        return time.compareTo(((Order) o).getTime());

    }
}

DelayedTest

public class DelayedTest {
    // 自动取消线程开关
   static boolean flag = true;
    //存放过期订单
   static DelayQueue<Order> queue = new DelayQueue();

    public static void main(String[] args) {
        DelayedTest delayedTest = new DelayedTest();
        //新建一个线程,用来模拟定时取消订单job
        new Thread(() -> {
            System.out.println("开启自动取消订单job,当前时间:"+ DateUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN));
            while (flag) {
                try {
                    Order order = delayedTest.queue.take();
                    order.setOrderStatus("超时取消");
                    System.out.println("订单:" + order.getOrderNO() + "超时取消"+ DateUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            //定义最早的订单的创建时间
            long now = System.currentTimeMillis();
            System.out.println("下单开始时间" + DateUtil.format(new Date(now), DatePattern.NORM_DATETIME_PATTERN));
            //下面模拟6个订单,每个订单的创建时间依次延后3秒
            queue.add(new Order("001", new Date(now + 3000), "未支付"));
            queue.add(new Order("002", new Date(now + 6000), "未支付"));
            queue.add(new Order("003", new Date(now + 9000), "未支付"));
            queue.add(new Order("004", new Date(now + 12000), "未支付"));
        }).start();
    }



}

在这里插入图片描述

redis简单实现

框架: spirngboot

  1. 依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置文件添加redis配置
spring.redis.port=6379
spring.redis.host=49.233.150.105
  1. redis开启过期键通知
    找到redis配置文件redis.conf,打开配置文件找到
    notify-keyspace-events配置查看是否开启,默认是没有开启的,添加配置
    notify-keyspace-events Ex
    在这里插入图片描述

重启redis
相关参数说明:
K:keyspace 事件,事件以 keyspace@ 为前缀进行发布

E:keyevent 事件,事件以 keyevent@ 为前缀进行发布

g:一般性的,非特定类型的命令,比如del,expire,rename等

$:字符串特定命令

l:列表特定命令

s:集合特定命令

h:哈希特定命令

z:有序集合特定命令

x:过期事件,当某个键过期并删除时会产生该事件

e:驱逐事件,当某个键因 maxmemore 策略而被删除时,产生该事件

A:g$lshzxe的别名,因此”AKE”意味着所有事件

  1. 配置 RedisListenerConfig 实现监听 Redis key 过期时间

RedisListenerConfig

@Configuration
public class RedisListenerConfig {

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}
  1. 定义监听器 RedisKeyExpirationListener,实现KeyExpirationEventMessageListener 接口

RedisKeyExpirationListener

@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    OrderDao orderDao;

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    /**
     * 针对 redis 数据失效事件,进行数据处理
     *
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        RedisSerializer<String> serializer = redisTemplate.getValueSerializer();
        // 获取到失效的 key
        String orderNo = message.toString();
        if (StrUtil.startWith("", "order")) {
        }
        OrderSummaryDO orderSummaryDO = new OrderSummaryDO();
        orderSummaryDO.setOrderNo(orderNo);
        orderSummaryDO.setOrderStatus(OrderStatusEnum.CLOSE.getCode());
        orderDao.updateOrderStatus(orderSummaryDO);
    }
}
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_42651904/article/details/106279593
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢