通過開啟key過期的事件通知,當key過期時,會發布過期事件;我們定義key過期事件的監聽器,當key過期時,就能收到回調通知。
注意:
1)由於redis key過期刪除是定時+惰性,當key過多時,刪除會有延遲,回調通知同樣會有延遲。
2)且通知是一次性的,沒有ack機制,若收到通知后處理失敗,將不再收到通知。需自行保證收到通知后處理成功。
3)通知只能拿到key,拿不到value
使用場景:
1)實現延時隊列
消息作為key,將需要延遲的時間設置為key的TTL,當key過期時,在監聽器收到通知,達到延遲的效果。
步驟:
1、修改 redis.conf / redis.window.conf
開啟 notify-keyspace-events Ex
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.