- 键空间
每个redis服务器默认有16个db,编号为0~15,db以字典的形式保存所有的键值对,这个字典就叫做键空间。redis操作set、get、del、flushdb、randomkey等,都是对键空间的操作,都会产生键空间通知。键空间通知是通过redis的发布订阅机制实现的,通过订阅键空间通知,可以对感兴趣的redis操作或者数据的变化进行监听。
当有操作时,redis会固定向两个channel: __keyspace@<db>__:<key>(键空间通知)和__keyevent@<db>__:<event>(键事件通知),订阅者可通过psubscribe __keyevent@*__:*订阅任意库的任意事件的消息。
- 配置
因为开启键空间通知功能需要消耗一些 CPU,所以在默认配置下, 该功能处于关闭状态。可以修改redis配置文件redis.conf开启或关闭键空间通知。
- notify-keyspace-events 为空字符串时,键空间通知关闭
- 当给notify-keyspace-events 设值时,表示键空间通知开启
notify-keyspace-events 相关配置如下:
字符 & 发送的通知
K & 键空间通知,所有通知以 `__keyspace@<db>__` 为前缀
E & 键事件通知,所有通知以 `__keyevent@<db>__` 为前缀
g & DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知
$ & 字符串命令的通知
l & 列表命令的通知
s & 集合命令的通知
h & 哈希命令的通知
z & 有序集合命令的通知
x & 过期事件:每当有过期键被删除时发送
e & 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送
A & 参数 g$lshzxe 的别名,即all
输入的参数中至少要有一个 K 或者 E , 否则的话, 不管其余的参数是什么, 都不会有任何通知被分发。例如:notify-keyspace-events 配置为 EX时,当有键值对过期时,将发送通知,channel为__keyevent@0__:expired。当配置为KEA时,会发送所有操作的键空间通知和键事件通知。
- 命令演示
通过命令psubscribe __keyevent@*__:expired订阅所有库过期事件的通知
set键值对mykey, 并设置10s后过期
mykey过期后,收到消息通知
- Java代码实现
1)引入spring-data-redis依赖包
2)配置redis连接池和消息监听容器
<bean class="redis.clients.jedis.JedisPoolConfig" id="jedisPoolConfig">
<property name="maxIdle" value="50" />
<property name="maxTotal" value="500" />
<property name="maxWaitMillis" value="10000" />
<property name="testOnBorrow" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<property name="numTestsPerEvictionRun" value="100" />
</bean>
<!-- 下面是配置模板方式 springData管理 redisTemplate -->
<!--jedisFactory-->
<bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" id="jedisConnFactory">
<property name="poolConfig" ref="jedisPoolConfig"/>
<property name="hostName" value="139.129.241.130"/>
<property name="port" value="6379"/>
<property name="timeout" value="10000"/>
</bean>
<bean class="org.springframework.data.redis.core.RedisTemplate" id="redisTemplate">
<property name="connectionFactory" ref="jedisConnFactory"/>
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
</property>
</bean>
<bean id="messageListener" class="com.dubbo.service.impl.ExpiredKeyNotification">
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
<!-- 配置监听者容器 -->
<bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
<property name="connectionFactory" ref="jedisConnFactory"/>
<property name="messageListeners">
<map>
<entry key-ref="messageListener">
<list>
<bean class="org.springframework.data.redis.listener.PatternTopic">
<constructor-arg value="__keyevent@*__:expired"/>
</bean>
</list>
</entry>
</map>
</property>
</bean>
3)实现消息监听接口
public class ExpiredKeyNotification implements MessageListener {
@Override
public void onMessage(Message message, byte[] bytes) {
System.out.println("收到消息");
byte[] body = message.getBody();
byte[] channel = message.getChannel();
//设置监听频道
String topic = new String(channel);
//key
String itemValue = new String(body);
System.out.println("频道topic:"+topic);
System.out.println("过期的键值对的K:"+itemValue);
}
}
4)当redis服务器有过期键时,就会发送通知
- 问题分析
1) 因为redis键空间通知是利用redis发布订阅机制实现的,因此不能作为一个可靠的通知,当客户端重启或宕机时,很有可能丢失消息。
2)使用redis键空间通知,需要先开启notify-keyspace-events 的配置,该功能比较消耗cpu,最好使用单独的redis服务器或集群。
3)过期通知的发送时间,因为redis删除过期键的方式有两个,一个是当键被访问时,会去检查是否过期;另一个是redis-server会渐近的检查并删除过期键。当过期键被发现并删除时,并且有订阅者正在监听对应的channel,会向该channel推送键过期通知。因此,redis不能保证过期键立即被删除,特别是当带有有效期的键值对较多时,可能会有明显的延迟。