延时任务-基于redis zset的完整实现

news/2024/4/27 21:03:24/文章来源:https://www.cnblogs.com/zimug/p/16615879.html

所谓的延时任务给大家举个例子:你买了一张火车票,必须在30分钟之内付款,否则该订单被自动取消。订单30分钟不付款自动取消,这个任务就是一个延时任务。 我之前已经写过2篇关于延时任务的文章:

  • 《完整实现-通过DelayQueue实现延时任务》
  • 《延时任务(二)-基于netty时间轮算法实战》

这两种方法都有一个缺点:都是基于单体应用的内存的方式运行延时任务的,一旦出现单点故障,可能出现延时任务数据的丢失。所以此篇文章给大家介绍实现延时任务的第三种方式,结合redis zset实现延时任务,可以解决单点故障的问题。给出实现原理、完整实现代码,以及这种实现方式的优缺点。

一、实现原理

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


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

  • 在用户下单的时候,同时生成延时任务放入redis,key是可以自定义的,比如:delaytask:order
  • value的值分成两个部分,一个部分是score用于排序,一个部分是member,member的值我们设置为订单对象(如:订单编号),因为后续延时任务时效达成的时候,我们需要有一些必要的订单信息(如:订单编号),才能完成订单自动取消关闭的动作。
  • 延时任务实现的重点来了,score我们设置为:订单生成时间 + 延时时长。 这样redis会对zset按照score延时时间进行排序。
  • 开启redis扫描任务,获取"当前时间 > score"的延时任务并执行。即: 当前时间 > 订单生成时间 + 延时时长的时候 ,执行延时任务。

二、准备工作

使用 redis zset 这个方案来完成延时任务的需求,首先肯定是需要redis,这一点毫无疑问。redis的搭建网上有很多的文章,我这里就不赘述了。

其次,笔者长期的java类应用系统开发都是使用SpringBoot来完成,所以也是习惯使用SpringBoot的redis集成方案。首先通过maven坐标引入spring-boot-starter-data-redis

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

其次需要在Spring Boot的application.yml配置文件中,配置redis数据库的链接信息。我这里配置的是redis的单例,如果大家的生产环境是哨兵模式、或者是集群模式的redis,这里的配置方式需要进行微调。其实这部分内容在我的个人博客里面都曾经系统的介绍过,感兴趣的朋友可以关注我的个人博客。

spring:redis:database: 0 # Redis 数据库索引(默认为 0)host: 192.168.161.3 # Redis 服务器地址port: 6379 # Redis 服务器连接端口password: 123456 # Redis 服务器连接密码(默认为空)timeout:  5000  # 连接超时,单位mslettuce:pool:max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 默认 8max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1max-idle: 8 # 连接池中的最大空闲连接 默认 8min-idle: 0 # 连接池中的最小空闲连接 默认 0

三、代码实现

下面的这个类就是延时任务的核心实现了,一共包含三个核心方法,我们来一一说明一下:

  • produce方法,用于生成订单-order为订单信息,可以是订单流水号,用于延时任务达到时效后关闭订单
  • afterPropertiesSet方法是InitializingBean接口的方法,之所以实现这个接口,是因为我们需要在应用启动的时候开启redis扫描任务。即:当OrderDelayService bean初始化的时候,开启redis扫描任务循环获取延时任务数据。
  • consuming函数,用于从redis获取延时任务数据,消费延时任务,执行超时订单关闭等操作。为了避免阻塞for循环,影响后面延时任务的执行,所以这个consuming函数一定要做成异步的,参考Spring Boot异步任务及Async注解的使用方法。我之前写过一个SpringBoot的可观测、易配置的异步任务线程池开源项目,源代码地址:https://gitee.com/hanxt/zimug-monitor-threadpool 。我的这个zimug-monitor-threadpool开源项目,可以做到对线程池使用情况的监控,我自己平时用的效果还不错,向大家推荐一下!
@Component
public class OrderDelayService  implements InitializingBean {//redis zset keypublic static final String ORDER_DELAY_TASK_KEY = "delaytask:order";@Resourceprivate StringRedisTemplate stringRedisTemplate;//生成订单-order为订单信息,可以是订单流水号,用于延时任务达到时效后关闭订单public void produce(String orderSerialNo){stringRedisTemplate.opsForZSet().add(ORDER_DELAY_TASK_KEY,     // redis keyorderSerialNo,    // zset  member//30分钟延时System.currentTimeMillis() + (30 * 60 * 1000)    //zset score);}//延时任务,也是异步任务,延时任务达到时效之后关闭订单,并将延时任务从redis zset删除@Async("test")public void consuming(){Set<ZSetOperations.TypedTuple<String>> orderSerialNos = stringRedisTemplate.opsForZSet().rangeByScoreWithScores(ORDER_DELAY_TASK_KEY,0,  //延时任务score最小值System.currentTimeMillis() //延时任务score最大值(当前时间));if (!CollectionUtils.isEmpty(orderSerialNos)) {for (ZSetOperations.TypedTuple<String> orderSerialNo : orderSerialNos) {//这里根据orderSerialNo去检查用户是否完成了订单支付//如果用户没有支付订单,去执行订单关闭的操作System.out.println("订单" + orderSerialNo.getValue() + "超时被自动关闭");//订单关闭之后,将订单延时任务从队列中删除stringRedisTemplate.opsForZSet().remove(ORDER_DELAY_TASK_KEY, orderSerialNo.getValue());}}}//该类对象Bean实例化之后,就开启while扫描任务@Overridepublic void afterPropertiesSet() throws Exception {new Thread(() -> {  //开启新的线程,否则SpringBoot应用初始化无法启动while(true){try {Thread.sleep(5 * 1000);   //每5秒扫描一次redis库获取延时数据,不用太频繁没必要} catch (InterruptedException e) {e.printStackTrace();  //本文只是示例,生产环境请做好相关的异常处理}consuming();}}).start();}
}

更多的内容参考代码中的注释,需要关注的点是:

  • 上文中的rangeByScoreWithScores方法用于从redis中获取延时任务,score大于0小于当前时间的所有延时任务,都将被从redis里面取出来。每5秒执行一次,所以延时任务的误差不会超过5秒。
  • 上文中的订单信息,我只保留了订单唯一流水号,用于关闭订单。如果你的业务需要传递更多的订单信息,请使用RedisTemplate操作订单类对象,而不是StringRedisTemplate操作订单流水号字符串。

订单下单的时候,使用如下的方法,将订单序列号放入redis zset中即可实现延时任务

orderDelayService.produce("这里填写订单编号");

四、优缺点

使用redis zset来实现延时任务的优点是:相对于本文开头介绍的两种方法,我们的延时任务是保存在redis里面的,redis具有数据持久化的机制,可以有效的避免延时任务数据的丢失。另外,redis还可以通过哨兵模式、集群模式有效的避免单点故障造成的服务中断。
至于缺点嘛,我觉得没什么缺点。如果非要勉强的说一个缺点的话,那就是我们需要额外维护redis服务,增加了硬件资源的需求和运维成本。但是现在随着微服务的兴起,redis几乎已经成了应用系统的标配,redis复用即可,所以我感觉这也算不上什么缺点吧!

码文不易,如果您觉得有帮助,请帮忙点击在看或者分享,没有您的支持我可能无法坚持下去!
欢迎关注我的公告号:字母哥杂谈,回复003赠送作者专栏《docker修炼之道》的PDF版本,30余篇精品docker文章。字母哥博客:zimug.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_2569.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

workbench小技巧——结合paraview

workbench计算完结果后,可以在计算完成的Temperature(或者其他的结果也可以)右键->Export...->STL File将其保存成文.stl格式的文件,并且如果在workbench中是半剖视图,那么生成的.stl格式的文件也是半剖视图。 半剖视图的.stl格式的文件可以在paraview中打开: 这样…

创建一个VUE项目

前期准备 1、安装node,官网安装(自带npm) 2、安装npm国内镜像cnpm:npm install -g cnpm;安装后可能在项目中无法使用,执行cnpm install express -g 3、安装开源前端打包工具webpack:cnpm install webpack -g 4、安装vue-cli脚手架工具:cnpm install vue-cli -g;使用vu…

校园内的汽车

https://www.acwing.com/problem/content/description/1587/思路: 电话记录的模型,先筛选出所有符合要求的记录,之后按照题目要求做即可。 #include <iostream> #include <cstring> #include <algorithm> #include <unordered_map> #include <ve…

如何让discuz论坛下方在线会员不显示在线会员

如何让discuz论坛下方在线会员不显示在线会员-百度经验 https://jingyan.baidu.com/article/4b07be3c6b143848b380f382.html如何让discuz论坛下方在线会员不显示在线会员呢? 后台界面设置,论坛缩略显示在线列表选择是(一般超过500在线会员会自动隐藏的)如下图所示:

Discuz!抱歉,您的IP 地址不在允许范围内办法

Discuz!抱歉,您的IP 地址不在允许范围内办法-百度经验 https://jingyan.baidu.com/article/ce436649394f183773afd305.html今天早上很郁闷呀,打开论坛发现竟然登陆不了,提示以下的问题: Discuz!x3.2 抱歉,您的 IP 地址不在允许范围内,或您的账号被禁用... 这是什么鬼,明…

TCP/UDP

一、定义和对比 TCP/UDP都是是传输层协议,但是两者具有不同的特性,同时也具有不同的应用场景,下面以图表的形式对比分析。 二、使用场景什么时候应该使用TCP?当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP…

项目准备

项目导入 资料连接:https://pan.baidu.com/s/1Xp97dflG_i1a8DyTKJWAjg   提取码:java 选择项目的pom.xml文件导入 项目启动 第一种方式:第二种方式: 启动Maven: 技术选型Web层Servlet:前端控制器html:视图Filter:过滤器BeanUtils:数据封装Jackson:json序列化工具 S…

论文阅读笔记-MapLite 2.0: Online HD Map Inference Using a Prior SD Map

MapLite 2.0:使用先前SD地图的在线高清地图推断MapLite 2.0: Online HD Map Inference Using a Prior SD Map MapLite 2.0:使用先前SD地图的在线高清地图推断 Abstract 部署全自动驾驶汽车一直是工业界和学术界深入研究的主题。然而,这些努力中的大部分都严重依赖于高清 (HD…

Parallels 升级后提示虚拟机网络初始化失败

之前一直没升级,奈何升级 macOS 后,parallels 打不开了,只能尝试去升级,但升级之后的系统打开总是提示网络初始化失败,如下图所示。解决如下方式已解决,注意,尝试之前,先把自己的 parallels 软件给关了。 1、打开资源库的 parallels 打开【访达】,点击顶部【前往】,点…

Centos7中升级python3.10.4版本

****** 先上结果图 ****** (之前是 2.7.5 版本,日志太长没法找到之前的版本截图了) ****** 先上结果图 ******1、下载安装一些依赖包yum install -y wget lrzsz net-tools zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc ma…

Java中时间戳的使用

原文链接 当前时间 import java.sql.Timestamp; //导包Timestamp nowTime = new Timestamp(System.currentTimeMillis()); System.out.println(nowTime);输出: 2022-06-08 11:15:51.014Long型时间戳 Long timeLong = System.currentTimeMillis(); System.out.println("ti…

用栈结构解决佩慈糖果盒问题(JavaScript)

封装的栈操作方法: https://www.cnblogs.com/LIXI-/p/16612874.htmlvar sweetBox = new Stack();sweetBox.push(red);sweetBox.push(yellow);sweetBox.push(red);sweetBox.push(yellow);sweetBox.push(white);sweetBox.push(yellow);sweetBox.push(white);sweetBox.push(yello…

HCIA学习笔记二十:STP生成树

一、环路的影响 1)环路产生• 交换机之间通过多条链路互连时,虽然能够提升网络可靠性,但是同时也带来环路问题。 2)广播风暴二、STP的作用 1)阻塞端口• STP通过阻塞端口来消除环路,并能实现链路备份的作用。 2)阻塞某端口后3)链路备份三、STP生成树基本计算过程 1)选…

taro小程序日期选择器

taro-swiper-weektaro-swiper-week 是一个基于 taro 的日期选择器控件。 可以用在h5、微信小程序等众多平台!简体中文 | English🔨 使用 先安装 npm install taro-swiper-week再引入页面 import SwiperWeek from "taro-swiper-week"; import "taro-swiper-we…

Docker 拉取Nginx镜像 和运行

Docker 镜像拉取docker pull [OPTIONS] NAME[:TAG|@DIGEST] 镜像拉取命令OPTIONS说明:-a :拉取所有 tagged 镜像--disable-content-trust :忽略镜像的校验,默认开启docker images 查询所有镜像docker run [OPTIONS] IMAGE [COMMAND] [ARG…] 运行镜像 docker run --help 可查看…

Java-基础语法

day02 - Java基础语法 1. 运算符 1.1 算术运算符(理解) 1.1.1 运算符和表达式 运算符:对常量或者变量进行操作的符号 表达式:用运算符把常量或者变量连接起来符合java语法的式子就可以称为表达式。 ​ 不同运算符连接的表达式体现的是不同类型的表达式。…

Java集合-List

1.Collection集合 1.1集合体系结构【记忆】集合类的特点 ​ 提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变集合类的体系图1.2Collection集合概述和基本使用【应用】Collection集合概述是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素…

Java异常处理

3.异常 3.1异常(记忆)异常的概述 ​ 异常就是程序出现了不正常的情况异常的体系结构3.2JVM默认处理异常的方式(理解)如果程序出现了问题,我们没有做任何处理,最终JVM 会做默认的处理,处理方式有如下两个步骤:把异常的名称,错误原因及异常出现的位置等信息输出在了控制…

JavaIO流---字节流和字符流

1.字节缓冲流 1.1字节缓冲流构造方法【应用】字节缓冲流介绍lBufferOutputStream:该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用lBufferedInputStream:创建BufferedInputStream将创建一个内部缓冲…

MySQL和Navicat安装过程记录Win10

安装MySQL双击下载好的文件点击Execute,等待绿勾后Finish验证配置环境变量以在命令行使用Win+R,输入cmd回车,输入mysql -uroot -p登录成功安装Navicat点击安装,等待完成即可 破解Navicat 破解软件下载地址及具体教程 关闭网络,关闭杀毒软件和Windows防护(不知道去哪里关可…