Redis key过期监听

通过开启key过期的事件通知,当key过期时,会发布过期事件;我们定义key过期事件的监听器,当key过期时,就能收到回调通知。

注意:

  1)由于Redis key过期删除是定时+惰性,当key过多时,删除会有延迟,回调通知同样会有延迟。因此性能较低

  2)且通知是一次性的,没有ack机制,若收到通知后处理失败,将不再收到通知。需自行保证收到通知后处理成功。

  3)通知只能拿到key,拿不到value

  4)Redis将数据存储在内存中,如果遇到恶意下单或者刷单的将会给内存带来巨大压力

 

使用场景:

  1)实现延时队列

    消息作为key,将需要延迟的时间设置为key的TTL,当key过期时,在监听器收到通知,达到延迟的效果。

 

 

步骤:  

  1、修改 redis.conf / redis.window.conf 

  开启 notify-keyspace-events Ex

 

  补充,notify-keyspace-events 配置选项:

K     Keyspace events, published with __keyspace@<db>__ prefix.(键空间通知)
E     Keyevent events, published with __keyevent@<db>__ prefix.(键事件通知)
g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...(通用命令(非类型特定的),如DEL、EXPIRE、RENAME)
$     String commands(字符串命令)
l     List commands(列表命令)
s     Set commands(集合命令)
h     Hash commands(哈希命令)
z     Sorted set commands(有序集合命令)
x     Expired events (events generated every time a key expires)(过期事件(每次密钥过期时生成的事件))
e     Evicted events (events generated when a key is evicted for maxmemory)(驱逐事件(当为maxmemory退出一个键时生成的事件))
t     Stream commands(Stream命令)
d     Module key type events(模块key类型事件)
m     Key-miss events (Note: It is not included in the 'A' class)(Key-miss事件(当访问不存在的键时通知,不包含在A中))
A     Alias for g$lshzxetd(g$lshzxetd的别名都可用A表示), so that the "AKE" string means all the events(Except key-miss events which are excluded from 'A' due to their unique nature)(用“AKE”可表示所有事件通知,除了特殊的Key-miss事件)

  获取配置:CONFIG GET notify-keyspace-events

 

  2、RedisConfig 中配置 Message Listener Containers (消息订阅者容器)

   类似于Redis pub/sub 中 Message Listener Containers 的配置,区别少了监听器的指定。

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) {
        // redis 消息订阅(监听)者容器
        RedisMessageListenerContainer messageListenerContainer = new RedisMessageListenerContainer();
        messageListenerContainer.setConnectionFactory(redisConnectionFactory);
        // messageListenerContainer.addMessageListener(new ProductUpdateListener(), new PatternTopic("*.product.update"));
        return messageListenerContainer;
    }

 

  3、定义 key过期监听器,继承 KeyExpirationEventMessageListener

@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    /**
     * 创建RedisKeyExpirationListener bean时注入 redisMessageListenerContainer
     *
     * @param redisMessageListenerContainer RedisConfig中配置的消息监听者容器bean
     */
    public RedisKeyExpirationListener(RedisMessageListenerContainer redisMessageListenerContainer) {
        super(redisMessageListenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String channel = new String(message.getChannel()); // __keyevent@*__:expired
        String pa = new String(pattern); // __keyevent@*__:expired
        String expiredKey = message.toString();
        System.out.println("监听到过期key:" + expiredKey);
    }
}

 

 

  可以看到,其本质是Redis的发布订阅,当key过期,发布过期消息(key)到Channel :__keyevent@*__:expired中,再看KeyExpirationEventMessageListener 源码:

public class KeyExpirationEventMessageListener extends KeyspaceEventMessageListener implements ApplicationEventPublisherAware {

    // 发布key过期频道的topic
    private static final Topic KEYEVENT_EXPIRED_TOPIC = new PatternTopic("__keyevent@*__:expired");

    private @Nullable
    ApplicationEventPublisher publisher;

    /**
     * 子类在由Spring容器创建bean的时候调用父类的此构造器,并传入容器中的RedisMessageListenerContainer bean作为参数
     */
    public KeyExpirationEventMessageListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    /**
     * doRegister 在 KeyspaceEventMessageListener(实现了InitializingBean和 MessageListener) 中的init方中被调用
     * 将我们自定义的key过期监听器添加到 消息监听容器中
     */
    @Override
    protected void doRegister(RedisMessageListenerContainer listenerContainer) {
        listenerContainer.addMessageListener(this, KEYEVENT_EXPIRED_TOPIC);
    }

    /**
     * 发布key过期事件
     *
     * @param message 过期key
     */
    @Override
    protected void doHandleMessage(Message message) {
        publishEvent(new RedisKeyExpiredEvent(message.getBody()));
    }

    /**
     * 由Spring RedisKeyExpiredEvent事件监听器执行实际上的redis key过期消息的发布
     */
    protected void publishEvent(RedisKeyExpiredEvent event) {

        if (publisher != null) {
            this.publisher.publishEvent(event);
        }
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}

 

  因此,我们定义的继承了KeyExpirationEventMessageListener的redis key 过期监听器本质上就是一个消息监听器,监听来自channel为__keyevent@*__:expired 上发布的消息

 

 

END.

posted @ 2021-02-13 03:56  杨岂  阅读(10020)  评论(0编辑  收藏  举报