键空间通知

键空间通知使得客户端可以通过订阅频道或模式, 来接收那些以某种方式改动了 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:键空间通知,所有通知以 keyspace@ 为前缀
  • E:键事件通知,所有通知以 keyevent@ 为前缀
  • g:DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知
  • $:字符串命令的通知
  • l:列表命令的通知
  • s:集合命令的通知
  • h:哈希命令的通知
  • z:有序集合命令的通知
  • x:过期事件:每当有过期键被删除时发送
  • e:驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送
  • A:参数 g$lshzxe 的别名

输入的参数中至少要有一个 K 或者 E , 否则的话, 不管其余的参数是什么, 都不会有任何通知被分发。
举个例子, 如果只想订阅键空间中和列表相关的通知, 那么参数就应该设为 Kl , 诸如此类。
将参数设为字符串 “AKE” 表示发送所有类型的通知。

注:所有命令都只在键真的被改动了之后,才会产生通知。
比如说,当 SREM key member [member …] 试图删除不存在于集合的元素时,删除操作会执行失败,因为没有真正的改动键,所以这一操作不会发送通知。
如果对命令所产生的通知有疑问, 最好还是使用以下命令, 自己来验证一下:

$ redis-cli config set notify-keyspace-events KEA
 $ redis-cli --csv psubscribe 'key*:’
 Reading messages… (press Ctrl-C to quit)
 “psubscribe”,"__key__😗",1


然后, 只要在其他终端里用 Redis 客户端发送命令, 就可以看到产生的通知了:

“pmessage”,“key*:",“keyspace@0:foo”,“set”
 “pmessage”,"__key__😗”,“keyevent@0:set”,“foo”
 …


过期通知的发送时间
Redis 使用以下两种方式删除过期的键:
1、当一个键被访问时,程序会对这个键进行检查,如果键已经过期,那么该键将被删除。
2、底层系统会在后台渐进地查找并删除那些过期的键,从而处理那些已经过期、但是不会被访问到的键。
当过期键被以上两个程序的任意一个发现、 并且将键从数据库中删除时, Redis 会产生一个 expired 通知。
Redis 并不保证生存时间(TTL)变为 0 的键会立即被删除: 如果程序没有访问这个过期键, 或者带有生存时间的键非常多的话, 那么在键的生存时间变为 0 , 直到键真正被删除这中间, 可能会有一段比较显著的时间间隔。因此, Redis 产生 expired 通知的时间为过期键被删除的时候, 而不是键的生存时间变为 0 的时候。

例:实现过期事件监听

1、开启redis.conf过期事件监听配置

notify-keyspace-events Ex

2、redis监听事件接收处理器

import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
 * redis监听事件接收处理器
 */
@Slf4j
@Component
public class RedisReceiver {
    // 收到消息之后执行的方法
    public void receiveMessage(Object message) {
        String key = message.toString(); // 获取到过期的key
        log.info("收到过期key:" + key);
    }
}

3、redis监听器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

/**
 * redis监听器
 */
@Configuration
public class RedisSubListenerConfig {
    // 初始化监听器
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 新增订阅频道及订阅者,订阅者必须有相关方法处理收到的消息
        // __keyevent@*__:expired 订阅Redis的所有数据库的键值失效事件
        container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@*__:expired"));
        return container;
    }
    // 利用反射来创建监听到消息之后的执行方法
    @Bean
    MessageListenerAdapter listenerAdapter(RedisReceiver redisReceiver) {
        return new MessageListenerAdapter(redisReceiver, "receiveMessage");
    }
}

参考:http://redisdoc.com/topic/notification.html