阅读本文大概需要 5 分钟。

来自:http://juejin.im/post/6844904158227595271

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 seconds50 parallel clients3 bytes payloadkeep alive: 1host configuration "save": 3600 1 300 100 60 10000host configuration "appendonly": nomulti-thread: no

其实这里有些不严谨 benchmark 线程不应该在 Docker 容器内部运行 . 跑分的时候大概 benchmark 和 redis 主线程各自持有 50%CPU测试代码如下:

@Service
@Slf4j
public class RedisJob {@Autowiredprivate 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");@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic 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 以及滞后时间

@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);// 设置监听的Topicreturn 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'的。

redis依赖_请勿过度依赖 Redis 的过期监听相关推荐

  1. redis开启过期监听

    java项目中,场景:订单没有付款到期取消订单,使用的是redis过期监听来做的,做个笔记!首先使用该功能需要下载2.8.0及以上的版本,这一部分详细内容可以访问redis官网:http://redi ...

  2. Redis自动过期机制之key的过期监听(7)

    Redis中的自动过期机制 前言 1.使用Redis Key自动过期机制 2.Springboot整合key过期监听 2.1. 创建表 order_number 2.2核心代码 2.2.1 核心代码 ...

  3. Springboot redis多数据源过期监听案例

    在上一篇Springboot redis多数据源案例中,我们实现了springboot下多数据源的案例. 本篇博客在此基础上,实现多数据源过期监听事件: 监听器配置类: package com.xin ...

  4. springboot集成redis,及过期监听

    redis配置 package cn.jianml.redis.config;import cn.jianml.redis.listener.RedisMessageListener; import ...

  5. docker redis 配置文件_基于Docker搭建Redis一主两从三哨兵

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:oscarwin juejin.im/post/5d26b03de51d454fa3 ...

  6. redis日志_为什么我的Redis这么“慢”?

    Redis 作为内存数据库,拥有非常高的性能,单个实例的 QPS 能够达到 10W 左右. 图片来自 Pexels 但我们在使用 Redis 时,经常时不时会出现访问延迟很大的情况,如果你不知道 Re ...

  7. python redis 操作_使用Python操作redis

    在使用python操作redis之前,需要先安装redis库: pip install redis. 创建连接方式: StrictRedis:实现大部分官方命令. Redis:是StrictRedis ...

  8. redis过期监听性能_基于Redis的延迟处理

    延迟处理是一个非常常用的一个功能; 例如, 下单成功后,在30分钟内没有支付,自动取消订单; 延迟队列便是延迟处理中最常见的实现方式; 先一起看下JDK中延迟队列是如何实现的. JUC的DelayQu ...

  9. maven插件依赖_当Maven依赖插件位于

    maven插件依赖 问题: 我们进行了一个集成测试,该测试创建了一个Spring ClassPathXmlApplicationContext ,同时这样做导致NoSuchMethodError爆炸. ...

最新文章

  1. [ZT]如何取得客户端的Windows登录用户名?
  2. 黑盒测试和白盒的区别,有哪些常见的白盒黑盒测试方法
  3. 编程入门python语言是多大孩子学的-什么是少儿Python编程?这一篇就够啦!
  4. 采购交货期延误的原因分析
  5. spring boot多环境配置
  6. 一开电脑都是广告,请问怎么永久关闭?
  7. python自动化控制_python用于自动化控制编程
  8. phpcms后台登陆页面存放地址
  9. 神舟K650c i7(W350STQ)上成功装好Mac OS X 10.9,兼谈如何安装WinXP、7、8.1、OSX、Ubuntu五系统(Chameleon、MBR)
  10. 【java实现定时自动发送QQ消息】
  11. 拉普拉斯金字塔图像融合原理
  12. dalvik下替换so简单dump出梆梆加固保护的odex
  13. JavaScript对象的键值对
  14. 2021年数维杯数学建模A题外卖骑手的送餐危机求解全过程文档及程序
  15. RT-Thread 入门学习笔记 - 熟悉全局中断的操作
  16. linux学习笔记 linux内核6.0.2目录结构
  17. 计算字符串长度.length()和数组长度.length的区别
  18. Docker ROS dbus[xxx]: The last reference on a connection was dropped without closing the connection
  19. Fiddler4抓包工具的使用
  20. java nio下载_Java Nio 多线程网络下载

热门文章

  1. 当Python的lambda表达式遇上变量作用域
  2. 计算机申报专业怎么写,给申请计算机专业的学生的建议
  3. springboot脚本启动bat_SpringBoot修改JVM参数(内置Tomcat命令行启动和IDEA工具配置修改)...
  4. 树的定义/性质/实现
  5. 在ubuntu14.04中安装及测试OpenCV
  6. map函数python返回值,Python中map函数使用
  7. 337. 打家劫舍 III(JavaScript)
  8. linux打开mysql某张表_Linux——MySQL多表连接
  9. android 模仿uc标签页,android模仿UC首页天气效果
  10. 怎么学好python leetcode的题目太难了_为什么leetcode中的python解法过于pythonic,而忽略了算法题主要关注的复杂度问题?...