我需要使 redis 哈希中的所有密钥过期,这些密钥超过 1 个月。
14 回答
Redis 的创建者 Quoth Antirez:
嗨,这是不可能的,要么为该特定字段使用不同的顶级密钥,要么将另一个具有过期时间的字段与提交的字段一起存储,同时获取两者,并让应用程序了解它是否仍然有效或无效当前时间。
Redis 不支持拥有TTL
除 top key 以外的散列,这会使整个散列过期。如果您使用的是分片集群,则可以使用另一种方法。这种方法不可能在所有场景中都有用,并且性能特征可能与预期的不同。还是值得一提的:
当有一个哈希时,结构基本上看起来像:
hash_top_key
- child_key_1 -> some_value
- child_key_2 -> some_value
...
- child_key_n -> some_value
由于我们要添加TTL
到子键,我们可以将它们移动到顶部键。要点是现在的键应该是hash_top_key
和子键的组合:
{hash_top_key}child_key_1 -> some_value
{hash_top_key}child_key_2 -> some_value
...
{hash_top_key}child_key_n -> some_value
我们是{}
故意使用这个符号的。这允许所有这些键落在同一个hash slot
. 你可以在这里阅读更多关于它的信息:https ://redis.io/topics/cluster-tutorial
现在,如果我们想做同样的哈希运算,我们可以这样做:
HDEL hash_top_key child_key_1 => DEL {hash_top_key}child_key_1
HGET hash_top_key child_key_1 => GET {hash_top_key}child_key_1
HSET hash_top_key child_key_1 some_value => SET {hash_top_key}child_key_1 some_value [some_TTL]
HGETALL hash_top_key =>
keyslot = CLUSTER KEYSLOT {hash_top_key}
keys = CLUSTER GETKEYSINSLOT keyslot n
MGET keys
这里有趣的是HGETALL
。首先,我们得到hash slot
所有孩子的钥匙。然后我们得到那个特定的键,hash slot
最后我们检索值。我们需要在这里小心,因为可能有不止一个n
键,hash slot
而且可能有一些我们不感兴趣但它们具有相同的键hash slot
。我们实际上可以编写一个Lua
脚本,通过执行EVAL
orEVALSHA
命令在服务器中执行这些步骤。同样,您需要考虑此方法在您的特定场景中的性能。
更多参考资料:
这在作为 Redis 的 Fork 的KeyDB中是可能的。因为它是一个 Fork,它与 Redis 完全兼容,并且可以作为替代品使用。
只需使用 EXPIREMEMBER 命令。它适用于集合、散列和排序集合。
EXPIREMEMBER 键名子键 [时间]
您还可以使用 TTL 和 PTTL 来查看过期时间
TTL 键名子键
此处提供更多文档:https ://docs.keydb.dev/docs/commands/#expiremember
你可以在redis中使用Sorted Set来获取一个以时间戳为分数的TTL容器。例如,每当您将事件字符串插入集合中时,您都可以将其分数设置为事件时间。因此,您可以通过调用获取任何时间窗口的数据
zrangebyscore "your set name" min-time max-time
此外,我们可以通过使用zremrangebyscore "your set name" min-time max-time
来删除旧事件来过期。
这里唯一的缺点是您必须从外部进程中进行内务处理以保持集合的大小。
有一个Redisson java 框架,它实现Map
了带有条目 TTL 支持的哈希对象。它在后台使用hmap
Rediszset
对象。使用示例:
RMapCache<Integer, String> map = redisson.getMapCache('map');
map.put(1, 30, TimeUnit.DAYS); // this entry expires in 30 days
这种方法非常有用。
我们在这里讨论了同样的问题。
我们有一个 Redis 哈希,一个哈希条目(名称/值对)的键,我们需要在每个哈希条目上保存单独的过期时间。
我们通过在写入哈希条目值时添加包含编码过期信息的 n 字节前缀数据来实现这一点,我们还将键设置为在写入值中包含的时间过期。
然后,在读取时,我们解码前缀并检查是否过期。这是额外的开销,但是,读取仍然是 O(n),并且当最后一个哈希条目过期时,整个密钥将过期。
关于 NodeJS 实现,我expiryTime
在我保存在 HASH 中的对象中添加了一个自定义字段。然后在特定时间段后,我使用以下代码清除过期的 HASH 条目:
client.hgetall(HASH_NAME, function(err, reply) {
if (reply) {
Object.keys(reply).forEach(key => {
if (reply[key] && JSON.parse(reply[key]).expiryTime < (new Date).getTime()) {
client.hdel(HASH_NAME, key);
}
})
}
});
如果您的用例是您在 Redis 中缓存值并且可以容忍陈旧的值,但希望偶尔刷新它们以免它们变得太陈旧,那么一个hacky解决方法是在字段值中包含时间戳并在您访问该值的任何地方处理到期。
这使您可以继续正常使用 Redis 哈希,而无需担心其他方法可能引起的任何并发症。唯一的代价是在客户端增加一些额外的逻辑和解析。这不是一个完美的解决方案,但这是我通常所做的,因为我出于任何其他原因不需要 TTL,而且我通常需要对缓存的值进行额外的解析。
所以基本上它会是这样的:
在 Redis 中:
hash_name
- field_1: "2021-01-15;123"
- field_2: "2021-01-20;125"
- field_2: "2021-02-01;127"
您的(伪)代码:
val = redis.hget(hash_name, field_1)
timestamp = val.substring(0, val.index_of(";"))
if now() > timestamp:
new_val = get_updated_value()
new_timestamp = now() + EXPIRY_LENGTH
redis.hset(hash_name, field_1, new_timestamp + ";" + new_val)
val = new_val
else:
val = val.substring(val.index_of(";"))
// proceed to use val
imo 最大的警告是,您永远不会删除字段,因此哈希可能会变得非常大。不确定是否有一个优雅的解决方案 - 如果感觉太大,我通常只是每隔一段时间删除一次散列。也许您可以跟踪您存储在某处的所有内容并定期删除它们(尽管此时您可能只是使用该机制手动使字段过期......)。
你可以。这是一个例子。
redis 127.0.0.1:6379> hset key f1 1
(integer) 1
redis 127.0.0.1:6379> hset key f2 2
(integer) 1
redis 127.0.0.1:6379> hvals key
1) "1"
2) "1"
3) "2"
redis 127.0.0.1:6379> expire key 10
(integer) 1
redis 127.0.0.1:6379> hvals key
1) "1"
2) "1"
3) "2"
redis 127.0.0.1:6379> hvals key
1) "1"
2) "1"
3) "2"
redis 127.0.0.1:6379> hvals key
如果您想使哈希中的特定密钥过期 1 个月。这是不可能的。Redis expire 命令适用于哈希中的所有键。如果您设置每日哈希键,您可以设置一个键的生存时间。
hset key-20140325 f1 1
expire key-20140325 100
hset key-20140325 f1 2
您可以在 Redis 中以不同的方式存储键/值来实现此目的,只需在存储键时添加前缀或命名空间,例如“hset_”
获取一个键/值
GET hset_key
等于HGET hset key
添加一个键/值
SET hset_key value
等于HSET hset key
获取所有键
KEYS hset_*
等于HGETALL hset
获取所有 vals 应该在 2 个操作中完成,首先获取所有键,
KEYS hset_*
然后获取每个键的值添加一个带有TTL 或过期的键/值,这是问题的主题:
SET hset_key value
EXPIRE hset_key
注意:KEYS
将在整个数据库中查找匹配键,这可能会影响性能,特别是如果您有大数据库。
笔记:
KEYS
将在整个数据库中查找匹配键,这可能会影响性能,尤其是在您拥有大型数据库的情况下。虽然SCAN 0 MATCH hset_*
可能会更好,只要它不阻塞服务器,但在大型数据库的情况下性能仍然是一个问题。您可以创建一个新的数据库来分别存储您想要过期的这些密钥,特别是如果它们是一小组密钥。
感谢@DanFarrell,他强调了与
KEYS
埃隆马斯克很快就会把人送上月球,我们仍然不能让 redis 上的字段过期:(
无论如何,我想出的解决方案是:
假设我想每 3 分钟过期一次:所以我将数据保存在 3 个字段 0 1 2 中。然后我以分钟为单位对当前时间执行模块% 3。
如果模块例如 == 0 所以我只使用 1 2 和 0 我删除;然后它更改为 1,所以我使用 2 和 0 并删除 1。
我没有使用它,我没有检查它,但我只是让你知道它可能
您可以使用psubscribe
和来使用 Redis Keyspace 通知"__keyevent@<DB-INDEX>__:expired"
。
这样,每次密钥到期时,您都会在 redis 连接上发布一条消息。
关于您的问题,基本上您创建了一个临时的“正常”密钥set
,其过期时间为 s/ms。它应该与您希望在集合中删除的密钥的名称相匹配。
由于您的临时密钥将在过期时发布到您的 redis 连接"__keyevent@0__:expired"
,因此您可以轻松地从原始集中删除您的密钥,因为消息将具有密钥的名称。
文档:https ://redis.io/topics/notifications (查找标志 xE)
static async setCount(ip: string, count: number) {
const val = await redisClient.hSet(ip, 'ipHashField', count)
await redisClient.expire(ip, this.expireTime)
}
尝试使您的密钥过期。
您可以轻松地过期Redis 哈希,例如使用 python
import redis
conn = redis.Redis('localhost')
conn.hmset("hashed_user", {'name': 'robert', 'age': 32})
conn.expire("hashed_user", 10)
这将在 10 秒后使哈希hashed_user中的所有子键过期
与 redis-cli 相同,
127.0.0.1:6379> HMSET testt username wlc password P1pp0 age 34
OK
127.0.0.1:6379> hgetall testt
1) "username"
2) "wlc"
3) "password"
4) "P1pp0"
5) "age"
6) "34"
127.0.0.1:6379> expire testt 10
(integer) 1
127.0.0.1:6379> hgetall testt
1) "username"
2) "wlc"
3) "password"
4) "P1pp0"
5) "age"
6) "34"
10秒后
127.0.0.1:6379> hgetall testt
(empty list or set)