原文地址
键空间通知使得客户端可以通过订阅频道或模式, 来接收那些以某种方式改动了 Redis 数据集的事件。
以下是一些键空间通知发送的事件的例子:
所有修改键的命令。
所有接收到 LPUSH key value [value …] 命令的键。
0 号数据库中所有已过期的键。
事件通过 Redis 的订阅与发布功能(pub/sub)来进行分发, 因此所有支持订阅与发布功能的客户端都可以在无须做任何修改的情况下, 直接使用键空间通知功能。
因为 Redis 目前的订阅与发布功能采取的是发送即忘(fire and forget)策略, 所以如果你的程序需要可靠事件通知(reliable notification of events), 那么目前的键空间通知可能并不适合你: 当订阅事件的客户端断线时, 它会丢失所有在断线期间分发给它的事件。
未来将会支持更可靠的事件分发, 这种支持可能会通过让订阅与发布功能本身变得更可靠来实现, 也可能会在 Lua 脚本中对消息(message)的订阅与发布进行监听, 从而实现类似将事件推入到列表这样的操作。
对于每个修改数据库的操作,键空间通知都会发送两种不同类型的事件。
比如说,对 0
号数据库的键 mykey
执行 DEL key [key …]
命令时, 系统将分发两条消息, 相当于执行以下两个 PUBLISH channel message
命令:
PUBLISH __keyspace@0__:mykey del
PUBLISH __keyevent@0__:del mykey
订阅第一个频道 __keyspace@0__:mykey
可以接收 0 号数据库中所有修改键 mykey 的事件, 而订阅第二个频道 __keyevent@0__:del
则可以接收 0 号数据库中所有执行 del 命令的键。
以 keyspace 为前缀的频道被称为键空间通知(key-space notification), 而以 keyevent 为前缀的频道则被称为键事件通知(key-event notification)。
当 del mykey
命令执行时:
del
。mykey
。因为开启键空间通知功能需要消耗一些 CPU , 所以在默认配置下, 该功能处于关闭状态。
可以通过修改 redis.conf
文件, 或者直接使用 CONFIG SET
命令来开启或关闭键空间通知功能:
notify-keyspace-events
选项的参数为空字符串时,功能关闭。notify-keyspace-events
的参数可以是以下字符的任意组合, 它指定了服务器该发送哪些类型的通知:
输入的参数中至少要有一个 K
或者 E
, 否则的话, 不管其余的参数是什么, 都不会有任何通知被分发。
举个例子, 如果只想订阅键空间中和列表相关的通知, 那么参数就应该设为 Kl
, 诸如此类。
将参数设为字符串"AKE"
表示发送所有类型的通知。
命令产生的通知
以下列表记录了不同命令所产生的不同通知:
DEL key [key …]
命令为每个被删除的键产生一个del
通知。
RENAME key newkey
产生两个通知:为来源键(source key)产生一个 rename_from
通知,并为目标键(destination key)产生一个 rename_to
通知。
EXPIRE key seconds
和 EXPIREAT key timestamp
在键被正确设置过期时间时产生一个 expire 通知。当 EXPIREAT key timestamp
设置的时间已经过期,或者 EXPIRE key seconds
传入的时间为负数值时,键被删除,并产生一个 del
通知。
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern …]] [ASC | DESC] [ALPHA] [STORE destination]
在命令带有 STORE 参数时产生一个 sortstore
事件。如果 STORE
指示的用于保存排序结果的键已经存在,那么程序还会发送一个 del
事件。
SET key value [EX seconds] [PX milliseconds] [NX|XX]
以及它的所有变种(SETEX key seconds value 、 SETNX key value 和 GETSET key value)都产生 set
通知。其中 SETEX key seconds value
还会产生 expire
通知。
MSET key value [key value …]
为每个键产生一个 set
通知。
SETRANGE key offset value
产生一个 setrange
通知。
INCR key 、 DECR key 、 INCRBY key increment
和 DECRBY key decrement
都产生 incrby
通知。
INCRBYFLOAT key increment
产生 incrbyfloat
通知。
APPEND key value
产生append
通知。
LPUSH key value [value …]
和 LPUSHX key value
都产生单个lpush
通知,即使有多个输入元素时,也是如此。
RPUSH key value [value …]
和 RPUSHX key valu
e 都产生单个 rpush
通知,即使有多个输入元素时,也是如此。
RPOP key
产生rpop
通知。如果被弹出的元素是列表的最后一个元素,那么还会产生一个 del 通知。
LPOP key
产生lpop
通知。如果被弹出的元素是列表的最后一个元素,那么还会产生一个 del 通知。
LINSERT key BEFORE|AFTER pivot value 产生一个 linsert 通知。
LSET key index value 产生一个 lset 通知。
LTRIM key start stop 产生一个 ltrim 通知。如果 LTRIM key start stop 执行之后,列表键被清空,那么还会产生一个 del 通知。
RPOPLPUSH source destination 和 BRPOPLPUSH source destination timeout 产生一个 rpop 通知,以及一个 lpush 通知。两个命令都会保证 rpop 的通知在 lpush 的通知之前分发。如果从键弹出元素之后,被弹出的列表键被清空,那么还会产生一个 del 通知。
HSET hash field value 、 HSETNX hash field value 和 HMSET 都只产生一个 hset 通知。
HINCRBY 产生一个 hincrby 通知。
HINCRBYFLOAT 产生一个 hincrbyfloat 通知。
HDEL 产生一个 hdel 通知。如果执行 HDEL 之后,哈希键被清空,那么还会产生一个 del 通知。
SADD key member [member …] 产生一个 sadd 通知,即使有多个输入元素时,也是如此。
SREM key member [member …] 产生一个 srem 通知,如果执行 SREM key member [member …] 之后,集合键被清空,那么还会产生一个 del 通知。
SMOVE source destination member 为来源键(source key)产生一个 srem 通知,并为目标键(destination key)产生一个 sadd 事件。
SPOP key 产生一个 spop 事件。如果执行 SPOP key 之后,集合键被清空,那么还会产生一个 del 通知。
SINTERSTORE destination key [key …] 、 SUNIONSTORE destination key [key …] 和 SDIFFSTORE destination key [key …] 分别产生 sinterstore 、 sunionostore 和 sdiffstore 三种通知。如果用于保存结果的键已经存在,那么还会产生一个 del 通知。
ZINCRBY key increment member 产生一个 zincr 通知。(译注:非对称,请注意。)
ZADD key score member [[score member] [score member] …] 产生一个 zadd 通知,即使有多个输入元素时,也是如此。
ZREM key member [member …] 产生一个 zrem 通知,即使有多个输入元素时,也是如此。如果执行 - ZREM key member [member …] 之后,有序集合键被清空,那么还会产生一个 del 通知。
ZREMRANGEBYSCORE key min max 产生一个 zrembyscore 通知。(译注:非对称,请注意。)如果用于保存结果的键已经存在,那么还会产生一个 del 通知。
ZREMRANGEBYRANK key start stop 产生一个 zrembyrank 通知。(译注:非对称,请注意。)如果用于保存结果的键已经存在,那么还会产生一个 del 通知。
ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX] 和 ZUNIONSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX] 分别产生 zinterstore 和 zunionstore 两种通知。如果用于保存结果的键已经存在,那么还会产生一个 del 通知。
每当一个键因为过期而被删除时,产生一个 expired
通知。
每当一个键因为maxmemory
政策而被删除以回收内存时,产生一个 evicted
通知。
所有命令都只在键真的被改动了之后,才会产生通知。
比如说,当SREM key member [member …]
试图删除不存在于集合的元素时,删除操作会执行失败,因为没有真正的改动键,所以这一操作不会发送通知。
如果对命令所产生的通知有疑问, 最好还是使用以下命令, 自己来验证一下:
Redis 使用以下两种方式删除过期的键:
当一个键被访问时,程序会对这个键进行检查,如果键已经过期,那么该键将被删除。
底层系统会在后台渐进地查找并删除那些过期的键,从而处理那些已经过期、但是不会被访问到的键。
当过期键被以上两个程序的任意一个发现、 并且将键从数据库中删除时, Redis 会产生一个 expired 通知。
Redis 并不保证生存时间(TTL)变为 0 的键会立即被删除: 如果程序没有访问这个过期键, 或者带有生存时间的键非常多的话, 那么在键的生存时间变为 0 , 直到键真正被删除这中间, 可能会有一段比较显著的时间间隔。
因此, Redis 产生expired
通知的时间为过期键被删除的时候, 而不是键的生存时间变为 0 的时候。
按上文内容,我们先将redis的键空间通知
开启,我们开启所有的通知,在可以端中测试后没问题再到代码中测试。
config set notify-keyspace-events KEA
psubscribe '__key*__:*'
set name wsl
以下代码采用string类型演示
新增和修改都是set指令,所以监听的主题都一样,实现MessageListener
接口,重写onMessage
这里就是收到消息的处理逻辑
@Component
@Data
public class RedisUpdateAndAddListener implements MessageListener {
//监听的主题
private final PatternTopic topic = new PatternTopic("__keyevent@*__:set");
@Override
public void onMessage(Message message,byte[] pattern){
String topic = new String(pattern);
String msg = new String(message.getBody());
System.out.println("收到key更新或修改,消息主题是:"+ topic+",消息内容是:"+msg);
}
}
在配置一下MessageListenerContainer
类,将我们写好的监听类添加到该类中即可,删除和过期都是需要添加,我这里就一起添加了后面就不做演示。
@Configuration
public class RedisConfig {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RedisUpdateAndAddListener redisUpdateAndAddListener;
@Autowired
private RedisDeleteListener redisDeleteListener;
@Autowired
private RedisExpiredListener redisExpiredListener;
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//监听所有的key的set事件
container.addMessageListener(redisUpdateAndAddListener, redisUpdateAndAddListener.getTopic());
//监听所有key的删除事件
container.addMessageListener(redisDeleteListener,redisDeleteListener.getTopic());
//监听所有key的过期事件
container.addMessageListener(redisExpiredListener,redisExpiredListener.getTopic());
return container;
}
}
在redis中对name这个key进行set操作
set name wsl
跟上面的更新监听一样,只需要把订阅主题更改一下即可。同样需要添加到这个RedisMessageListenerContainer
,上面已经添加,这里不做演示
@Component
@Data
public class RedisDeleteListener implements MessageListener {
//监听主题
private final PatternTopic topic = new PatternTopic("__keyevent@*__:del");
/**
*
* @param message 消息
* @param pattern 主题
*/
@Override
public void onMessage(Message message, byte[] pattern) {
String topic = new String(pattern);
String msg = new String(message.getBody());
System.out.println("收到key的删除,消息主题是:"+ topic+",消息内容是:"+msg);
}
}
在redis输入命令,del name
在控制台可以看到已经收到消息了。
如上面的操作方式一样
@Data
@Component
public class RedisExpiredListener implements MessageListener {
//监听主题
private final PatternTopic topic = new PatternTopic("__keyevent@*__:expired");
@Override
public void onMessage(Message message, byte[] pattern) {
String topic = new String(pattern);
String msg = new String(message.getBody());
System.out.println("收到key的过期,消息主题是:"+ topic+",消息内容是:"+msg);
}
}