如果项目中业务系统涉及到类似于订单超时需要触发一些逻辑这种事,如果通过本地定时或分布式任务调度中心来控制,由于每次都要查询是否满足条件,对业务系统性能影响特别大。查了下资料,主流使用redis监听过期key来实现比较好点。

注意redis的删除过期key也不是很精确的,会有延迟的。默认是1秒10次去抽查100设置了有效期的key,如果过期的key总数高于25,会继续循环处理直到低于25或超过默认约束的时间。有兴趣的可以查看这篇文章了解redis删除key的策略详情。

除了此方案 还有一种可行的方案,通过粗粒度定时和本地延迟队列DelayQueue来实现。比如,5分钟查询一次将在5分钟内过期的数据放到本地延迟队列里,然后本地延迟队列来触发毫秒级的响应。


照例,先上核心代码

环境:

JDK1.8

springboot 2.3.0.RELEASE

package com.xxx.configuration;import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class,  MessageListener.class })
@AutoConfigureAfter({ JacksonAutoConfiguration.class,RedisAutoConfiguration.class })
public class RedisAutoConfiguration {/*** 独立redis 非集群*如果存在spring.redis.host配置 代表是单例 非集群*/@Configuration@ConditionalOnExpression("!'${spring.redis.host:}'.isEmpty()")public static class RedisStandAloneAutoConfiguration {@Beanpublic RedisMessageListenerContainer customizeRedisListenerContainer(RedisConnectionFactory redisConnectionFactory,MessageListener messageListener) {RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);redisMessageListenerContainer.addMessageListener(messageListener,new PatternTopic("__keyevent@0__:expired"));return redisMessageListenerContainer;}}/*** redis 集群*/@Configuration@ConditionalOnExpression("'${spring.redis.host:}'.isEmpty()")public static class RedisClusterAutoConfiguration {@Beanpublic RedisMessageListenerFactory redisMessageListenerFactory(BeanFactory beanFactory,RedisConnectionFactory redisConnectionFactory) {RedisMessageListenerFactory beans = new RedisMessageListenerFactory();beans.setBeanFactory(beanFactory);beans.setRedisConnectionFactory(redisConnectionFactory);return beans;}}
}
package com.xxx.configuration;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;/*** @author chendatao*         <p>*         spring启动时 循环生成多个监听的bean 每个bean对应一组host:port ;对每个分片进行监听*/
public class RedisMessageListenerFactory implements BeanFactoryAware, ApplicationListener<ContextRefreshedEvent> {@Value("${spring.redis.password}")private String password;private DefaultListableBeanFactory beanFactory;private RedisConnectionFactory redisConnectionFactory;@Autowiredprivate MessageListener messageListener;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = (DefaultListableBeanFactory) beanFactory;}public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory) {this.redisConnectionFactory = redisConnectionFactory;}@Overridepublic void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {RedisClusterConnection redisClusterConnection = redisConnectionFactory.getClusterConnection();if (redisClusterConnection != null) {Iterable<RedisClusterNode> nodes = redisClusterConnection.clusterGetNodes();for (RedisClusterNode node : nodes) {
// 如何此处判断了node.isMaster() 那么当redis主从自动切换时 会丢失从服务的监听if (node.isMaster()) {String containerBeanName = "messageContainer" + node.hashCode();if (beanFactory.containsBean(containerBeanName)) {return;}
//RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(node.getHost(), node.getPort());redisStandaloneConfiguration.setPassword(password);LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);lettuceConnectionFactory.afterPropertiesSet();BeanDefinitionBuilder containerBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RedisMessageListenerContainer.class);containerBeanDefinitionBuilder.addPropertyValue("connectionFactory", lettuceConnectionFactory);containerBeanDefinitionBuilder.setScope(BeanDefinition.SCOPE_SINGLETON);containerBeanDefinitionBuilder.setLazyInit(false);beanFactory.registerBeanDefinition(containerBeanName,containerBeanDefinitionBuilder.getRawBeanDefinition());RedisMessageListenerContainer container = beanFactory.getBean(containerBeanName,RedisMessageListenerContainer.class);String listenerBeanName = "messageListener" + node.hashCode();if (beanFactory.containsBean(listenerBeanName)) {return;}
//                    监听key过期事件container.addMessageListener(messageListener, new PatternTopic("__keyevent@0__:expired"));container.start();}}}}}
package com.xxx.redis;import com.xxx.utils.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;import java.util.Map;/*** @author chendatao*         监听过期key事件*/
@Component
@Slf4j
public class KeyExpiredEventMessageListener implements MessageListener {private final static String EXPIRY_LOCK = "expiryLock_";@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate Map<String, KeyExpireHandler> keyExpireHandlerMap;@Overridepublic void onMessage(Message message, byte[] pattern) {log.info("accept keyExpiredEvent message={}", message);String expiredKey = message.toString();if (expiredKey.startsWith(EXPIRY_LOCK)) {
//            过期锁 不需要监听log.debug("expireLock {} expire.", expiredKey);return;}
//非业务过期key 不需要处理if (!expiredKey.startsWith("pickup_order_timer_")) {log.debug("other key expire,{} not need lister.", expiredKey);return;}String lockName = EXPIRY_LOCK + expiredKey;String localIp = IpUtil.getLocalIp();
//        防止分布式应用都响应过期key事件 此处加分布式锁try {
//            1小时有效期 不宜设置太短 也不能太长 boolean b = redisUtil.tryLock(lockName, localIp, 60 * 60 * 1000);if (!b) {
//没有抢到锁 不处理log.info("Don't need handler {}!", expiredKey);return;}this.routeHandler(expiredKey);} finally {使用本机ip 防止其他机器释放此锁 不需要释放锁
//            boolean b = redisUtil.releaseLock(lockName, localIp);
//            log.debug("release {},key={}", b, lockName);}log.info("accept keyExpiredEvent end message={}", message);}/*** 路由找到对应处理类 并触发处理逻辑* 约定:所有实现KeyExpireHandler接口的实现类都是互斥的 一个key只能仅有一个处理类** @param expiredKey*/private void routeHandler(String expiredKey) {
//do something}
}

redis集群中的每个都需要配置下redis.conf文件,增加 notify-keyspace-events EX

说明:

RedisAutoConfiguration类用于自动装配Redis,根据是否配置spring.redis.host属性来决定独立redis还是集群;
RedisMessageListenerFactory类用于循环生成多个监听的bean 每个bean对应一组host:port ,对每个分片进行监听;由于Redis不支持对集群进行监听,只能根据不同的分片动态生成bean来监听实现;
KeyExpiredEventMessageListener类用于具体的业务实现,由于业务系统是分布式的,那么同一个redis key过期会通知多台机器,故使用redis锁来通知,获取到锁的去实现业务。

redis实现计时器相关推荐

  1. Redis的学习记录

    Redis的学习记录 1.先导了解 1.1 NOSQL概述 1.1.1 为什么要用NoSql? 1.1.2 NoSql了解 1.1.3 NoSql特点 1.1.4 NoSQL的四大分类 2. Redi ...

  2. Redis附加功能之键过期功能

    一.键过期功能的相关命令 二.设置生存时间 Redis 提供了两个命令来设置键的生存时间(TTL,time to live),它们分别是: 如果给定的键不存在,那么 EXPIRE 和 PEXPIRE ...

  3. Redis命令——Keys相关

    from:http://blog.csdn.net/codolio/article/details/6411684 以下为Redis中有关Keys操作的一些命令,内容主要来源于Redis的官方文档.如 ...

  4. python重复执行_关于计时器:在Python中每x秒重复执行一次函数的最佳方法是什么?...

    我想永远每60秒在Python中重复执行一个函数(就像目标C中的NSTimer一样). 这段代码将作为守护进程运行,实际上就像使用cron每分钟调用python脚本一样,但不需要用户设置. 在这个关于 ...

  5. redis读取自增时候指定的key问题

    首先,此文章是接了如下文章写的 Spring boot redis自增编号控制 踩坑 上面这个问题解决后,公司这边功能其实已经实现了,但是考虑到一种情况,因为我们这边号的生成就是根据上面的自增编号来的 ...

  6. Redis基础知识之————如何处理客户端连接

    redis 连接建立 Redis Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作: 首先,客户端 ...

  7. Redis 6 RC1发布,带来众多新特性

    Redis 6 RC1 发布了,项目创建人 antirez 在博客中介绍,这是迄今最"企业"化的版本(SSL 与 ACL 等特性与企业极相关),也是最大的版本,同时也是参与人数最多 ...

  8. flask redis_在Flask应用程序中将Redis队列用于异步任务

    flask redis By: Content by Edward Krueger and Josh Farmer, and Douglas Franklin. 作者: 爱德华·克鲁格 ( Edwar ...

  9. note.. redis五大数据类型

    redis 五大数据类型使用 nosql介绍,由来 什么是nosql 阿里巴巴的架构 nosql 四大分类 redis入门 概述 redis 安装 (docker) 基础的知识 redis五大数据类型 ...

最新文章

  1. 马化腾:微信将被谁颠覆,领衔下一代互联网的终端居然是?
  2. canvas中的碰撞检测笔记
  3. Delphi - 使用字符串时,一个注意地方
  4. Spring Boot操作ES进行各种高级查询(值得收藏)
  5. linux下ntp服务器搭建方法
  6. 20211201 二范数的导数小于等于导数的二范数(导数存在情况下)
  7. 这几天惨遭Delphi类型转换折磨,请问怎么把double转成int类型
  8. Linux按照行数、大小切分文件
  9. 连接ebay服务器时系统出现问题,客户端从服务器收到SOAP Fault:验证ebay失败
  10. XAMPP中启动tomcat报错的解决方法
  11. axios请求失败重新发起请求_axios请求超时,设置重新请求的完美解决方法
  12. android 设置EditText光标位置
  13. 阶段5 3.微服务项目【学成在线】_day01 搭建环境 CMS服务端开发_12-MongoDb入门-基础概念...
  14. 快播案:程序正义、盗版和色情
  15. vba_1《考勤表》
  16. 编写myqq即时聊天脚本,实现相互通信(UDP)
  17. 泛微oa 明细数据合计
  18. ie主页被篡改(修改注册表)
  19. 免费高速的钉钉内网穿透——阿里出品必是精品(不限速,不限流量)
  20. 文法二义性与语言二义性

热门文章

  1. 小程序的支付的完整代码(php)
  2. JAVA基础之二维数组三维数组及应用
  3. 卸载高版本的labview的vision模块和VAS采集模块装低版本
  4. 华为荣耀9升级鸿蒙os,华为荣耀9款手机可升级EMUI 11了!优先升鸿蒙OS
  5. 华为云设计语言_让开发者相见恨晚?!华为云软件开发云实现云上敏捷开发
  6. AST实战|AST入门与实战星球高频问题汇总(二)
  7. 【渝粤教育】广东开放大学 普通心理学 形成性考核 (30)
  8. 进程、线程、协程之间的关系
  9. 徐佳计算机教授,计算机与软件学院第六届ECNU Coder程序设计竞赛成功举办
  10. OpenMV与PID控制