请勿过度依赖Redis的过期监听!!
作者:迪壳
Redis 过期监听场景
业务中有类似等待一定时间之后执行某种行为的需求 , 比如 30 分钟之后关闭订单 . 网上有很多使用 Redis 过期监听的 Demo , 但是其实这是个大坑 , 因为 Redis 不能确保 key 在指定时间被删除 , 也就造成了通知的延期 . 不多说 , 跑个测试
测试情况
先说环境 , redis 运行在 Docker 容器中 , 分配了 一个 cpu 以及 512MB 内存, 在 Docker 中执行 redis-benchmark -t set -r 100000 -n 1000000 结果如下:
\====== SET ======
1000000 requests completed in 171.03 seconds
50 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 3600 1 300 100 60 10000
host configuration "appendonly": no
multi-thread: no
其实这里有些不严谨 benchmark 线程不应该在 Docker 容器内部运行 . 跑分的时候大概 benchmark 和 redis 主线程各自持有 50%CPU
测试代码如下:
@Service
@Slf4j
public class RedisJob {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public LocalDateTime end = LocalDateTime.of(LocalDate.of(2020, 5, 12), LocalTime.of(8, 0));
@Scheduled(cron = "0 56 \* \* \* ?")
public void initKeys() {
LocalDateTime now = LocalDateTime.now();
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
log.info("开始设置key");
LocalDateTime begin = now.withMinute(0).withSecond(0).withNano(0);
for (int i = 1; i < 17; i++) {
setExpireKey(begin.plusHours(i), 8, operations);
}
log.info("设置完毕: " + Duration.between(now, LocalDateTime.now()));
}
private void setExpireKey(LocalDateTime expireTime, int step, ValueOperations<String, String> operations) {
LocalDateTime localDateTime = LocalDateTime.now().withNano(0);
String nowTime = dateTimeFormatter.format(localDateTime);
while (expireTime.getMinute() < 55) {
operations.set(nowTime + "@" + dateTimeFormatter.format(expireTime), "A", Duration.between(expireTime, LocalDateTime.now()).abs());
expireTime = expireTime.plusSeconds(step);
}
}
}
大概意思就是每小时 56 分的时候 , 会增加一批在接下来 16 小时过期的 key , 过期时间间隔 8 秒 , 且过期时间都在 55 分之前
@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
public DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public void onMessage(Message message, byte\[\] pattern) {
String keyName = new String(message.getBody());
LocalDateTime parse = LocalDateTime.parse(keyName.split("@")\[1\], dateTimeFormatter);
long seconds = Duration.between(parse, LocalDateTime.now()).getSeconds();
stringRedisTemplate.execute((RedisCallback<Object>) connection -> {
Long size = connection.dbSize();
log.info("过期key:" + keyName + " ,当前size:" + size + " ,滞后时间" + seconds);
return null;
});
}
}
这里是监测到过期之后打印当前的 dbSize 以及滞后时间
搜索公纵号:[MarkerHub][],关注回复[ vue ]获取前后端入门教程!
@Bean
public RedisMessageListenerContainer configRedisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(100);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(3600);
executor.setThreadNamePrefix("redis");
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER\_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
// 设置Redis的连接工厂
container.setConnectionFactory(connectionFactory);
// 设置监听使用的线程池
container.setTaskExecutor(executor);
// 设置监听的Topic
return container;
}
设置 Redis 的过期监听 以及线程池信息 ,
最后的测试结果是当 key 数量小于 1 万的时候 , 基本上都可以在 10s 内完成过期通知 , 但是如果数量到 3 万 , 就有部分 key 会延迟 120s . 顺便贴一下我最新的日志
2020-05-13 22:16:48.383 : 过期key:2020-05-13 11:56:02@2020-05-13 22:14:08 ,当前size:57405 ,滞后时间160
2020-05-13 22:16:49.389 : 过期key:2020-05-13 11:56:02@2020-05-13 22:14:32 ,当前size:57404 ,滞后时间137
2020-05-13 22:16:49.591 : 过期key:2020-05-13 10:56:02@2020-05-13 22:13:20 ,当前size:57403 ,滞后时间209
2020-05-13 22:16:50.093 : 过期key:2020-05-13 20:56:00@2020-05-13 22:12:32 ,当前size:57402 ,滞后时间258
2020-05-13 22:16:50.596 : 过期key:2020-05-13 07:56:03@2020-05-13 22:13:28 ,当前size:57401 ,滞后时间202
2020-05-13 22:16:50.697 : 过期key:2020-05-13 20:56:00@2020-05-13 22:14:32 ,当前size:57400 ,滞后时间138
2020-05-13 22:16:50.999 : 过期key:2020-05-13 19:56:00@2020-05-13 22:13:44 ,当前size:57399 ,滞后时间186
2020-05-13 22:16:51.199 : 过期key:2020-05-13 20:56:00@2020-05-13 22:14:40 ,当前size:57398 ,滞后时间131
2020-05-13 22:16:52.205 : 过期key:2020-05-13 15:56:01@2020-05-13 22:16:24 ,当前size:57397 ,滞后时间28
2020-05-13 22:16:52.808 : 过期key:2020-05-13 06:56:03@2020-05-13 22:15:04 ,当前size:57396 ,滞后时间108
2020-05-13 22:16:53.009 : 过期key:2020-05-13 06:56:03@2020-05-13 22:16:40 ,当前size:57395 ,滞后时间13
2020-05-13 22:16:53.110 : 过期key:2020-05-13 20:56:00@2020-05-13 22:14:56 ,当前size:57394 ,滞后时间117
2020-05-13 22:16:53.211 : 过期key:2020-05-13 06:56:03@2020-05-13 22:13:44 ,当前size:57393 ,滞后时间189
2020-05-13 22:16:53.613 : 过期key:2020-05-13 15:56:01@2020-05-13 22:12:24 ,当前size:57392 ,滞后时间269
2020-05-13 22:16:54.317 : 过期key:2020-05-13 15:56:01@2020-05-13 22:16:00 ,当前size:57391 ,滞后时间54
2020-05-13 22:16:54.517 : 过期key:2020-05-13 18:56:00@2020-05-13 22:15:44 ,当前size:57390 ,滞后时间70
2020-05-13 22:16:54.618 : 过期key:2020-05-13 21:56:00@2020-05-13 22:14:24 ,当前size:57389 ,滞后时间150
2020-05-13 22:16:54.819 : 过期key:2020-05-13 17:56:00@2020-05-13 22:14:40 ,当前size:57388 ,滞后时间134
2020-05-13 22:16:55.322 : 过期key:2020-05-13 10:56:02@2020-05-13 22:13:52 ,当前size:57387 ,滞后时间183
2020-05-13 22:16:55.423 : 过期key:2020-05-13 07:56:03@2020-05-13 22:14:16 ,当前size:57386 ,滞后时间159
可以看到 , 当数量到达 5 万的时候 , 大部分都已经滞后了两分钟 , 对于业务方来说已经完全无法忍受了
总结
可能到这里 , 你会说 Redis 给你挖了一个大坑 , 但其实这些都在文档上写的明明白白
- How Redis expires keys:https://redis.io/commands/expire#how-redis-expires-keys
- Timing of expired events:https://redis.io/topics/notifications#timing-of-expired-events
尤其是在 Timing of expired events 中 , 明确的说明了 "Basically expired events are generated when the Redis server deletes the key and not when the time to live theoretically reaches the value of zero.", 这两个文章读下来你会感觉 , 卧槽 Redis 的过期策略其实也挺'Low'的
其实公众号看多了 , 你会发现大部分 Demo 都是互相抄来抄去 , 以及翻译官方 Demo . 建议大家还是谨慎一些 , 真要使用的话 , 最好读一下官方文档 , 哪怕用百度翻译也要有一些自己的理解 .
文章比较枯燥 , 感谢大家耐心阅读 , 如有建议 恳请留言.
(完)
关注公众号:java宝典
请勿过度依赖Redis的过期监听!!的更多相关文章
- 领导:谁再用redis过期监听实现关闭订单,立马滚蛋!
日前拜读阿牛老师的大作 领导:谁再用定时任务实现关闭订单,立马滚蛋! 发现其方案有若干瑕疵,特此抛砖引玉讨论一二. 在电商.支付等领域,往往会有这样的场景,用户下单后放弃支付了,那这笔订单会在指定的时 ...
- spring boot 实现redis 的key的过期监听,执行自己的业务
最近几天进一步了解了一下redis,发现了key的过期监听功能,实现方式如下: 在redis的配置文件 redis.conf 中找到"EVENT NOTIFICATION"模块, ...
- Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程
待解决的问题 Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程 解决办法 为spring session添加spr ...
- redis过期key监听事件
目录 redis安装 docker拉取 启动 redis 配置 命令监听 问题 程序监听 具体监听类 效果 总结 redis常用语缓存操作,但是redis功能不仅仅于此.今天我们来看看redis的ke ...
- springboot使用Redis,监听Redis键过期的事件设置与使用代码
我使用的是Windows下的Redis服务,所以一下Redis设置都是在Windows平台进行. 1.修改Redis配置文件 1.1:Windows下的Redis存在两个配置文件 修改带有servic ...
- Redis集群下过期key监听
1. 前言 在使用redis集群时,发现过期key始终监听不到.网上也没有现成的解决方案.于是想,既然不能监听集群,那我可以建立多个redis连接,分别对每个redis的key过期进行监听.以上做法可 ...
- SpringBoot监听redis过期key
开启过期监听 vim /etc/redis.conf 取消notify-keyspace-events Elg的注释 pom.xml 添加: <dependency> <groupI ...
- JAVA实现redis超时失效key 的监听触发
过期事件通过Redis的订阅与发布功能(pub/sub)来进行分发. 而对超时的监听呢,并不需要自己发布,只有修改配置文件redis.conf中的:notify-keyspace-events Ex, ...
- springboot redis 监听过期key值事件
redis 中的key值过期后,触发通知事件 1.创建springboot工程,创建监听类 maven配置 <dependencies> <dependency> <gr ...
随机推荐
- Redis 三大缓存
Redis 三大缓存 过去的有些事情不一定要忘记,但一定要放下. 背景:Redis 三大缓存:缓存穿透.缓存击穿.缓存雪崩,是Redis 面试必须要掌握的东西. 一.缓存穿透 1.概念简述 ...
- 使用phoenix连接hbase
hbase本身不支持SQL查询,为了实现这个功能,引入了phoenix,通过它可以实现hbase的sql查询.这里记录下如何配置并使用phoenix来操作hbase. 1. 下载地址 phoenix下 ...
- PJzhang:vulnhub靶机sunset系列SUNSET:TWILIGHT
猫宁~~~ 地址:https://www.vulnhub.com/entry/sunset-twilight,512/ 关注工具和思路. nmap 192.168.43.0/24靶机IP192.168 ...
- 关于JSON的零碎小知识
1.ali的fastjson在将实体类转成jsonString的时候,一些首字母大写的字段会自动修改为小字母,这种字段加 @JsonProperty(value = "DL_id" ...
- IPSecVPN介绍 & (Cisco Packet Tracer)IPSecVPN实验演示
一.基础知识 VPN(Virtual Private Network)虚拟专有网络,即虚拟专网.VPN可以实现在不安全的网络上,安全的传输数据,好像专网!VPN只是一个技术,使用PKI技术,来保证数据 ...
- Dominate【操作系统的经典算法】
此篇文章我们来谈一谈操作系统中都出现过哪些算法,请欣赏下图 ↓ 进程和线程管理中的算法 进程和线程在调度时候出现过很多算法,这些算法的设计背景是当一个计算机是多道程序设计系统时,会频繁的有很多进程或者 ...
- Java 审计之XXE篇
Java 审计之XXE篇 0x00 前言 在以前XXE漏洞了解得并不多,只是有一个初步的认识和靶机里面遇到过.下面来 深入了解一下该漏洞的产生和利用. 0x01 XXE漏洞 当程序在解析XML输入时, ...
- mysql-1-select
#进阶1:基础查询 /* 语法: SELECT 查询列表 FROM 表名; 特点: 1.查询列表可以是:表中字段.常量值.表达式.函数 2.查询的结果是一个虚拟的表格 */ USE myemploye ...
- AES加密 Pkcs7 (BCB模式) java后端版本与JS版本对接
1.BCB模式是需要设置iv偏移量和Key值,这两个值就像账号和密码一样,当这两个值一致时才能确保加密和解密的数据一致.(ps:这两个值千万不能暴露出去哦!) 2.JAVA版本代码: 这里的iv偏移量 ...
- C++字符串的输入输出整理
最近在跟一门北大C++程序设计的慕课,openjudge上做到一道题,要求定义一种能够输入输出学生姓名,年龄,学号和学年成绩的类.比较特别的是输入的形式是以逗号隔开的一长串字符串. 我用的方法通过是通 ...
