Redis Key 过期事件订阅

需求

​ 在自建企业内部威胁情报的过程中, 需要通过Redis统一管理过期时间的Key。因此需要对过期的Key进行实时监听, 并进行回调处理。

思路

​ 在 Redis2.8.0 版本之后,其推出了一个新的特性——键空间消息(Redis Keyspace Notifications),它配合 2.0.0 版本之后的 SUBSCRIBE 就能完成这个定时任务的操作了。

Redis 的键空间通知支持 订阅指定 Key 的所有事件 与 订阅指定事件 两种方式。

Keyspace notifications are implemented sending two distinct type of events for every operation affecting the Redis data space. For instance a DEL operation targeting the key named mykey in database 0 will trigger the delivering of two messages, exactly equivalent to the following two PUBLISH commands:

1
2
PUBLISH __keyspace@0__:mykey del
PUBLISH __keyevent@0__:del mykey

通过 Redis 的键空间通知(keyspace notification)可以做到:将IoC数据写入Redis,设置过期时间10分钟,利用 Redis 键过期回调提醒,如果未被消费,则进行处理。

实现

1. 修改 redis.conf 开启redis key过期提醒

By default keyspace events notifications are disabled because while not very sensible the feature uses some CPU power. Notifications are enabled using the notify-keyspace-events of redis.conf or via the CONFIG SET.

由于键空间通知比较耗CPU, 所以 Redis默认是关闭键空间事件通知的, 需要手动开启 notify-keyspace-events 后才启作用。

1
2
3
4
5
6
7
8
9
10
11
K:keyspace事件,事件以__keyspace@<db>__为前缀进行发布;        
E:keyevent事件,事件以__keyevent@<db>__为前缀进行发布;
g:一般性的,非特定类型的命令,比如del,expire,rename等;
$:String 特定命令;
l:List 特定命令;
s:Set 特定命令;
h:Hash 特定命令;
z:Sorted 特定命令;
x:过期事件,当某个键过期并删除时会产生该事件;
e:驱逐事件,当某个键因maxmemore策略而被删除时,产生该事件;
A:g$lshzxe的别名,因此”AKE”意味着所有事件。

notify-keyspace-events Ex 表示开启键过期事件提醒

redis.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# RDB Config
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum no
dir ./
# AOF Config
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# set Password
requirepass %{mypassword}
# set notify-keyspace-events
notify-keyspace-events Ex

2. RedisHelper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python3
# Author: Canon
# Date: 2019-12-27
# Version: 0.2

import redis

class RedisHelper():
def __init__(self):
# 连接Redis
self.__conn = redis.Redis(host='127.0.0.1', port=6379, password='mypassword', db=0)
# 定义keyevent, 此处0为indexdb
self.keyevent = '__keyevent@0__:expired'

# 发布方法
def publish(self, key, msg):
ttl = 10
self.__conn.setex(key, msg, ttl)
return True

# 订阅方法
def subscribe(self):
sub = self.__conn.pubsub()
sub.subscribe(self.keyevent)
for msg in sub.listen():
if msg['type'] == 'message':
ex_key = msg['data'].decode()
print(ex_key)

参考