我需要过期所有在Redis哈希表中超过1个月的键。
这是不可能的, 为了保持Redis的简洁。
Redis 的创始人 Antirez 曾说:
嗨,这是不可能的。你可以使用一个不同的顶级键来存储特定的字段,或者将另一个带有过期时间的字段与该字段一起存储,获取两个字段,并让应用程序根据当前时间确定它是否仍然有效。
Redis不支持在除了顶级键之外的哈希上设置TTL
,否则将使整个哈希过期。如果您正在使用分片集群,则可以使用另一种方法。这种方法可能并非在所有场景中都有用,并且性能特征可能与预期有所不同。但仍值得提及:
当存在哈希时,其结构基本上如下:
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
我们有意使用 {}
符号。这样可以让所有这些键落在同一个 哈希槽
中。您可以在这里阅读更多信息: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
或EVALSHA
命令在服务器上执行这些步骤。同样,您需要考虑此方法对您特定情况的性能影响。
更多参考文献:
EXPIRE myHashKey 10
可以让你的哈希在10秒后过期。https://redis.io/commands/expire - jonlink这在KeyDB中是可以实现的,它是Redis的一个分支。由于它是一个分支,它完全兼容Redis,并可作为一种替代品使用。
只需使用EXPIREMEMBER命令即可。它适用于集合、哈希和排序集。
EXPIREMEMBER keyname subkey [time]
您还可以使用TTL和PTTL查看过期时间
TTL keyname subkey
您可以在redis中使用排序集合(Sorted Set)来创建一个以时间戳为分数的TTL容器。
例如,每当您将事件字符串插入集合时,可以将其分数设置为事件时间。
因此,您可以通过调用zrangebyscore "your set name" min-time max-time
获取任何时间窗口的数据。
此外,我们可以使用zremrangebyscore "your set name" min-time max-time
进行过期处理,以删除旧事件。
唯一的缺点是您需要从外部进程执行清理操作来维护集合的大小。
我们在此讨论的问题是相同的。
我们有一个 Redis 哈希表,其中包含一个键和哈希条目(名称/值对),我们需要在每个哈希条目中保持单独的过期时间。
我们通过添加 n 字节的前缀数据来实现这一点,包含编码过期信息,当我们写入哈希条目值时,我们还设置键在所写入的值所包含的时间到期。
然后,在读取时,我们解码前缀并检查是否过期。这是额外的开销,但是读取仍然是 O(n),并且整个键将在最后一个哈希条目过期时过期。
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
我认为最大的注意事项是,您永远不会删除字段,因此哈希表可能会变得非常大。不确定是否有优雅的解决方案 - 如果感觉太大,我通常会偶尔删除哈希表。也许您可以在某个地方跟踪您存储的所有内容,并定期删除它们(尽管在那时,您可能会使用该机制手动到期字段...)。
有一个Redisson的Java框架,它实现了具有entry TTL支持的哈希Map
对象。它在底层使用hmap
和zset
Redis对象。使用示例:
RMapCache<Integer, String> map = redisson.getMapCache('map');
map.put(1, 30, TimeUnit.DAYS); // this entry expires in 30 days
关于NodeJS实现,我已经在保存在HASH中的对象中添加了自定义的expiryTime
字段。然后,在特定时间段之后,我使用以下代码清除过期的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);
}
})
}
});
Array.filter
来创建一个需要从哈希表中删除的 keys
数组,然后将其作为参数传递给 client.hdel(HASH_NAME, ...keys)
以进行单次调用,从而使其更加高效。 - doublesharpconst keys = Object.keys(reply).filter(key => reply[key] && JSON.parse(reply[key]).expiryTime < Date.now()); client.hdel(HASH_NAME, ...keys);
- doublesharp您可以通过在存储键时添加前缀或命名空间来以不同的方式在Redis中存储键/值,例如“hset_”
获取键/值GET hset_key
等同于HGET hset key
添加键/值SET hset_key value
等同于HSET hset key
获取所有键KEYS hset_*
等同于HGETALL hset
获取所有值应该在两个操作中完成,首先获取所有键KEYS hset_*
然后获取每个键的值
使用TTL或过期时间添加键/值是问题的主题:
SET hset_key value
EXPIRE hset_key
注意: KEYS
会在整个数据库中查找匹配的键,这可能会影响性能,特别是当你的数据库很大时。而SCAN 0 MATCH hset_*
可能会更好,只要它不会阻塞服务器,但对于大型数据库性能仍然是一个问题。
如果您要过期一些键,并且它们是一小部分键,您可以创建一个新的数据库来单独存储这些键。
感谢 @DanFarrell 指出与
KEYS
相关的性能问题。