提问:如何找到redis中所有过期的key?

这个问题,我也只想到了一种办法,如果有其他比较好的方案,热烈欢迎评论区一展风采哈~


先说说Redis的删除策略。

1、定时删除

在创建key的时候,为key创建一个定时器,让定时器在key过期时间来临时,对key进行删除。

优点:内存空会尽快被释放

缺点:若过期的key有很多,删除这些key会占用很多CPU的时间,导致CPU压力大,删除的时候不会考虑CPU是否空闲,性能影响较大

 

2、惰性删除

key过期后不立即删除,每次从数据库获取key的时候去检测是否过期。过期才会删除,并且返回null。

优点:删除操作只发生在查询key的时刻,而且只删除当前的key,所有对CPU是比较友好的。

缺点:若大量超时的key,在很久一段时间内没有被获取过,那么可能发生内存泄漏,因为大量无用的垃圾占用了内存空间。

 

3、定期删除

每隔一段时间执行一次删除(在redis.conf配置文件中设置hz,1s刷新的频率)过期key的操作。

redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?假如redis存了几十万个key,每隔100ms就遍历所有的设置过期时间的key的话,就会给CPU带来很大的负载。

默认hz为10,看下注释描述。

优点:

1、通过限制操作的时长和频率,来减少删除操作对CPU时间的占用,这种方式可以优化定时删除策略的缺点。

2、定期删除过期的key,这种方式可以优化惰性删除的缺点

 

缺点:

在内存友好方面,不如定时删除策略

在CPU方面,不如惰性删除

 

难点:

如何合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)

 

- 当redis服务器初始化的时,读取配置server.hz

 

4、对比总结:

定时删除和定期删除为主动删除:Redis会定期主动淘汰一批已过去的key

惰性删除为被动删除:用到的时候才会去检验key是不是已过期,过期就删除

惰性删除为redis服务器内置策略

定期删除可以通过:

  • 第一、配置redis.conf 的hz选项,默认为10 (即1秒执行10次,100ms一次,值越大说明刷新频率越快,最Redis性能损耗也越大) 
  • 第二、配置redis.conf的maxmemory最大值,当已用内存超过maxmemory限定时,就会触发主动清理策略

 

  • memcached只是用了惰性删除,而Redis同时使用了惰性删除与定期删除,这也是二者的一个不同点(可以看做是redis优于memcached的一点)
  • 对于惰性删除而言,并不是只有获取key的时候才会检查key是否过期,在某些设置key的方法上也会检查(eg.setnx key2 value2:该方法类似于memcached的add方法,如果设置的key2已经存在,那么该方法返回false,什么都不做;如果设置的key2不存在,那么该方法设置缓存key2-value2。假设调用此方法的时候,发现redis中已经存在了key2,但是该key2已经过期了,如果此时不执行删除操作的话,setnx方法将会直接返回false,也就是说此时并没有重新设置key2-value2成功,所以对于一定要在setnx执行之前,对key2进行过期检查)

5、Redis采用的过期策略

惰性删除+定期删除

  • 惰性删除流程
    • 在进行get或setnx等操作时,先检查key是否过期,
    • 若过期,删除key,然后执行相应操作;
    • 若没过期,直接执行相应操作
  • 定期删除流程(简单而言,对指定个数个库的每一个库随机删除小于等于指定个数个过期key)
    • 遍历每个数据库(就是redis.conf中配置的"database"数量,默认为16)
      • 检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执行20次,循环体时下边的描述)
        • 如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历
        • 随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key
        • 判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。

 

6、RDB和AOF在持久化时,是如何对过期key进行处理的

6.1、RDB对过期key的处理

过期key对RDB没有任何影响

  • 从内存数据库持久化数据到RDB文件
    • 持久化key之前,会检查是否过期,过期的key不进入RDB文件
  • 从RDB文件恢复数据到内存数据库
    • 数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)

6.2、AOF对过期key的处理

过期key对AOF没有任何影响

  • 从内存数据库持久化数据到AOF文件:
    • 当key过期后,还没有被删除,此时进行执行持久化操作(该key是不会进入aof文件的,因为没有发生修改命令)
    • 当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉)
  • AOF重写
    • 重写时,会先判断key是否过期,已过期的key不会重写到aof文件 

7、当新数据进入redis时,如果内存不足怎么办?

Redis使用内存存储数据,在执行每一个命令前,会调用freeMemoryIfNeeded()检测内存是否充足。如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法

注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。

(err)OOM command not allowed when used memory > 'maxmemory'

 

最大可使用内存maxmemory:占用物理内存的比例,默认值为0,表示不限制。通常设置在50%以上

每次选取待删除数据的个数:maxmemory-samples  选取数据时并不会全库扫描,导致严重的性能损耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据

删除策略:maxmemory-policy 达到最大内存后,对被选出来的数据进行删除的策略

 


以上针对redis的删除策略有些是直接拷贝别人的文字,在理解上是一致的,我也就没有再打一遍文字。

现在说说如何找到redis中所有过期的key?

监听Redis的过期队列:

代码实现:

pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
@Configuration
public class RedisListenerConfig {

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory factory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        return container;
    }
}
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
        String expiredKey = message.toString();
        System.out.println("监听到过期的key:" + expiredKey);
        String resultKey = redisTemplate.opsForValue().get(expiredKey);
        System.out.println("返回:" + resultKey);

    }
}

就这简单的几行代码即可,开始测试,创建一个key并设置20s过期

 

 

完事儿~ 再次说明一下,这只是一种方式实现,不是绝对的。

 

简单去看了一下redis的listener底层的实现,其实原理上就是和MQ类似,维护一个过期的topic队列,来监听这个队列。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐