Redis缓存何以一枝独秀?

2023-01-11 00:00:00 数据 执行 缓存 时间 过期

大家好,又见面了。


本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面。如果感兴趣,欢迎关注以获取后续更新。


上一篇文章中呢,我们简单的介绍了下Redis的整体情况。作为集中式缓存的代表,Redis可以帮助我们在项目中完成很多特定的功能。Redis准确的说是一个非关系型数据库,但是由于其超高的并发处理性能,及其对于缓存场景所提供的一系列能力构建,使其成为了分布式系统中的集中缓存的选择。

Redis对于缓存能力场景的支持,除了基础的缓存增删改查,还支持对记录的过期时间设定,支持多种不同的数据淘汰策略等等。此外为了解决内存型组件数据可靠性问题,还提供了一系列的数据持久化方案。

本篇文章中,我们就一起聊一聊这方面内容。

数据过期能力

为了节约内存的使用量,保证有限的内存空间能够被更有价值的数据使用,所以很多内存缓存组件都会支持数据过期能力。之前我们提过的本地缓存组件Guava Cache、Caffeine等支持基于缓存容器对象级别设置统一的过期时间,而Redis则支持对每条记录设定单独的过期时间。

创建时设定过期时间

可以在创建记录的时候指定过期时间,redis提供了setex命令可以实现插入的时候同步指定过期时间。比如:

setex key1 5 value1

上述命令实现了往redis中写入一个key1记录,并同时设定了5s后过期。如果在JAVA SpringBoot项目中可以直接使用相关API接口来实现:

stringRedisTemplate.opsForValue().set("key1", "value1", 5, TimeUnit.SECONDS);

这样缓存写入5s之后,缓存记录就会过期失效。描述到这里可以看出,这是一种基于创建时间来判定是否过期的机制,也即常规上说的TTL策略,当设定了过期时间之后不管有没有被使用都会到期被强制清理掉。但有很多场景下也会期望数据能够按照TTI(指定时间未使用再过期)的方式来过期清理,如用户鉴权场景:

假设用户登录系统后生成token并存储到Redis中,指定token有效期30分钟,那么如果用户一直在使用系统的时候突然时间到了然后退出要求重新登录,这个体验感就会很差。正确的预期应该是用户连续操作的时候就不要退出登录,只有连续30分钟没有操作的时候才过期处理。

略有遗憾的是,Redis并不支持按照TTI机制来做数据过期处理。但是作为补偿,Redis提供了一个重新设定某个key值过期时间的方法,可以通过expire方法来实现指定key的续期操作,以一种曲线救国的方式满足诉求。

实现缓存的续期

通过expire命令,可以对已有的记录重新设定过期时间,如果此前已经有设定了过期时间,则覆盖原先的过期时间。

expire key1 30

执行上述命令,可以将key1的过期时间给重新设定为30s,不管此前是否有过期时间。同样地,在代码中也可以方便的实现这一命令:

stringRedisTemplate.expire("key1", 30, TimeUnit.SECONDS);

对于上面说的用户token续期的诉求,可以这样来操作:

用户登录成功后,会生成一个token令牌,然后将令牌与用户信息存储到redis中,设定30分钟有效期。
每次请求接口中携带token来鉴权,每次get请求的时候,就重新通过expire操作将token的过期时间重新设定为30分钟。
持续30分钟无请求后,此条token缓存信息过期失效。

同样实现了TTI的效果。

实现指定时刻过期

Redis的过期时间设定,是基于当前命令执行时刻开始的相对过期时间,只能设定距离当前多久后失效,如果想要实现在固定时刻失效,还需要调用端执行一点小小的换算处理来实现。

public void test() {
    LocalDateTime dateTime = LocalDateTime.parse("2022-11-23 22:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd " +
            "HH:mm:ss"));
    Date date = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
    long expireTimeLong = date.getTime() - System.currentTimeMillis();
    stringRedisTemplate.expire("key1", expireTimeLong, TimeUnit.MILLISECONDS);
}

通过计算出目标时刻与当前时刻的时间差值,作为过期时间设定到记录上,即可。

数据淘汰策略

前面强调过,Redis是一个基于内存的缓存数据库,而内存的容量通常是有限的。虽然Reids有提供数据过期处理逻辑,但是当数据量特别多的时候就需要数据淘汰机制来兜底了。

这里数据淘汰策略与数据过期两个概念的差异要先弄清楚:

  • 数据过期,是符合业务预期的一种数据删除机制,为记录设定过期时间,过期后从缓存中移除。

  • 数据淘汰,是一种“有损自保”的降级策略,是业务预期之外的一种数据删除手段。指的是所存储的数据没达到过期时间,但缓存空间满了,对于新的数据想要加入内存时,为了避免OOM而需要执行的一种应对策略。

试想下,把Redis当做一个容器,容器已满的情况下继续往里面放东西,应对之法其实就两种:

  1. 直接拒绝放入。

  2. 扔掉容器中部分已有内容,腾出空间接纳新内容放入。


遵循上述认知,Redis提供了6种不同的数据淘汰机制,供使用方按需选择,将有限的空间仅用来存储热点数据,实现缓存的价值大化。如下:


对几种策略具体含义梳理归纳如下表所示:

数据淘汰策略具体含义说明
noeviction淘汰新进入的数据,即拒绝新内容写入缓存,直到缓存有新的空间。
allkeys-lru将内存中已有的key内容按照LRU策略将久没有使用的记录淘汰掉,然后腾出空间用来存放新的记录。
volatile-lru从设置了过期时间的key里面按照LRU策略,淘汰掉久没有使用的记录。与allkeys-lru相比,这种方式仅会在设定了过期时间的key里面进行淘汰。
allkeys-random从已有的所有key里面随机剔除部分,腾出空间容纳新数据。
volatile-random从已有的设定了过期时间的key里面随机剔除部分,腾出空间容纳新的数据
volatile-ttl从已有的设定了过期时间的key里面,将近将要过期的数据提前剔除掉,与volatile-lru的区别在于排序逻辑不一样,一个基于ttl规则排序,一个基于lru策略排序。

相关文章