Redis分库监听实现延时任务

一、Redis

Redis数据库有16个,分别是0-15,每个数据库用数字命名,而且每个数据库的连接密码都一样,redis只允许一个密码。数据库之间不能共享,并且基于单机才有,如果是集群,就没有数据库的概念了。

redis之所以分这么多个数据库,也是为了区分业务,不同的业务存放在不同的库,但是一个redis,一般是给一个项目用,项目内的不同业务,单独用一个库,这样不会相互有数据交叉。现在很多微服务项目,一个项目里有多个微服务,redis统一由团队管理,每个服务连接自己的库就可以了。

redis的数据库个数,也是可以修改的,redis.conf里面设置了databases 16,默认是16个,0-15,如果要修改数据库个数,可以修改这个配置。

Redis不支持自定义数据库的名字,每个数据库都以编号命名,开发者必须自己记录哪些数据库存储了哪些数据。另外Redis也不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问。

最重要的一点是多个数据库之间并不是完全隔离的,比如FLUSHALL命令可以清空一个Redis实例中所有数据库中的数据。综上所述,这些数据库更像是一种命名空间,而不适宜存储不同应用程序的数据。比如可以使用0号数据库存储某个应用生产环境中的数据,使用1号数据库存储测试环境中的数据,但不适宜使用0号数据库存储A应用的数据而使用1号数据库B应用的数据,不同的应用应该使用不同的Redis实例存储数据。由于Redis非常轻量级,一个空Redis实例占用的内在只有1M左右,所以不用担心多个Redis实例会额外占用很多内存。

二、监听机制

配置文件默认是 #注释了的,改为notify-keyspace-events Ex 重启redis,记住指定redis.conf配置文件启动

字符 发送通知
K 键空间通知,所有通知以 keyspace@ 为前缀,针对Key
E 键事件通知,所有通知以 keyevent@ 为前缀,针对event
g DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知
$ 字符串命令的通知
l 列表命令的通知
s 集合命令的通知
h 哈希命令的通知
z 有序集合命令的通知
x 过期事件:每当有过期键被删除时发送
e 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送
A 参数 g$lshzxe 的别名,相当于是All

三、环境搭建

引入 Maven 依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.5.0</version>
</dependency>

配置 Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Configuration
public class RedisConfig {

@Bean(name = "db0RedisTemplate")
public RedisTemplate<String, Object> db0RedisTemplate() {
return getRedisTemplate(db0Factory());
}
@Bean(name = "db1RedisTemplate")
public RedisTemplate<String, Object> db1RedisTemplate() {
return getRedisTemplate(db1Factory());
}

@Bean
@Primary
public LettuceConnectionFactory db0Factory(){
return new LettuceConnectionFactory(getRedisConfig(0), getClientConfig());
}

@Bean
public LettuceConnectionFactory db1Factory(){
return new LettuceConnectionFactory(getRedisConfig(1), getClientConfig());
}

private RedisStandaloneConfiguration getRedisConfig(int dbNo) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("127.0.0.1");
config.setPort(6379);
config.setPassword("");
config.setDatabase(dbNo);
return config;
}

private LettuceClientConfiguration getClientConfig() {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(10);
poolConfig.setMaxIdle(5);
poolConfig.setMinIdle(2);
poolConfig.setMaxWaitMillis(3000);
return LettucePoolingClientConfiguration.builder().poolConfig(poolConfig).build();
}

public RedisTemplate<String, Object> getRedisTemplate(LettuceConnectionFactory factory) {
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(String.class);
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}

public StringRedisTemplate getStringRedisTemplate(LettuceConnectionFactory factory) {
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}

@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
// 添加自定义的监听器
// 这里只监听 0号数据库
container.addMessageListener(new RedisMessageListener(container), new ChannelTopic("_keyevent@0_:expired"));
container.setConnectionFactory(connectionFactory);
return container;
}

}

定义监听

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class RedisMessageListener extends KeyExpirationEventMessageListener {

public RedisMessageListener(RedisMessageListenerContainer listenerContainer){
super(listenerContainer);
}

@Override
public void onMessage(Message message, byte[] bytes) {
String key = message.toString();
System.out.println("过期的key为:" + key);
}
}

四、测试

编写测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@SpringBootTest(classes = SpringbootLogApplication.class)
class RedisTest {

@Resource(name = "db0RedisTemplate")
RedisTemplate<String, Object> redisTemplate0;

@Test
void contextLoads() {
redisTemplate0.opsForValue().set("test1", "1", 5, TimeUnit.SECONDS);
redisTemplate0.opsForValue().set("test2", "1", 10, TimeUnit.SECONDS);
redisTemplate0.opsForValue().set("test3", "1", 15, TimeUnit.SECONDS);
redisTemplate0.opsForValue().set("test4", "1", 20, TimeUnit.SECONDS);
redisTemplate0.opsForValue().set("test5", "1", 25, TimeUnit.SECONDS);
redisTemplate0.opsForValue().set("test6", "1", 30, TimeUnit.SECONDS);
redisTemplate0.opsForValue().set("test7", "1", 35, TimeUnit.SECONDS);
redisTemplate0.opsForValue().set("test8", "1", 40, TimeUnit.SECONDS);
redisTemplate0.opsForValue().set("test9", "1", 45, TimeUnit.SECONDS);
}
}

控制台效果

image-20201225172643152