1.背景

不知道大家有没有用Redis的Hash数据结构来缓存一种类的多个不同对象实体的经历,把不同对象的ID作为Hash的field,对象的JSON序列化字符串作为value。如果这个Hash里面的对象太多,且有部分对象过了一定时间后就不会再被访问到,这个时候我们是不是会想到要给其中某些field(后面暂且称之为子元素吧)给设置过期时间,不然的话,如果Hash里面的对象数量一直增长,将会造成Redis的内存爆炸。所以,怎么给Hash的子元素设置过期时间呢?

2.是否是伪需求

这时候有些同学就会说了,我直接把每一个field当做Redis的String类型的key,对象的JSON字符串作为String的value存不就可以了,这样不就可以针对每一个对象设置一个过期时间了。按理说,如果只要求设置过期时间,这样做是没问题的,而且这样做也最简单。但是如果你做过测试,你会发现同样数量的元素,用一个Hash结构存储和用若干个String结构存储,更节省内存。因为我们简单想一下,不管我们的Hash还是String类型的key,一般都会有个前缀,而用Hash定位一个元素只需要在Redis中存储一份这个前缀,但是用String就会导致不同的元素都要有这个前缀,不然你就无法根据前缀拼接ID来定位一个元素了。

所以给Hash里面的子元素设置过期时间的需求是存在的。

3.如何实现

方式一:我们可以使用Redis的ZSET数据结构来实现

下图是一个hash结构的数据,我们用hash里面的key表示id,value是实体类的JSON字符串:

然后我们再每次往Agent这个hash结构存储数据的时候,顺便往以AgentExpire为key的ZSET数据结构存储一份数据,这个数据的key是id,value是过期时间戳,如下图所示:

然后我们有个定时任务定时去扫描这个ZSET里面score小于当前时间的元素,也就是使用ZSET的rangeByScore命令:

long currentTimeMillis = System.currentTimeMillis();
Set<String> expireKeys = redisTemplate.opsForZSet().rangeByScore("AgentExpire", 0, currentTimeMillis);

这样我们就能找出哪些过期的key,然后去hash里面删除对应的元素了。

这个定时任务的间隔决定了发现过期key的敏感度,假如定时任务一秒钟扫描一次,那么hash里面的某个key最多超过过期时间一秒就会被删除。

方式二:通过延时队列

我们在往hash存入一个元素之后,往延时队列推送一条数据,延时的时长就是过期时间,这样当我们从延时队列取出数据时,把hash里面相应id的元素删掉即可。关于延时队列的知识本文就不赘述了,网上有很多关于延时队列的文章。



技术分享      Redis

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!