ToB企服应用市场:ToB评测及商务社交产业平台

标题: 延时任务-基于redis zset的完整实现 [打印本页]

作者: 没腿的鸟    时间: 2022-9-16 17:18
标题: 延时任务-基于redis zset的完整实现
所谓的延时任务给大家举个例子:你买了一张火车票,必须在30分钟之内付款,否则该订单被自动取消。订单30分钟不付款自动取消,这个任务就是一个延时任务。   我之前已经写过2篇关于延时任务的文章:
这两种方法都有一个缺点:都是基于单体应用的内存的方式运行延时任务的,一旦出现单点故障,可能出现延时任务数据的丢失。所以此篇文章给大家介绍实现延时任务的第三种方式,结合redis zset实现延时任务,可以解决单点故障的问题。给出实现原理、完整实现代码,以及这种实现方式的优缺点。
一、实现原理

首先来介绍一下实现原理,我们需要使用redis zset来实现延时任务的需求,所以我们需要知道zset的应用特性。zset作为redis的有序集合数据结构存在,排序的依据就是score。

所以我们可以利用zset score这个排序的这个特性,来实现延时任务
二、准备工作

使用 redis zset 这个方案来完成延时任务的需求,首先肯定是需要redis,这一点毫无疑问。redis的搭建网上有很多的文章,我这里就不赘述了。
其次,笔者长期的java类应用系统开发都是使用SpringBoot来完成,所以也是习惯使用SpringBoot的redis集成方案。首先通过maven坐标引入spring-boot-starter-data-redis
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6.     <groupId>org.apache.commons</groupId>
  7.     <artifactId>commons-pool2</artifactId>
  8. </dependency>
复制代码
其次需要在Spring Boot的application.yml配置文件中,配置redis数据库的链接信息。我这里配置的是redis的单例,如果大家的生产环境是哨兵模式、或者是集群模式的redis,这里的配置方式需要进行微调。其实这部分内容在我的个人博客里面都曾经系统的介绍过,感兴趣的朋友可以关注我的个人博客。
  1. spring:
  2.   redis:
  3.     database: 0 # Redis 数据库索引(默认为 0)
  4.     host: 192.168.161.3 # Redis 服务器地址
  5.     port: 6379 # Redis 服务器连接端口
  6.     password: 123456 # Redis 服务器连接密码(默认为空)
  7.     timeout:  5000  # 连接超时,单位ms
  8.     lettuce:
  9.       pool:
  10.         max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 默认 8
  11.         max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
  12.         max-idle: 8 # 连接池中的最大空闲连接 默认 8
  13.         min-idle: 0 # 连接池中的最小空闲连接 默认 0
复制代码
三、代码实现

下面的这个类就是延时任务的核心实现了,一共包含三个核心方法,我们来一一说明一下:
  1. @Component
  2. public class OrderDelayService  implements InitializingBean {
  3.   //redis zset key
  4.   public static final String ORDER_DELAY_TASK_KEY = "delaytask:order";
  5.   @Resource
  6.   private StringRedisTemplate stringRedisTemplate;
  7.   //生成订单-order为订单信息,可以是订单流水号,用于延时任务达到时效后关闭订单
  8.   public void produce(String orderSerialNo){
  9.     stringRedisTemplate.opsForZSet().add(
  10.             ORDER_DELAY_TASK_KEY,     // redis key
  11.             orderSerialNo,    // zset  member
  12.             //30分钟延时
  13.             System.currentTimeMillis() + (30 * 60 * 1000)    //zset score
  14.     );
  15.   }
  16.   //延时任务,也是异步任务,延时任务达到时效之后关闭订单,并将延时任务从redis zset删除
  17.   @Async("test")
  18.   public void consuming(){
  19.       
  20.       Set<ZSetOperations.TypedTuple<String>> orderSerialNos = stringRedisTemplate.opsForZSet().rangeByScoreWithScores(
  21.               ORDER_DELAY_TASK_KEY,
  22.               0,  //延时任务score最小值
  23.               System.currentTimeMillis() //延时任务score最大值(当前时间)
  24.       );
  25.       if (!CollectionUtils.isEmpty(orderSerialNos)) {
  26.         for (ZSetOperations.TypedTuple<String> orderSerialNo : orderSerialNos) {
  27.           //这里根据orderSerialNo去检查用户是否完成了订单支付
  28.           //如果用户没有支付订单,去执行订单关闭的操作
  29.           System.out.println("订单" + orderSerialNo.getValue() + "超时被自动关闭");
  30.           //订单关闭之后,将订单延时任务从队列中删除
  31.           stringRedisTemplate.opsForZSet().remove(ORDER_DELAY_TASK_KEY, orderSerialNo.getValue());
  32.         }
  33.       }
  34.   }
  35.   //该类对象Bean实例化之后,就开启while扫描任务
  36.   @Override
  37.   public void afterPropertiesSet() throws Exception {
  38.     new Thread(() -> {  //开启新的线程,否则SpringBoot应用初始化无法启动
  39.       while(true){
  40.         try {
  41.           Thread.sleep(5 * 1000);   //每5秒扫描一次redis库获取延时数据,不用太频繁没必要
  42.         } catch (InterruptedException e) {
  43.           e.printStackTrace();  //本文只是示例,生产环境请做好相关的异常处理
  44.         }
  45.         consuming();
  46.       }
  47.     }).start();
  48.   }
  49. }
复制代码
更多的内容参考代码中的注释,需要关注的点是:
订单下单的时候,使用如下的方法,将订单序列号放入redis zset中即可实现延时任务
  1. orderDelayService.produce("这里填写订单编号");
复制代码
四、优缺点

使用redis zset来实现延时任务的优点是:相对于本文开头介绍的两种方法,我们的延时任务是保存在redis里面的,redis具有数据持久化的机制,可以有效的避免延时任务数据的丢失。另外,redis还可以通过哨兵模式、集群模式有效的避免单点故障造成的服务中断。
至于缺点嘛,我觉得没什么缺点。如果非要勉强的说一个缺点的话,那就是我们需要额外维护redis服务,增加了硬件资源的需求和运维成本。但是现在随着微服务的兴起,redis几乎已经成了应用系统的标配,redis复用即可,所以我感觉这也算不上什么缺点吧!
码文不易,如果您觉得有帮助,请帮忙点击在看或者分享,没有您的支持我可能无法坚持下去!
欢迎关注我的公告号:字母哥杂谈,回复003赠送作者专栏《docker修炼之道》的PDF版本,30余篇精品docker文章。字母哥博客:zimug.com

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4