当前位置:   article > 正文

SpringBoot应用篇基于Redis实现延时队列_redis 定时取消订单

redis 定时取消订单

SpringBoot应用篇基于Redis实现延时队列

延时队列,相信各位小伙伴并不会陌生,jdk原生提供了延时队列的使用,当然我们这里介绍的不是这种;在实际的项目中,如果我们有延时队列的场景,可以怎样去实现呢

举一个简单的例子,如下单15分钟内,若没有支付,则自动取消订单

本文将介绍一种非常非常简单的实现方式

I. 方案设计

要实现15分钟后自动取消订单,这个也太简单了,来给出一段神级代码

  1. new Thread(() -> {
  2.   // 休眠十五分钟,执行取消订单
  3.   Thread.sleep(15 * 60 * 1000);
  4.   cancelOrder(); 
  5. }).start();

好的,本文就此结束(开玩笑....)

忽略上面的段子,接下来想一想,如果让我们来实现一个延时队列,可以怎么整?

  • 单机:

    • DelayQueue

    • 定时任务

  • 分布式:

首先我们这里排除掉单机版,至于原因,现在单体单实例应用实在不多见了,直接来看多实例的情况吧

在上面的几种方案中,重心放在redis上,两种case,下面分别介绍一下

1. redis过期时间

我们知道,在使用redis做缓存时,可以设置失效时间,借助redis的失效事件,我们可以来实现延时队列的场景

比如,现在一个订单,我们在redis中新加入一个订单id,失效时间设置为15分钟;当支付成功之后,主动删除这个缓存;若一直没有付钱,则15分钟后,触发一个过期事件,然后订阅这个事件,来执行取消订单

上面这种实现,有两个问题

  • key失效监听,可能存在大量的无效信息

  • 广播方式消费事件,多实例接收到这个事件,怎么防并发?或者没有一个实例接收到这个事件,那么这个取消订单就会漏掉

显然上面的第二点,漏消息是不能接受的

2. redis zset

zset属于redis提供的几个基本数据结构中的一种,它的特点是有 value + score

如果我们想使用zset拉实现演示队列,那么一个可选的方案就是将score设置为触发的时间戳,value为业务值

然后写一个定时任务,不断的从zset中,取出score小于当前时间戳的数据,任务它们都是已经到期可以执行的

借助这个方案,可以相对简单的实现一个演示队列了

II. redis演示队列实现

1. 环境配置

接下来我们将以redis的zset来实现延时队列,本文借助SpringBoot来搭建一个演示工程,使用的基本配置如下

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

核心依赖:

  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <!-- 下面这里两个非必须,主要是后面的实现演示使用到了 -->
  6. <dependency>
  7.     <groupId>com.alibaba</groupId>
  8.     <artifactId>fastjson</artifactId>
  9. </dependency>
  10. <dependency>
  11.     <groupId>org.aspectj</groupId>
  12.     <artifactId>aspectjweaver</artifactId>
  13. </dependency>

redis使用默认的配置,本机 localhost + 6379

2. 核心实现

借助redis zset来实现延时队列,具体的实现代码就很简单了,无非是从zset中取出score小于当前时间戳的数据

  1. private static final Long DELETE_SUCCESS = 1L;
  2. @Autowired
  3. private StringRedisTemplate redisTemplate;
  4. public String fetchOne(String key) {
  5.     Set<String> sets = redisTemplate.opsForZSet().rangeByScore(key0, System.currentTimeMillis(), 03);
  6.     if (CollectionUtils.isEmpty(sets)) {
  7.         return null;
  8.     }
  9.     for (String val : sets) {
  10.         if (DELETE_SUCCESS.equals(redisTemplate.opsForZSet().remove(key, val))) {
  11.             // 删除成功,表示抢占到
  12.             return val;
  13.         }
  14.     }
  15.     return null;
  16. }

注意上面的实现,有一个点需要说一下

zset:每次查询时取了三个数据,然后遍历获取到的数据,依次尝试去删除,若删除成功,则表示当前实例抢占到了这个消息

为什么这样设计?

这里有两个点,先解释第一个,为啥先查后删

如果我们按照正常的实现流程,每次从zset中取一个,但是无法保证这个时候就只有我一个人拿到了这个数据,在多实例的场景下,可能存在多个实例同时拿到了它,那么如何才能表示只有我一个人霸占了她呢(忽然进入言情的世界

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/600955
推荐阅读
相关标签