内存回收
redis中的每个对象都由一个redisObject表示,redisObject的type属性和ptr属性才是指向底层实现数据结构的指针。在redisObject中还维护了一个refcount属性用于引用计数,当对象被创建时,引用计数初始化为1,当对象被新程序使用时refcount+1,当不再被某个程序使用时,refcount-1,当该值为0时,对象所占用的内存就会被回收。
引用计数除了用于内存会说外,还用于对象共享。比如key a的值为10,key b的值也为10,那么为了节省内存,redis会将数据库键的值指向同一个字符串对象,被共享的值对象的引用计数+1。前面说的有序集合的skiplist实现字典和跳表共享对象也是通过这种方式实现,实际上redis默认会初始化0-9999的字符串用于共享。
过期键删除策略
过期删除主要有两种策略:定时删除,惰性删除,其实还有一种定期删除。
定时删除:定时删除,对内存友好,但如果键很多,消耗cpu。
惰性删除:使用时检测到过期再删除,对cpu友好,但是如果过期键很多,占用太多内存。
定期删除:中和前两种,隔一段时间删除过期键,并限制删除操作的频率和时长。但如果控制不好,则可能退化为定时删除或惰性删除。
redos对于过期键的删除策略主要采用惰性删除和定期删除结合,每次执行redis命令前都会调用expireIfNeeded函数对键进行检查,如果过期则删除该键。定期操作通过activeExpireCycle函数实现,分多次再规定时间内遍历redis数据库,随机检查部分键,删除过期键。每个库默认检查20个键。实际上这个过程还分慢模式和快模式,慢模式下超时时间为25毫秒,如果超时,则会再次以快模式回收过期键,快模式下超时时间1毫秒且2s内只能执行一次。
内存回收策略
redis的内存回收机制主要是对于过期键的删除策略,以及对于内存使用达到上限时的内存回收策略。
redis通过配置maxmemory来控制最大可用内存,通过info memory
可用查看redis内存信息。这里列出几个主要配置:
used_memory:由 Redis 分配器分配的内存总量,包含了redis进程内部的开销和数据占用的内存,单位字节
used_memory_human:用更直观的方式显示内存总量
used_memory_rss:向操作系统申请的内存大小。与 top、ps命令的输出一致。
maxmemory:Redis实例的最大内存配置
maxmemory_policy:当达到maxmemory时的淘汰策略
mem_fragmentation_ratio:碎片率,used_memory_rss/ used_memory
实际上由于内存碎片,redis达到最大内存的往往是超过maxmemory的。
32位的redis没记错的话最大只能配置3g。
redis的内存回收策略主要有以下6种:
策略 | 详情 |
---|---|
noeviction | 默认策略,禁止淘汰,内存不足的时候只能读不能写,写会返回错误 |
volatile-lru | 对超时的键值对采用最近最少使用的淘汰策略(lru)回收 |
allkeys-lru | 对所有键值对采用最近最少使用的淘汰策略(lru)回收 |
volatile-random | 对超时的键值对采用随机淘汰策略回收 |
allkeys-random | 对所有键值对采用随机淘汰策略回收 |
volatile-ttl | 采用删除存活时间最短的键值对策略 |
这里提以下LRU,即Least Recently Used 近期最少使用算法,很多缓存策略都使用了这种策略进行空间的释放。
Redis 在默认情况下会采用 noeviction 策略,通过maxmemory_policy参数配置。
实际上对于上面提到的LRU或TTL算法,redis并不是很精确的实现,为什么呢,因为redis中可能有很多的键值对,redis不可能每次都对比全部的键值对来确定删除哪个键值对,这样会消耗cpu且更耗时,所以redis通过
maxmemory-samples参数来控制,该参数默认5,该参数配置选取固定数目的key作为样本进行比较,然后按照淘汰策略回收。maxmemory-samples越大,则lru算法更接近严格上的lru算法,精确度更高,但是开销也变大了。查找资料得知,redis作者经过测试,该值配置为10已经很接近真正的lru了。
本文总结自《redis设计与实现》