1.Redis过期键删除策略
Redis key过期的方式有三种:
被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key
当前已用内存超过maxmemory限定时,触发主动清理策略
被动删除
只有key被操作时(如GET),REDIS才会被动检查该key是否过期,如果过期则删除之并且返回NIL。
1、这种删除策略对CPU是友好的,删除操作只有在不得不的情况下才会进行,不会其他的expire key上浪费无谓的CPU时间。
2、但是这种策略对内存不友好,一个key已经过期,但是在它被操作之前不会被删除,仍然占据内存空间。如果有大量的过期键存在但是又很少被访问到,那会造成大量的内存空间浪费。expireIfNeeded(redisDb *db, robj *key)函数位于src/db.c。
主动删除
先说一下时间事件,对于持续运行的服务器来说, 服务器需要定期对自身的资源和状态进行必要的检查和整理, 从而让服务器维持在一个健康稳定的状态, 这类操作被统称为常规操作(cron job)
在 Redis 中, 常规操作由 redis.c/serverCron
实现, 它主要执行以下操作
更新服务器的各类统计信息,比如时间、内存占用、数据库占用情况等。
清理数据库中的过期键值对。
对不合理的数据库进行大小调整。
关闭和清理连接失效的客户端。
尝试进行 AOF 或 RDB 持久化操作。
如果服务器是主节点的话,对附属节点进行定期同步。
如果处于集群模式的话,对集群进行定期同步和连接测试。
Redis 将 serverCron
作为时间事件来运行, 从而确保它每隔一段时间就会自动运行一次, 又因为 serverCron
需要在 Redis 服务器运行期间一直定期运行, 所以它是一个循环时间事件: serverCron
会一直定期执行,直到服务器关闭为止。
在 Redis 2.6 版本中, 程序规定 serverCron
每秒运行 10
次, 平均每 100
毫秒运行一次。 从 Redis 2.8 开始, 用户可以通过修改 hz
选项来调整 serverCron
的每秒执行次数, 具体信息请参考 redis.conf
文件中关于 hz
选项的说明
maxmemory
当前已用内存超过maxmemory限定时,触发主动清理策略
volatile-lru:只对设置了过期时间的key进行LRU(默认值)
allkeys-lru : 删除lru算法的key
volatile-random:随机删除即将过期key
allkeys-random:随机删除
volatile-ttl : 删除即将过期的
noeviction : 永不过期,返回错误当mem_used内存已经超过maxmemory的设定,对于所有的读写请求,都会触发redis.c/freeMemoryIfNeeded(void)函数以清理超出的内存。注意这个清理过程是阻塞的,直到清理出足够的内存空间。所以如果在达到maxmemory并且调用方还在不断写入的情况下,可能会反复触发主动清理策略,导致请求会有一定的延迟。
如何用数组和链表实现LRU缓存淘汰算法?
LRU缓存淘汰算法
缓存思想在硬件设计、软件设计中都有着十分重要的作用。而缓存空间是有限的,当缓存空间消耗殆尽时,我们就需要对缓存进行更新。常见的缓存更新算法主要有如下几种:先进先出策略 FIFO(First In,First Out)、最少使用策略LFU(Least Frequently Used)、最近最少使用策略 LRU(Least Recently Used)。
先进先出策略 FIFO:采用队列即可实现。
最少使用策略LFU:使用有序链表实现。
最近最少使用策略LRU:可以使用单链表或者数组实现。
使用数组实现LRU缓存淘汰算法
使用数组实现有如下两种方式:
1.需要移动大量的数据元素
对于数组的维护方式如下:我们让越靠近数组尾端的元素是最近一次访问的。我们需要一个变量来记录当前数组已经存储的元素的个数。
当一个新的数据被访问时,我们需要从头开始遍历数组,如果发现当一个新的数据被访问时,我们需要从头开始遍历数组。
如果数据已经在缓存中,不管缓存满不满操作都是一致的:将当前数据从原来的位置移除,然后将该位置后面的所有元素向前移动一个元素,最后将当前数据放入到数组尾部即可。
如果数据不在缓存中,则分为缓存满或不满两种情况:
如果缓存已满,将数组首部元素删除(被淘汰掉的数据),然后将数组中的剩余元素向前移动一个元素,最后将当前数据放入到数组尾部即可。
如果缓存未满,直接将当前数据放入到数组尾部即可。
2.仅需移动少量数据元素
不再使用越靠近数组尾端的的元素是最近一次访问的方式,而是采用用一个位置变量记录当前最近一次访问数据在数组中的下标(这个下标是循环的,也就是说下标到达数组尾部后,直接从0开始)。另外,当数据已经在缓存中时,也不是立即进行数据元素的移动,如果缓存未满,只是进行简单的标记(标记数组当前下标存储的数据是无效的),然后直接将新插入的数据放入到位置变量的下一个位置即可。只有当缓存已满时,才需要进行缓存数据的淘汰操作,判断位置变来量的下一个位置是否是有效的数据,如果有效则将被淘汰,否则不需要淘汰,然后当前数据插入到该位置即可。
1.遍历当前数组,判断新访问的数据是否已经在缓存中。
2.如果已经在缓存中,把当前位置标记为数据无效。
3.如果缓存未满:直接将当前数据放入到位置变量的下一个位置。同时更新位置变量的值。
如果缓存已满:此时可能发生数据的淘汰。判断位置变量的下一个位置数据是否有效,如果有效则进行数据淘汰,否则不用进行数据淘汰。最后,将当前数据放入到位置变量的下一个位置即可,同时需要更新位置变量。
由于每次都需要进行遍历,所以此算法的时间复杂度为O(n)。
LinkedHashMap自身已经实现了顺序存储,默认情况下是按照元素的添加顺序存储,也可以启用按照访问顺序存储,即最近读取的数据放在最前面,最早读取的数据放在最后面,然后它还有一个判断是否删除最老数据的方法,默认是返回false,即不删除数据,我们使用LinkedHashMap实现LRU缓存的方法就是对LinkedHashMap实现简单的扩展,扩展方式有两种,一种是inheritance,一种是delegation,具体使用什么方式看个人喜好