第一步: 单独注册一个RedisTemplate到spring boot中,连接redis的1号数据库
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
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.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import java.time.Duration;
/**
* redis第二连接库配置
*/
@Configuration
public class RedisMoreDateConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
// @Value("${spring.redis.password}")
// private String password;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public GenericObjectPoolConfig getPoolConfig() {
// 配置redis连接池
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(3);
poolConfig.setMinIdle(0);
poolConfig.setMaxWaitMillis(-1);
return poolConfig;
}
@Bean(name = "redisTemplateOne")
public StringRedisTemplate getRedisTemplate() {
return getStringRedisTemplate(1);
}
private StringRedisTemplate getStringRedisTemplate(int database) {
// 构建工厂对象
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(host);
config.setPort(port);
// config.setPassword(RedisPassword.of(password));
LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(6000))
.poolConfig(getPoolConfig())
.build();
LettuceConnectionFactory factory = new LettuceConnectionFactory(config, clientConfig);
// 设置使用的redis数据库
factory.setDatabase(database);
// 重新初始化工厂
factory.afterPropertiesSet();
return new StringRedisTemplate(factory);
}
/**
* 监听key过期事件
*/
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
return container;
}
/**
* 指定监听库
* 监听redis的1号数据库的所有键的过期时间
*/
@Bean
public ChannelTopic expiredTopic() {
return new ChannelTopic("__keyevent@1__:expired");
}
}
第二步:
什么是redis键空间?
Redis的键空间通知:keyspace notifications
这个功能是在2.8版本后加入的,在客户端通过订阅鸡汁,来接收那些以某种方式改变了redis数据空间的事件通知。
比如说,改变key的命令;所有在0号库过期的key;
事件类型
改变redis数据空间的每个操作,键空间通知都会发送两个不同的事件。
比如在1号数据库,执行 del zhangsan 的操作,将会触发两个消息:
1、keyevent@1:del zhangsan
2、keyspace@1:zhangsan del
keyspace是对所有zhangsan的key操作进行监听的。
keyevent是对所有执行成功的del操作进行监听,
如果有订阅者订阅监听了他,则会收到zhangsan返回的通知。
我就是利用了可以接收某个库过期的key这个操作,完成了这个定时任务。
而这个功能是可以通过redis配置来实现的,下面来说下到底怎么修改这个配置。
redis配置
因为键空间通知会消耗一定的CPU时间,所以在默认情况下,redis是关闭这个功能的,我们需要手动修改redis.conf来修改此功能的开启状态。
找到配置中 # notify-keyspace-events去掉注释,然后重新启动redis就可以了,easy吧?
这条配置,是需要在后面加多个指定的字符组成,来代表其不同的功能:
K:keyspace事件,事件以__keyspace@<库名>__为前缀进行发布;
E:keyevent事件,事件以__keyevent@<库名>__为前缀进行发布;
g:一般性的,非特定类型的命令,比如del,expire,rename等;
$:字符串特定命令;
l:列表特定命令;
s:集合特定命令;
h:哈希特定命令;
z:有序集合特定命令;
x:过期事件,当某个键过期并删除时会产生该事件;
e:驱逐事件,当某个键因maxmemore策略而被删除时,产生该事件;
A:g$lshzxe的别名,因此”AKE”意味着所有事件。
这些选项中,至少要包含K或者E,否则就没啥用了
比如我在这个定时任务中,使用了E和x:notify-keyspace-events Ex
第三步: 监控方法
expiredTopic()方法,就可以指定某个库,某个事件,expired就是过期啦。
@Component
@Slf4j
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* 失效后回调函数
* @param message
* @param pattern
*/
@SneakyThrows
@Override
public void onMessage(Message message, byte[] pattern) {
super.onMessage(message, pattern);
String msg = new String(message.getBody(), "UTF-8");
String channel = new String(message.getChannel(), "UTF-8");
log.info("Redis-Listener Channel:"+channel+" Listen to the key:"+msg);
//msg字符串处理,获取想要的key名字....
//具体的业务处理
//想要定时,那就继续把key存到redis,结合过期时间
}
}