SpringBoot 结合 redis 实现定时任务
最近要做一个答题活动相关项目,用户答题时间到后,如果前台没有提交答题,则由后台自动提交.
最简单的方法自然就是定时任务,简单了解了一下定时任务,感觉这里最简单的就是通过监听 redis 键值来实现试卷的自动提交。
实现思路
用户请求试卷时,向 redis 中插入一个 key/value 键值对,保证能通过 key 查询到指定试卷即可,value 为任意值,
同时,在插入时,设置好过期时间,我这里是根据试卷规定的答题时间加上设定的网络延迟时间,具体根据需求来就行了.
等到设置的 redis 过期时间一到,redis 就会通知程序,程序再执行相关方法,就可以解决自动提交的问题了。
ps:通过监听 redis 提供的过期队列来实现定时器功能,,redis 向监听者发送消息告知过期值时,监听者只能获取到对应的 key 值,而取不到 value 值,因为已经过期了,所以这里一定要保证能够通过 key 值定位到对应的试卷,value 为任意值。
开启 redis key 过期提醒
修改 redis 相关事件配置。
找到 redis 配置文件redis.conf
,搜索 notify-keyspace-events
配置项。
如果没有,添加notify-keyspace-events Ex
,如果有值,则追加 Ex
,相关参数说明如下:
- K: keyspace 事件,事件以 keyspace@ 为前缀进行发布
- E: keyevent 事件,事件以 keyevent@ 为前缀进行发布
- A: g$lshzxe 的别名,因此”AKE”意味着所有事件
- g: 一般性的,非特定类型的命令,比如 del,expire,rename 等
- $: 字符串特定命令
- l: 列表特定命令
- s: 集合特定命令
- h: 哈希特定命令
- z: 有序集合特定命令
- x: 过期事件,当某个键过期并删除时会产生该事件
- e: 驱逐事件,当某个键因 maxmemore 策略而被删除时,产生该事件
SpringBoot 相关配置
引入依赖
我一般都用 maven,所以这里是在 pom 文件中引入 springboot redis 的相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
项目中 redis 相关配置
新建配置类RedisListenerConfig
来配置监听 redis key 过期事件
/**
* @ClassName RedisListenerConfig
* @Description redis消息处理
* @Author ScarletDrop
* @Date 2020/7/10 10:50
* @Version 1.0
**/
@Configuration
public class RedisListenerConfig {
private final RedisConnectionFactory redisConnectionFactory;
public RedisListenerConfig(RedisConnectionFactory redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory;
}
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer() {
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
return redisMessageListenerContainer;
}
@Bean
public RedisExpiredListener keyExpiredListener() {
return new RedisExpiredListener(this.redisMessageListenerContainer());
}
}
新建监听器 RedisExpiredListener
实现 KeyExpirationEventMessageListener
接口,该接口监听 redis 所有 db 的过期事件__keyevent@*__:expired
/**
* @ClassName RedisMessageReceiver
* @Description redis消息处理
* @Author ScarletDrop
* @Date 2020/7/10 10:58
* @Version 1.0
**/
@Slf4j
public class RedisExpiredListener extends KeyExpirationEventMessageListener {
// 自定义监听redis db库 方法一 写死在代码里面
/**
* 配置监听的具体redis db库
**/
private static final Topic KEYEVENT_EXPIRED_TOPIC = new PatternTopic("__keyevent@11__:expired");
/**
* 注册订阅通道
**/
@Override
protected void doRegister(RedisMessageListenerContainer listenerContainer) {
listenerContainer.addMessageListener(this, KEYEVENT_EXPIRED_TOPIC);
}
// 自定义监听redis db库 方法二 从yml文件中获取redis db库
/**
* 获取监听的redis db库
**/
@Value("${spring.redis.database}")
private String database;
/**
* 注册订阅通道
**/
@Override
protected void doRegister(RedisMessageListenerContainer listenerContainer) {
// 配置监听的具体redis库
String dbExp = StringUtils.format("__keyevent@{}__:expired", database);
log.info("redis监听库监听通道:{}", dbExp);
Topic keyevent_expired_topic = new PatternTopic(dbExp);
listenerContainer.addMessageListener(this, keyevent_expired_topic);
}
// ps 上面这两个方法的配置也可以跳过,不配的话就是走父类配置,监听全部redis库
public RedisExpiredListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
// 这里是具体监听失效redis的方法
@Override
public void onMessage(Message message, byte[] pattern) {
String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
//过期的key 可以在这之后进行相关操作
String key = new String(message.getBody(), StandardCharsets.UTF_8);
log.debug("redis key 过期:pattern={},channel={},key={}", new String(pattern), channel, key);
}
}
ps:做完之后感觉挺简单,实际上最开始弄的时候是找的监听指定的 db,在网上查了半天,找了很多方法,结果都没生效,最后才找到的这个,特此记录。
参考文章
Q.E.D.