springboot-redis设置定时触发任务详解

最近研究了一下“redis定时触发”,网上查了查资料,这里记录一下。

        从Redis 2.8.0开始,Redis加入了发布/订阅模式以及键空间消息提醒(keyspace notification)功能。键空间消息提醒提供了允许客户端通过订阅指定信道获取Redis数据变化的能力。

        需要注意的是,键空间消息提醒并非可靠的,它不会对订阅端是否接收到消息进行确认。例如某个订阅的客户端暂时断开连接,在其直到恢复连接期间发生的事件将无法再次获得。

配置

        在默认情况下,Redis并未开启键空间消息提醒功能。为了打开该功能,需要通过notify-keyspace-events配置进行设置,例如:

redis> CONFIG GET notify-keyspace-events
1) "notify-keyspace-events"
2) ""
redis> CONFIG SET notify-keyspace-events KEA
OK
redis> CONFIG GET notify-keyspace-events
1) "notify-keyspace-events"
2) "AKE"

        在上述示例中将notify-keyspace-events配置为KEA,代表除未命中外的所有事件。

        其中,K与E代表事件的两种类型——Keyspace与Keyevent。

  •         Keyspace代表与事件名称相关的消息,例如订阅对指定键进行的操作事件;
  •         Keyevent代表与键名称相关的消息,例如订阅发生键过期事件的相关键名称。

关于更多的notify-keyspace-events配置,可参考下面的描述:

  • K:Keyspace事件,将会以__keyspace@<db>__作为事件的前缀
  • E:Keyevent事件,将会以__keyevent@<db>__作为事件的前缀
  • g:非特定类型的通用命令,例如DEL、EXPIRE、RENAME等
  • $:字符串命令,例如SET、INCR等
  • l:列表命令,例如LPUSH、LPOP等
  • s:集合命令,例如SADD、SREM等
  • h:哈希表命令,例如HSET、HINCRBY等
  • z:有序集合命令,例如ZSET、ZREM等
  • t:流命令,例如XADD、XDEL等
  • x:过期事件(在每个发生键过期的时侯产生)
  • e:淘汰事件(在每个发生键被淘汰的时候产生)
  • m:未命中事件(在访问某个不存在的键使产生)
  • A:配置g$lshztxe的别名,但不包括未命中事件m

订阅指定事件

        在完成配置后,可通过SUBSCRIBE命令订阅指定信道实现对一个或多个指定事件的订阅。例如通过订阅__keyevent@0__:expired实现订阅数据库0中的键过期事件例如示例1:订阅键过期事件。

        订阅的信道的格式为__<type>@<db>__:<event>,其包括了事件类型(keyspace或keyevent)、数据库(例如数据库0)以及事件(例如expired)三部分组成。对应事件的名称,可参考下文命令事件章节。

        另外,也可以通过PSUBSCRIBE命令订阅一个或多个复合正则表达式匹配的信道。例如通过订阅__key*@*__:*订阅Redis中所有数据库中的所有事件。

命令事件

Redis为许多命令提供了不同的事件,在本文中将选择其中部分命令及其对应的事件进行介绍:

  • DEL:在某个键被删除时产生del事件
  • EXPIRE、PEXPIRE、EXPIREAT以及PEXPIREAT:当设置正数过期时间或未来时间的时间戳,则产生expire事件,否则产生del事件(将立即被删除)
  • SET以及同类的SETEX、SETNX、GETSET:产生set事件,若使用SETEX则也会产生expire事件
  • MSET:将会为每个键都产生一个set事件
  • LPUSH、LPUSHX与RPUSH、RPUSHX:根据插入的方向分别产生lpush或rpush事件
  • RPOP、LPOP:分别产生rpop与lpop事件,若移出的是列表中的最后一个元素,将会同时产生del事件
  • LSET:产生lset事件
  • LREM:产生lrem事件,同样若移除的元素为列表中的最后一个元素时将同时产生del事件
  • HSET、HSETNX以及HMSET:产生一个hset事件
  • HDEL:产生一个hdel事件,且在移除后哈希表为空的情况下产生del事件
  • SADD:产生一个sadd事件
  • SREM:产生一个srem事件,且在移除后集合为空的情况下产生del事件
  • SMOVE:原键中产生srem事件且在目标键中产生sadd事件
  • SINTERSTORE、SUNIONSTORE、SDIFFSTORE:分别产生sinterstore、sunionstore以及sdiffstore事件,且在结果为空集且目标键存在的情况下,将会产生del事件
  • ZADD:无论添加几个元素都只产生一个zadd事件
  • ZREM:无论移除几个元素都只产生一个zrem事件,当移除后有序集合为空时产生del事件
  • XADD:产生xadd事件,若使用MAXLEN子命令可能会同时产生xtrim事件
  • XDEL:产生xdel事件
  • PERSIST:如果对应的键所关联的过期事件成功被移除,则产生persist事件
  • 在键发生过期时产生expired事件
  • 在达到maxmemory设定的内存值后发生键淘汰时产生evicted事件


关于更多的命令相关事件,请参考keyspace notification相关文档:
Redis keyspace notifications | Redis

实例


配置

springboot-maven配置
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

        springboot提供了spring-boot-starter-data-redis,里面也已经封装了spring中redis的配置spring-data-redis。


项目代码

http请求访问时,service层函数内部调用redisTemplate在Redis里设置一个注销key

例子1:多份数据,关键数据在value中

代码例子如下:

个人平台账号可申请注销
 

    @Autowiredprivate RedisTemplate redisTemplate;public void deleteUser(String key, String value){// 关键代码redisTemplate.opsForValue().set(key, value);// 设置七天注销时间redisTemplate.expire(key, DEFAULT_DELETE_TIME, TimeUnit.SECONDS);// 监听过期key,获取value使用String tempKey = key + "_2";redisTemplate.opsForValue().set(tempKey, value);}


注意:

         redis里面设置了两个key,原因在于:key过期之后,在ResdisExpirationListener 的 onMessage函数中 无法拿到key对应的value,所以设置两个,key不同但是value一样。这个value是为了key到期之后触发想要的任务函数。

申请注销后七天内可撤销注销

public void withdrawDeleteUser(String key) {redisTemplate.delete(key);// 删除临时keyredisTemplate.delete(key + "_2");
}


注意: redis里面设置的两个key都必须删除新建RedisListenerConfig.class

@Component
public class RedisListenerConfig {@Bean@Primarypublic RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory){RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(connectionFactory);return container;}
}

新建ResdisExpirationListener.class
        业务最主要在onMessage函数,key过期后会自动触发该函数,message信息是key,根据redisTemplate和另外设置的一个key拿到value,这个value在触发的定时任务函数里面用到了,所以必须拿到。

@Slf4j
@Component
@Transactionalpublic class ResdisExpirationListener extends KeyExpirationEventMessageListener {@AutowiredUserService userService;@Autowiredprivate RedisTemplate redisTemplate;public ResdisExpirationListener(RedisMessageListenerContainer listenerContainer) {super(listenerContainer);}@Overridepublic void onMessage(Message message, byte[] pattern){String messageKey = message.toString();// 业务实现if(messageKey.contains(RedisKey.DELETE_USER)){String userKey = redisTemplate.opsForValue().get(messageKey + "_2").toString();Long userId = Long.parseLong(userKey);// 触发定时任务userService.deleteUserProcess(userId);// 删除临时keyredisTemplate.delete(messageKey + "_2");}}
}

好了,可以跑跑看了。

例子2:关键数据在key中

 添加redis监听

package com.cpl.tsl.listener;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;import java.util.Date;/*** RedisKey键监听以及业务逻辑处理** @author: lll* @date: 2022年03月07日 14:03:49*/
@Component
public class RedisTaskListener extends KeyExpirationEventMessageListener {private static final Logger logger = LoggerFactory.getLogger(RedisTaskListener.class);@Value("${applicationName:tsl}")private String applicationName;/*** @param listenerContainer 监听器*/public RedisTaskListener(RedisMessageListenerContainer listenerContainer) {super(listenerContainer);}@Overridepublic void onMessage(Message message, byte[] pattern) {String expiredKey = message.toString();// 将拿到的过期键使用之前拼接时的特殊符号分割成字符数组String[] expiredKeyArr = expiredKey.split("\\|");String businessSign = expiredKeyArr[0].toString();String expiredTimeSign = expiredKeyArr[1].toString();logger.info(businessSign +":"+ expiredTimeSign);Date date = new Date();// 只有本业务才执行以下操作if (businessSign.equals(applicationName + ":ORDERINFO")) {logger.info("订单超时,已取消");} else {logger.error("非订单业务不做处理");}}
}

添加controller调动接口

package com.cpl.tsl.controller;import com.cpl.tsl.utils.RedisUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import java.util.Date;
import java.util.UUID;/*** 测试类**/
@RestController
@RequestMapping("/")
@Api(tags = "测试模块")
public class TestController {private static final Logger logger = LoggerFactory.getLogger(TestController.class);@Resourceprivate RedisTemplate<String, String> template;@Resourceprivate RedisUtil redisUtil;@Value("${applicationName:tsl}")private String applicationName;/*** redis设置定时key,模拟订单下单,超时提醒或者去掉订单**/@RequestMapping(value = "/putkeys", method = RequestMethod.POST)@ApiOperation(value = "测试redis存储参数", notes = "测试redis存储参数")public String putRedisTaskKeys() {/*** 存入订单信息*/Date date = new Date();//设置超时时间30秒Long overTime = new Long(30);//创建订单号String orderNo = UUID.randomUUID().toString();//订单信息String orderInfo = "这是订单号为:" + orderNo + " 的订单,价格是:2000元,下单时间是:" + date;//redis keyString redisKey = applicationName + ":ORDERINFO|" + orderNo;redisUtil.set(redisKey, orderInfo, overTime);logger.info("下单时间:" + date);logger.info("订单的redisKey " + redisKey + " 订单信息 " + orderInfo);return "下单成功";}/*** 手动处理订单,从redis移除订单**/@RequestMapping(value = "/removeKeys", method = RequestMethod.POST)@ApiOperation(value = "测试redis移除参数", notes = "测试redis移除参数")public String removeRedisTaskKeys(@ApiParam(name = "orderNo", value = "订单号", required = true) @RequestParam("orderNo") String orderNo) {/*** 处理订单*///拼接redis keyString redisKey = applicationName + ":ORDERINFO|" + orderNo;//删除redis keyredisUtil.del(redisKey);logger.info("订单redisKey " + redisKey + " 已处理");return "处理完成";}}

例子3

application.yml

redis:
    localhost: localhost
    port: 6379 
    database: 7
    password:
    # 过期事件订阅,接收7号数据库中所有key的过期事件
    listen-pattern: __keyevent@7__:expired

Redis 事件广播配置类

import com.coisini.springbootlearn.core.listener.RedisMessageListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;

@Configuration
public class RedisListenerConfiguration {

    @Value("${spring.redis.listen-pattern}")
    public String pattern;

    @Bean
    public RedisMessageListenerContainer listenerContainer(RedisConnectionFactory redisConnection) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnection);

        /**
         * Topic是消息发布(Pub)者和订阅(Sub)者之间的传输中介
         */
        Topic topic = new PatternTopic(this.pattern);

        container.addMessageListener(new RedisMessageListener(), topic);
        return container;
    }
}

Redis 事件广播监听器

import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;

public class RedisMessageListener implements MessageListener {

    /**
     * Redis 事件监听回调
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        byte[] body = message.getBody();

        String expiredKey = new String(body);

        System.out.println("监听到已过期的key:" + expiredKey);

        /**
         * 监听到过期事件回调
         * TODO:
         */

    }
}


测试接口
@RestController
@RequestMapping("/redis")
public class RedisController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping(value = "/setExpiredVal")
    public String setExpiredVal(@RequestParam String name) {
        // 设置 20s 后过期
        redisTemplate.opsForValue().set("name", name, 20, TimeUnit.SECONDS);
        return "setVal is ok";
    }

}

优缺点


在 Spring Boot 中整合 Redis 监听订单超时主要的优缺点:

优点

  1. 实时性:使用 Redis 来监听订单超时,可以实现实时性处理。当订单超时时,处理操作可以立即触发,而不需要定期轮询数据库或其他方式。

  2. 高性能:Redis 是一个内存数据库,因此具有高性能。它能够快速存储和检索数据,适合用于订单超时处理。

  3. 可扩展性:Redis 支持分布式部署,因此可以轻松扩展应用程序以处理更多订单。可以使用 Redis Sentinel 或 Redis Cluster 来实现高可用性和负载均衡。

  4. 减轻数据库压力:将订单超时的检查和处理从数据库转移到 Redis,可以减轻数据库服务器的负载,因为不再需要频繁地查询数据库。

  5. 简化代码:Redis 提供了内置的过期键和发布/订阅功能,这些功能使订单超时的处理逻辑更加简单和可维护。

缺点

  1. 单一点故障:如果 Redis 实例发生故障,可能导致订单超时处理不可用。为了解决这个问题,可以使用 Redis Sentinel 或 Redis Cluster 来提高可用性。

  2. 不适合持久性数据:Redis 是一个内存数据库,不适合用于持久性数据存储。如果订单数据需要长期保留,仍然需要在数据库中保留订单信息。

  3. 配置和维护:Redis 需要一些配置和维护工作,包括备份、监控、调整内存限制等。这可能需要额外的管理工作。

  4. 消息队列的竞争条件:如果多个实例同时处理订单超时,可能会引发竞争条件,需要在代码中进行处理。

  5. 性能成本:虽然 Redis 具有高性能,但在大规模订单处理时,可能需要更多的 Redis 实例和更强大的硬件,这可能带来一些成本。

参考

Redis键空间通知详解_notify-keyspace-events-CSDN博客

Spring boot整合 redis实现订单超时处理 - 掘金

https://www.cnblogs.com/vergilyn/p/7285457.html

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

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

相关文章

Unity Mono加密解决方案

Unity Mono 是 Unity 引擎默认的脚本运行时环境&#xff0c;在游戏开发中扮演着重要的角色。Mono 由跨平台的开源 .NET 框架实现&#xff0c;它允许开发者使用 C# 等编程语言编写游戏逻辑。凭借简单易用的开发环境和高效的脚本编译速度&#xff0c;得到了众多游戏的青睐。 在 …

【Maven教程】(十二):版本管理 ——版本号定义约定及相关概念,自动化版本发布与创建分支,GPG签名 ~

Maven 版本管理 1️⃣ 版本管理的概念2️⃣ Maven 的版本号定义约定3️⃣ 主干、标签与分支4️⃣ 自动化版本发布5️⃣ 自动化创建分支6️⃣ GPG签名6.1 GPG 及其基本使用6.2 Maven GPG Plugin &#x1f33e; 总结 一个健康的项目通常有一个长期、合理的版本演变过程。例如JUn…

win10 + vs2017 + cmake3.17编译OSG-3.4.1

1. 下载文件 主要用到4个文件 1&#xff09;OSG-3.4.1源码2&#xff09;OSG第三方依赖库3&#xff09;OSG示例数据4&#xff09;cmake-3.17 我已经准备好了&#xff0c;大家可以自行下载。下载路径&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1E3YESh0T9KPlJJe2…

利用Python+selenium技术,实现浏览器基本操作详解,代码有详细注释

首先&#xff0c;需要安装selenium库和对应的浏览器驱动程序。以Chrome浏览器为例&#xff0c;可以使用以下命令安装selenium和chromedriver&#xff1a; pip install selenium然后&#xff0c;需要下载对应版本的chromedriver&#xff0c;并将其添加到环境变量中。下载地址&a…

代码随想录二刷 |二叉树 |144.二叉树的前序遍历

代码随想录二刷 &#xff5c;二叉树 &#xff5c;144.二叉树的前序遍历 题目描述解题思路代码实现递归法迭代法 题目描述 144.二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输…

godot 报错Unable to initialize Vulkan video driver解决

版本 godot 4.2.1 现象 godot4.2.1 默认使用vulkan驱动&#xff0c;如果再不支持vulkan驱动的主机上&#xff0c;进入引擎编辑器将报错如下 解决 启动参数添加 –rendering-driver opengl3 即可进入引擎编辑器 此时运行项目仍然会报错无法初始化驱动 在项目设置中配置编…

面试__Java常见异常有哪些?

java.lang.IllegalAccessError&#xff1a;违法访问错误。当一个应用试图访问、修改某个类的域&#xff08;Field&#xff09;或 者调用其方法&#xff0c;但是又违反域或方法的可见性声明&#xff0c;则抛出该异常。 java.lang.InstantiationError&#xff1a;实例化错误。当…

vue实现公式编辑器组件

实现方式一 1、效果图 2、实现代码 组件弹框实现 样式自己调整 公式的数字与汉字元素、符号 建立元素表 动态获取 完整代码&#xff08;calculate.vue&#xff09; <template><div id"formulaPage"><divref"formulaView"class"f…

记录今日将C语言的Windows程序更改为python语言Windows程序,实现子窗口控制,类似微信程序框架最简单的原型

基本思路 为什么要选择python制作Windows应用程序&#xff0c;主要就是源代码直接展示&#xff0c;发现问题随时修改&#xff0c;同时可以不断增加新的功能方便。 由于C语言的Windows程序中结构类型在python中不能使用&#xff0c; 因此我们按照ctypes模块指导意见继承structu…

Verilog自学还是报班?

FPGA作为国内领先的芯片产品&#xff0c;和传统芯片相比并不局限于单纯的研究和设计芯片&#xff0c;而是针对多种领域的产品通过特定的芯片模型进行优化设计。FPGA本身也构成了典型的半定制电路&#xff0c;涵盖了数字管理模块、输入、输出等单元。 FPGA最大的特点是可以在同…

Unity_C#中使用protobuf

Unity_C#中使用protobuf 下载官方protobuf地址&#xff1a; https://github.com/protocolbuffers/protobuf/releaseshttps://links.jianshu.com/go?tohttps%3A%2F%2Fgithub.com%2Fprotocolbuffers%2Fprotobuf%2Freleases protobuf-c#源码生成dll&#xff0c;导入unity 1.…

双向无线功率传输系统MATLAB仿真

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 模型简介&#xff1a; 初级侧转换器通过双向 AC/DC 转换器从电网获取电力&#xff0c;并由直流线电压 Vin 供电&#xff0c;而拾波侧被视为连接到 EV&#xff0c;并由连接到任一存储的单独直流源 Vout 表示或…

N26:构建无缝体验的平台工程之路-Part 2

​ 在第一​​​​​​部分&#xff0c;我们介绍了 N26 团队为达成 “在 Day 1 实现轻松部署” 的目标而设定的战略规划和开发人员体验图&#xff0c;在这一部分&#xff0c;我们将带您了解该团队如何构建最简可行平台以及该平台如何运作。 01 计划构建最简可行平台 我们通…

LeetCode //C - 605. Can Place Flowers

605. Can Place Flowers You have a long flowerbed in which some of the plots are planted, and some are not. However, flowers cannot be planted in adjacent plots. Given an integer array flowerbed containing 0’s and 1’s, where 0 means empty and 1 means no…

高通开发系列 - 功耗问题之添加CPU Idle和Hotplug的功能

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 返回:专栏总目录 目录 概述CPU IdleCPU TopologyCPU Idle DriverCPU Idle GovernorCPU的hotplug函数

docker相关的命令

镜像管理命令 说明 docker images 查看本机镜像 docker search 镜像名称 从官方仓库查找镜像 docker pull 镜像名称:标签 下载镜像 docker push 镜像名称:标签 上传镜像 docker save 镜像名称:标签 -o 备份镜像名称.tar 备份镜像为tar包 docker load -i 备份镜像名…

ifconfig命令和ip命令

1.ifconfig ifconfig是Linux中用于显示和配置网络接口参数的命令。以下是一些常用的ifconfig命令选项&#xff1a; 查看所有网络接口的信息&#xff1a;ifconfig -a查看指定网络接口的信息&#xff0c;例如eth0&#xff1a;ifconfig eth0配置网络接口的IP地址、子网掩码和广播…

TCP/UDP 协议

目录 一.TCP协议 1.介绍 2.报文格式 ​编辑 确认号 控制位 窗口大小 3.TCP特性 二.TCP协议的三次握手 1.tcp 三次握手的过程 三.四次挥手 2.有限状态机 四.tcp协议和udp协议的区别 五.udp协议 UDP特性 六.telnet协议 一.TCP协议 1.介绍 TCP&#xff08;Transm…

DBCA创建RAC的过程截图

以下错误是由于配置的内存较大&#xff0c;而大页&#xff08;HugePage&#xff09;配置较小导致&#xff0c;调整大页后就好了。