基于Redis中zset实现延时任务

news2025/1/12 1:52:02

目录

概要

一、实现原理

适用场景

二、准备工作

三、代码实现

四、zset的优缺点

优点

缺点


概要

        本文章主要记录的是使用Redis中的zset实现延时任务,在工作中,像这样的的延时任务是不可避免的,举个栗子:买一张火车票,必须在30分钟之内付款,否则该订单被自动取消。订单30分钟不付款自动取消,这个任务就是一个延时任务。

一、实现原理

首先来介绍一下实现原理,我们需要使用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 的环境搭建在这里就不赘述了。其次,使用SpringBoot框架来完成。

Spingboot集成redis:

<!-- 工具类jar包 -->
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>${commons.collections.version}</version>
</dependency>

<!--redis-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>${jedis.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Springboot中application.yml配置文件中配置redis相关信息:

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

三、代码实现

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

  • produce方法,用于生成订单——order为订单信息,可以是订单流水号,也可以是订单对象,具体看业务需要,用于延时任务达到时效后关闭订单
  • afterPropertiesSet方法是InitialzingBean接口的方法,之所以实现这个接口,是因为我们需要在应用启动的时候开启redis扫描任务。(也可以使用定时任务)即:当OrderDelayServiceImpl bean初始化的时候,开启redis扫描任务循环获取延时任务数据。
  • consuming函数,用于从redis获取延时任务数据,消费延时任务,执行超时订单关闭等操作。为了避免阻塞for循环,影响后面延时任务的执行,所以这个consuming函数一定要做成异步的(参考Spring Boot异步任务及Async注解的使用方法)。
package com.techstone.techstone.modules.service.impl;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Set;

/**
 * ZSet延时任务
 */
@Service
public class OrderDelayServiceImpl implements InitializingBean {

    /**
     * redis zset key
     */
    public static final String ORDER_DELAY_TASK_KEY = "delaytask:order";

    /**
     * @ --> @Resource和@Autowired区别
     * 1.来源不同:
     *      来自不同的父类,@Autowired是Spring定义的注解,@Resource是java定义的注解
     * 2.依赖查找的顺序不同:+
     *      有两种方式:按名称(byName)查询、按照类型(byType)查找。
     *      。@Autowired是先根据类型查找,如果存在多个Bean再根据名称查找;
     *      。@Resource是先根据名称查找,如果查不到,再根据类型进行查找。
     * 3.支持的参数不同:
     *      。@Autowired只支持设置一个required的参数
     *      。@Resource支持7个参数,
     * 4.依赖注入的用法不同:
     *      。@Autowired支持属性注入、构造方法注入、Setter注入
     *      。@Resource只支持属性注入和Setter注入
     * 5.编译器提示不同:
     *      在IDEA中注入的是Mapper对象,使用@Autowired编译器回提示报错信息;
     *      使用@Resource就不会了
     */
    @Resource
    private StringRedisTemplate stringRedisTemplate;


    /**
     * 生成订单-order为订单信息,可以是订单流水号,用于延时任务达到时效后关闭订单
     * @param orderSerialNo
     */
    public void produce(String orderSerialNo){
        stringRedisTemplate.opsForZSet().add(
          ORDER_DELAY_TASK_KEY,  // redis key
          orderSerialNo,    // zset memeber
          System.currentTimeMillis() + (60 * 1000) // zset score
        );
    }

    /**
     * 延时任务,也是异步任务,延时任务达到时效之后关闭订单,并将延时任务从redis zset删除
     */
    @Async("test")
    public void consuming(){
        // rangeByScoreWithScores方法用于从redis中获取延时任务,score大于0小于当前时间的所有延时任务
        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扫描任务
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        // 开启新的线程,否则SpringBoot应用初始化无法启动
        new Thread(()->{
            while (true){
                try {
                    // 每5秒扫描一次redis库获取延时数据,不用太频繁没必要
                    Thread.sleep(5 * 1000);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                consuming();
            }
        }).start();
    }
}

需要关注的点:

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

模拟订单下单,使用如下操作,将订单序列号放入redis zset中即可实现延时任务,笔者是采用接口的形式测试的。

@GetMapping("/testZSetScore")
@ApiOperation(value = "测试缓存ZSet延时任务")
public Result<String> testZSetScore(@RequestParam("id") String id){
    // 主要调用这个方法
    orderDelayService.produce(id);
    return new Result<String>().ok("已成功将key设置到Redis缓存中");
}

四、zset的优缺点

优点

  • 延时任务保存再redis里,redis具有数据持久化的机制,可以有效的避免延时任务数据的丢失。还可以通过哨兵模式、集群模式有效的避免单点故障造成的服务中断。

缺点

  • 需要额外维护redis服务,增加了硬件资源的需求和运维成本。


作者:筱白爱学习!!

欢迎关注转发评论点赞沟通,您的支持是筱白的动力!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业如何利用网络趋势做好线上营销?

随着互联网的不断发展&#xff0c;线上营销越来越成为企业营销的重要组成部分。如何利用网络趋势做好线上营销&#xff0c;已经成为各大企业关注的焦点。本文将为大家介绍如何利用网络趋势做好线上营销的方法和技巧。 一、了解网络趋势 了解网络趋势是做好线上营销的关键。网络…

uboot移植Linux-SD驱动代码解析

一、uboot与linux驱动 1.1、uboot本身是裸机程序 (1)狭义的驱动概念是指&#xff1a;操作系统中用来具体操控硬件的代码叫驱动 广义的驱动概念是指&#xff1a;凡是操控硬件的代码都叫驱动 (2)裸机程序中是直接使用寄存器的物理地址来操控硬件的&#xff0c;操作系统中必须通…

最新版千帆直播网站系统PHP完整版源码(PC+WAP在线观看视频)附安装教程

最新版千帆直播网站PHP完整版源码&#xff0c;PCWAP在线观看视频直播系统 安装方法&#xff1a; 1、导入数据库文件 zhibo.sql 2、修改数据库配置文件 有多处包含UC配置; 根目录&#xff1a;config.inc.php – config.php 其他路径&#xff1a; Conf/config.php Admin/C…

JVM(三):JVM命令与参数

JVM命令与参数 文章目录 JVM命令与参数JVM参数标准参数-X 参数-XX参数其他参数说明常用参数的意义 常用命令jpsjinfojstatjstackjmap 常用工具jconsolejvisualvm内存分析工具 MATGC日志分析工具内存分析工具 MATGC日志分析工具 经过前面的各种分析学习&#xff0c;我们知道了关…

淦,服务器被人传了后门木马。。。

「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 今天很暴躁&#xff0c;因为睡眠被打扰了。 一个朋友大半夜打我电话&#xff0c;说她云服…

ThingsBoard 接入摄像头方案

0、上图 废话不多说,先给大家来个效果图: 1、概述 最近,我在群里看到有很多兄弟向我咨询摄像头接入到tb的方案,这个就是找对人了,后续我会截图我当初做的东西,其实这个很简单,而且我这种方法是最好的,下面给大家一一道来。 我总结了下面几种情况,其实关键在于摄像头…

QML画布元素

在早些时候的Qt4中加入QML时&#xff0c;一些开发者讨论如何在QtQuick中绘制一个圆形。类似圆形的问题&#xff0c;一些开发者也对于其它的形状的支持进行了讨论。在QtQuick中没有圆形&#xff0c;只有矩形。在Qt4中&#xff0c;如果你需要一个除了矩形外的形状&#xff0c;你需…

浅谈电力物联网在智能配电系统应用

摘要&#xff1a; 在社会经济和科学技术不断发展中&#xff0c;配电网实现了角色转变&#xff0c;传统的单向供电服务形式已经被双向能流服务形式取代&#xff0c;社会多样化的用电需求也得以有效满足。随着物联网技术的发展&#xff0c;泛在电力物联网开始应用于当今的电力系…

使用【SD-WEBUI】插件生成单张图包含多个人物:分区域的提示词

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;潜变量成对&#xff08;Latent Couple&#xff09;&#xff08;1.1&#xff09;可自组LoRA&#xff08;Composable LoRA&#xff09; &#xff08;二&#xff09;分区扩散&#xff08;Multi Diffusion&#…

@Configuration(proxyBeanMethods = false) 解析

又是美好的一天呀~ 个人博客地址&#xff1a; huanghong.top 往下看看~ Configuration(proxyBeanMethods false) 解析proxyBeanMethods分析总结 Configuration(proxyBeanMethods false) 解析 最近看一些源码的时候&#xff0c;发现很多Configuration配置类上Configuration(p…

Mysql 学习(九)多表连接原理

连接介绍 为了更加方便的介绍一下连接&#xff0c;我们先创建两个表格 t1 和 t2 CREATE TABLE t1 (m1 int, n1 char(1));CREATE TABLE t2 (m2 int, n2 char(1));INSERT INTO t1 VALUES(1, a), (2, b), (3, c);INSERT INTO t2 VALUES(2, b), (3, c), (4, d);连接的本质是将各个…

GitHub上的AutoGPT神秘的面纱

最近一直在说AutoGPT&#xff0c;有很多的视频介绍&#xff0c;但是本着收藏等于学会的原则&#xff0c;收藏一堆一直没看。 这里用一句话说明白&#xff1a;AutoGPT就是一个用Python套装的壳子&#xff0c;用来更省事的调用OpenAI的API。&#xff08;如果你不明白API&#xf…

WiFi(Wireless Fidelity)基础(二)

目录 一、基本介绍&#xff08;Introduction&#xff09; 二、进化发展&#xff08;Evolution&#xff09; 三、PHY帧&#xff08;&#xff08;PHY Frame &#xff09; 四、MAC帧&#xff08;MAC Frame &#xff09; 五、协议&#xff08;Protocol&#xff09; 六、安全&#x…

ByteBuffer的讲解和使用

1.它其实就是一个数据读取或者写入的一个缓冲区 2.基本的操作步骤&#xff1a; 向buffer写入数据&#xff0c;例如调用channel.read(buffer)调用flip()切换至读模式从buffer读取数据&#xff0c;例如调用buffer.get()调用clear()或者compact()切换至写模式重复以上步骤 3.内部…

企业商务租车为工作提供便利

在当代的忙碌生活中&#xff0c;我们总会遇到各种各样的烦恼。最突出的是企业在商务工作中&#xff0c;常常会因为各种原因而导致耽误时间&#xff0c;如火急火燎的去谈生意&#xff0c;却遇到了堵车的现象&#xff0c;或者车辆出现问题而导致耽误时间&#xff0c;而广州商务租…

ICV:中国车载超声波雷达市场规模预计2024年可达20亿美元

近年来&#xff0c;由于市场对车辆先进安全功能的需求的增加&#xff0c;汽车超声波传感器市场一直保持稳步增长。ICV估计&#xff0c;车载超声波传感器全球市场预计在2022年至2027年之间以11.5&#xff05;的复合年增长率增长&#xff0c;这种增长是由越来越多的高级驾驶辅助系…

python 开发 1 之 拷贝文件

目录 一、需求&#xff1a; 二、python拷贝分析 1、需要的库&#xff0c;及源路径、目标路径定义 2、定义的拷贝数组 3、自定义拷贝函数 1&#xff09; 如果目标路径不存在时&#xff0c;先创建目标路径 2&#xff09;遍历元组数组中的文件 3&#xff09;如果源文件或目…

【JavaEE初阶】多线程进阶(六)JUC 线程安全的集合类

文章目录 JUC&#xff08;java.util.concurrent&#xff09;的常见类Callable接口相关面试题 ReentrantLock(可重入锁)原子类信号量SemaphoreCountDownLatch 线程安全的集合类多线程环境使用 ArrayList多线程使用队列多线程使用哈希表&#xff08;重点&#xff09;相关面试题 J…

2.sql server数据表的管理(实验报告)

目录 一﹑实验目的 二﹑实验平台 三﹑实验内容和步骤 四﹑命令(代码)清单 五﹑运行结果 一﹑实验目的 掌握使用SQL Server管理平台和Transact-SQL语句Create table和Alter table创建和修改表的方法&#xff1b;掌握在SQL Server管理平台中对表进行插入、修改和删除数据操作…

哪种蓝牙耳机戴着最舒服?久戴不痛的蓝牙耳机推荐

很多喜欢跑步或通勤的时候带着耳机听音乐&#xff0c;而现在无线耳机市场规模扩大之后&#xff0c;也开始走向更加细分的市场&#xff0c;以满足越来越不同的差异化需求&#xff0c;但是佩戴的舒适度是很多人关注的&#xff0c;下面整理了几款佩戴舒适度高的蓝牙耳机&#xff0…