Redis键通知相关小记(EVENT NOTIFICATION)

文章目录

  • 一、事件通知介绍
    • 1、前言
    • 2、配置详解
    • 3、订阅指定事件
    • 4、命令事件
    • 5、一些示例
  • 二、SpringBoot实现Redis失效监听事件
    • 1、场景说明
    • 2、代码实现(Redis单机版)
      • 2.1 环境准备
      • 2.2 配置监听Bean
      • 2.3 配置监听key
    • 3、Redis集群事件监听
  • 三、一些问题

一、事件通知介绍

1、前言

官方参考文档:https://redis.io/docs/manual/keyspace-notifications/

从Redis 2.8.0开始,Redis加入了发布/订阅模式以及键空间消息提醒(keyspace notification)功能。键空间消息提醒提供了允许客户端通过订阅指定信道获取Redis数据变化的能力。需要注意的是,键空间消息提醒并非可靠的,它不会对订阅端是否接收到消息进行确认。例如某个订阅的客户端暂时断开连接,在其直到恢复连接期间发生的事件将无法再次获得。

2、配置详解

各版本Redis配置文件:https://redis.io/docs/manual/config/

可以通过对redis的redis.conf文件中配置notify-keyspace-events参数可以指定服务器发送哪种类型的通知。下面对于一些参数的描述。默认情况下此功能是关闭的。

字符 通知
K 键空间通知,所有通知以 keyspace@ 为前缀
E 键事件通知,所有通知以 keyevent@ 为前缀
g DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知
$ 字符串命令的通知
l 列表命令的通知
s 集合命令的通知
h 哈希命令的通知
z 有序集合命令的通知
x 过期事件:每当有过期键被删除时发送
e 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送
A 参数 g$lshzxe 的别名
redis> CONFIG GET notify-keyspace-events
1) "notify-keyspace-events"
2) ""
redis> CONFIG SET notify-keyspace-events KEA
OK
redis> CONFIG GET notify-keyspace-events
1) "notify-keyspace-events"
2) "AKE"

在上述示例中将notify-keyspace-events配置为KEA,代表除未命中外的所有事件。其中,KE代表事件的两种类型——KeyspaceKeyevent

Keyspace代表与事件名称相关的消息,例如订阅对指定键进行的操作事件;Keyevent代表与键名称相关的消息,例如订阅发生键过期事件的相关键名称。

3、订阅指定事件

在完成配置后,可通过SUBSCRIBE命令订阅指定信道实现对一个或多个指定事件的订阅。例如通过订阅__keyevent@0__:expired实现订阅数据库0中的键过期事件

订阅的信道的格式为__@__:,其包括了事件类型(keyspacekeyevent)、数据库(例如数据库0)以及事件(例如expired)三部分组成。另外,也可以通过PSUBSCRIBE命令订阅一个或多个复合正则表达式匹配的信道。例如通过订阅__key*@*__:*订阅Redis中所有数据库中的所有事件。

4、命令事件

Redis为许多命令提供了不同的事件,在本文中将选择其中部分命令及其对应的事件进行介绍:

  • DEL:在某个键被删除时产生del事件

  • EXPIREPEXPIREEXPIREAT以及PEXPIREAT:当设置正数过期时间或未来时间的时间戳,则产生expire事件,否则产生del事件(将立即被删除)

  • SET以及同类的SETEXSETNXGETSET:产生set事件,若使用SETEX则也会产生expire事件

  • MSET:将会为每个键都产生一个set事件

  • LPUSHLPUSHXRPUSHRPUSHX:根据插入的方向分别产生lpushrpush事件

  • RPOPLPOP:分别产生rpoplpop事件,若移出的是列表中的最后一个元素,将会同时产生del事件

  • LSET:产生lset事件

  • LREM:产生lrem事件,同样若移除的元素为列表中的最后一个元素时将同时产生del事件

  • HSETHSETNX以及HMSET:产生一个hset事件

  • HDEL:产生一个hdel事件,且在移除后哈希表为空的情况下产生del事件

  • SADD:产生一个sadd事件

  • SREM:产生一个srem事件,且在移除后集合为空的情况下产生del事件

  • SMOVE:原键中产生srem事件且在目标键中产生sadd事件

  • SINTERSTORESUNIONSTORESDIFFSTORE:分别产生sinterstoresunionstore以及sdiffstore事件,且在结果为空集且目标键存在的情况下,将会产生del事件

  • ZADD:无论添加几个元素都只产生一个zadd事件

  • ZREM:无论移除几个元素都只产生一个zrem事件,当移除后有序集合为空时产生del事件

  • XADD:产生xadd事件,若使用MAXLEN子命令可能会同时产生xtrim事件

  • XDEL:产生xdel事件

  • PERSIST:如果对应的键所关联的过期事件成功被移除,则产生persist事件

  • 在键发生过期时产生expired事件

  • 在达到maxmemory设定的内存值后发生键淘汰时产生evicted事件

  • ……

关于更多的命令相关事件,请参考keyspace notification相关文档

5、一些示例

订阅键过期事件

redis1> SUBSCRIBE __keyevent@0__:expired
1) "subscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
# redis2> SETEX greeting 1 "hello world"
# 等待1秒后:
1) "message"
2) "__keyevent@0__:expired"
3) "greeting"

订阅所有事件

redis1> PSUBSCRIBE __key*@*__:*
1) "psubscribe"
2) "__key*@*__:*"
3) (integer) 1
# redis2> SET greeting "hello world"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyspace@0__:greeting"
4) "set"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyevent@0__:set"
4) "greeting"

二、SpringBoot实现Redis失效监听事件

1、场景说明

基于Redis的主动事件的处理,比如:当用户购买了会员卡十分钟内没有付款,需要通过小程序或者APP向用户主动推送购买会员卡的优势,引导用户继续完成支付并购买等,类似的场景需要用户在指定的时间点后主动通知或者继续引导,使用 Redis过期键Event优雅、快捷的实现。

因为Redis的订阅/发布是没有ACK确认机制的,因此可能会丢失消息,如果要保证消息可靠,应该选择MQ消息队列实现,

2、代码实现(Redis单机版)

2.1 环境准备

引入对应redis依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
    <version>2.3.12.RELEASEversion>
dependency>

设置application.yml连接信息

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    password: 123456
    timeout: 20000
    lettuce:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 50
        min-idle: 0

最后更改redis.conf 配置 notify-keyspace-events

# 默认
notify-keyspace-events "" 
# 更改为
notify-keyspace-events Ex

2.2 配置监听Bean

@Configuration
public class RedisConfig {
   
    /**
     * key过期事件订阅需要
     */
    @Bean
    @ConditionalOnMissingBean(RedisMessageListenerContainer.class)
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        //连接池的设置
        container.setConnectionFactory(connectionFactory);
        return container;
    }

}

2.3 配置监听key

@Component
@Slf4j
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private final String SET_NX = "setnx:";

    private final Integer database = 0;

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    protected void doRegister(RedisMessageListenerContainer listenerContainer) {
        String topic = "__keyevent@"+database+"__:expired";
        log.info("配置监听哪个频道:"+topic);
        PatternTopic patternTopic = new PatternTopic(topic);
        // 频道可以是多,多个传list
        listenerContainer.addMessageListener(this,patternTopic);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 获取过期的key,可以做自己的业务
        String expiredKey = message.toString();
        // 寻找需要的key前缀
        if(!expiredKey.startsWith("test")){
            return;
        }
        // 利用redis setIfAbsent命令,如果为空set返回true,如果不为空返回false,类似setnx加锁操作
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(SET_NX + expiredKey, String.valueOf(System.currentTimeMillis()),10, TimeUnit.SECONDS);
        if (aBoolean){
            // 避免多个服务监听情况下重复消费,也可以加锁与解锁
            // 注意:只能获取失效的key值,不能获取key对应的value值
            System.out.println(expiredKey);
            // 这里可以进行相应的业务代码
        }
    }
}



3、Redis集群事件监听

这里就简单说一下思路,因为不能监听集群,那就建立多个redis连接,分别对每个redis的key过期进行监听,相当于注册多个监听器,最后进行过期key的筛选即可

三、一些问题

参考:领导:谁再用 Redis 过期监听实现关闭订单,立马滚蛋!

对于延时任务一般实现的方法有几种:

  1. 使用 rocketmq、rabbitmq、pulsar 等消息队列的延时投递功能

  2. 使用 redisson 提供的 DelayedQueue

有一些方案虽然广为流传但存在着致命缺陷,不要用来实现延时任务

  1. 使用 redis 的过期监听

  2. 使用 rabbitmq 的死信队列

  3. 使用非持久化的时间轮

Redis6.0学习笔记

Redis过期监听机制实现订单超时处理

Redis集群下过期key监听

SpringBoot监听redis过期key

你可能感兴趣的:(#,SpringBoot,redis,过期监听)