网站建设丿金手指专业,营销型企业网站怎么建站,做视频有收益的网站,请人做网站收费最近研究了一下“redis定时触发”#xff0c;网上查了查资料#xff0c;这里记录一下。 从Redis 2.8.0开始#xff0c;Redis加入了发布/订阅模式以及键空间消息提醒#xff08;keyspace notification#xff09;功能。键空间消息提醒提供了允许客户端通过订阅指定信道获取…最近研究了一下“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配置可参考下面的描述
KKeyspace事件将会以__keyspacedb__作为事件的前缀EKeyevent事件将会以__keyeventdb__作为事件的前缀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命令订阅指定信道实现对一个或多个指定事件的订阅。例如通过订阅__keyevent0__:expired实现订阅数据库0中的键过期事件例如示例1:订阅键过期事件。 订阅的信道的格式为__typedb__: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 groupIdorg.springframework.boot/groupId artifactIdspring-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 {BeanPrimarypublic 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 RedisTemplateString, 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: __keyevent7__:expiredRedis 事件广播配置类
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 监听订单超时主要的优缺点
优点 实时性使用 Redis 来监听订单超时可以实现实时性处理。当订单超时时处理操作可以立即触发而不需要定期轮询数据库或其他方式。 高性能Redis 是一个内存数据库因此具有高性能。它能够快速存储和检索数据适合用于订单超时处理。 可扩展性Redis 支持分布式部署因此可以轻松扩展应用程序以处理更多订单。可以使用 Redis Sentinel 或 Redis Cluster 来实现高可用性和负载均衡。 减轻数据库压力将订单超时的检查和处理从数据库转移到 Redis可以减轻数据库服务器的负载因为不再需要频繁地查询数据库。 简化代码Redis 提供了内置的过期键和发布/订阅功能这些功能使订单超时的处理逻辑更加简单和可维护。
缺点 单一点故障如果 Redis 实例发生故障可能导致订单超时处理不可用。为了解决这个问题可以使用 Redis Sentinel 或 Redis Cluster 来提高可用性。 不适合持久性数据Redis 是一个内存数据库不适合用于持久性数据存储。如果订单数据需要长期保留仍然需要在数据库中保留订单信息。 配置和维护Redis 需要一些配置和维护工作包括备份、监控、调整内存限制等。这可能需要额外的管理工作。 消息队列的竞争条件如果多个实例同时处理订单超时可能会引发竞争条件需要在代码中进行处理。 性能成本虽然 Redis 具有高性能但在大规模订单处理时可能需要更多的 Redis 实例和更强大的硬件这可能带来一些成本。 参考
Redis键空间通知详解_notify-keyspace-events-CSDN博客
Spring boot整合 redis实现订单超时处理 - 掘金
https://www.cnblogs.com/vergilyn/p/7285457.html