过期key删除
redis具体设置key过期时间的功能,但清除过期key则是个大问题,当redis数据量较大时,如果通过遍历所有expires数据字典(在redis底层源码中通过数据库结构体的expires属性来保存所有设置了过期时间的key和其过期时间,了解更多可以看我的帖子,redisDb 和 redisObject)中的key,来删除过期key,对性能会是极大的损耗,redis提供了两种解决方案
惰性删除:
这种删除方式是当我们使用这个数据时才回去检查过期时间,如果过期,则删除该缓存。
周期删除:
周期删除是指redis按一定频率抽查设置了过期时间的key是否过期,并且由expiers_cursor配合记录清理位置,它包括两种模式
- slow模式:这种模式下按照server.hz配置的参数的频率执行清理任务,假如说将其设置为10,那么该模式下,redis将每秒执行十次清理,并且每次执行时间不能超过25ms
- fast模式:该模式下将在一个循环中不断执行清理操作,并且保证两次清理任务之间间隔不低于2ms,并且每次清理操作执行不能超过1ms
内存淘汰策略
redis作为一款内存数据库,会占用大量的内存空间,当内存空间不够大时,redis也要根据不同的内存淘汰策略来进行处理,redis提供了如下八种内存淘汰策略供我们在配置中选择,具体如下:
- noeviction : 不淘汰任何 key ,但是内存满时不允许写入新数据,默认就是这种策略。
- volatile-ttl : 对设置了 TTL (过期时间)的 key ,比较 key 的剩余 TTL 值, TTL 越小越先被淘汰
- allkeys-random :对全体 key ,随机进行淘汰。
- volatile-random :对设置了 TTL 的 key ,随机进行淘汰。
- allkeys-lru : 对全体 key ,基于 LRU 算法进行淘汰
- volatile-lru : 对设置了 TTL 的 key ,基于 LRU 算法进行淘汰
- allkeys-lfu : 对全体 key ,基于 LFU 算法进行淘汰
- volatile-lfu : 对设置了 TTL 的 key ,基于 LFI 算法进行淘汰
LRU ( Least Recently Used )算法
在使用 LRU 算法进行内存淘汰时,redisObject结构体(redis源码中通过redisObject结构体保存一个键值对信息,了解更多可以看我的另一个帖子redisDb 和 redisObject)中的lru字段保存的是键的最近访问时间的时间戳。Redis 使用一个 24 位的时间戳来表示键的最近访问时间。具体操作如下:
访问时间记录:
- 每次访问键时,Redis 会更新该键的 lru 字段,记录当前的时间戳。
淘汰策略:
- 当内存达到上限,需要淘汰键时,Redis 会选择那些 lru 字段值最小(即最久未被访问)的键进行淘汰。
LFU ( Least Frequently Used )算法
在使用 LFU 算法进行内存淘汰时,redisObject结构体(redis源码中通过redisObject结构体保存一个键值对信息,了解更多可以看我的另一个帖子redisDb 和 redisObject)中的lru字段保存的时一个表示键的访问频率多少的整数,但这个整数并不是记录具体的访问次数。而是通过LFU算法计算访问频率,以节省内存并仍然能够体现出键的访问频率。具体操作如下:
访问频率计算:
- Redis 使用 LFU 算法时,lru 字段存储的是一个代表访问频率大小的整数(并不是访问次数)。
- 每次访问键时,Redis 会根据LFU算法来决定是否增加该计数器的值。首先计算 1 / (lru现在的值 * lfu_log_factor + 1),其中 lfu_log_factor是用于控制lru增涨速度的系数,默认为 10。
- 根据这个公式计算出的概率值,再生成一个 0 到 1 之间的随机数,如果随机数小于这个概率值,则增加 lru 的值。
通过这种方式,随着lru的增大公式计算的值就会越来愈小,生成随机数小于这个概率值的概率就会越来越小,增加就会越来愈慢,以达到反应当前key的访问频率,并且节省内存的目的。
减少频率:
- 每过一段时间,Redis 会减小 lru 字段的值,默认是每分钟减小一次。这确保了即使频繁访问过的键,如果长时间不再被访问,其频率计数也会逐渐减少。
无论是LRU还是LFU,在进行淘汰key的比较时都是比较redisobject结构体中的lru属性的值,而lru属性在LRU相关模式下会记录最近访问时间戳,在LFU模式化会用一个简单的算法记录访问频率。
内存碎片处理
redis内存管理依赖于jemalloc,他是一款由c语言编写的内存管理工具。
jemalloc("Jason Evans malloc")是一个高效的内存分配器,最初由 Jason Evans 开发。它主要用于替代标准库中的 malloc
实现,以提高内存分配和释放的性能,同时减少内存碎片。jemalloc 被广泛应用于许多高性能服务器软件和数据库系统中,包括 Redis、Facebook 的 HHVM、Couchbase 等。
jemalloc 的内存管理机制
内存池和分区分配:
- jemalloc 会提前将内存分成多个大小不同的块或内存池。每个内存池负责分配特定大小范围的内存块。这种方式减少了内存碎片并提高了内存分配效率。
小对象分配:
- 对于小对象,jemalloc 使用固定大小的内存块来进行分配。这种方式使得小对象的分配和释放速度非常快,并且减少了内存碎片的产生。
大对象分配:
- 对于大对象,jemalloc 直接从操作系统请求大块的连续内存,并进行管理和分配。
后台线程和脏页回收:
- jemalloc 可以配置后台线程来定期清理脏页和未使用的内存块。这种机制帮助减少内存碎片并提高内存使用效率。
Redis 中 jemalloc 的应用
内存分配请求:
- 当 Redis 需要分配内存时,它会调用 jemalloc 的内存分配函数(例如
malloc
、calloc
、realloc
等)来请求所需的内存。 - jemalloc 根据当前的内存池和分区策略,找到合适的内存块并返回给 Redis。
内存释放:
- 当 Redis 不再需要某块内存时,它会调用 jemalloc 的内存释放函数(例如
free
)来归还内存 - jemalloc 会将这块内存标记为可用,并在需要时重新分配给其他内存请求
内存清理和统计:
- redis可以通过进行相关配置开启jemalloc 的后台线程(默认不开启,因为清理内存回收脏页需要消耗大量的时间),定期清理和回收脏页。通过 Redis 的
INFO MEMORY
命令还可以获取详细的内存使用统计信息,以帮助监控和优化内存使用。
配置 jemalloc
可以通过设置环境变量或在 Redis 配置文件中配置 jemalloc 的参数,以优化内存管理。例如:
export MALLOC_CONF="background_thread:true,dirty_decay_ms:10000,muzzy_decay_ms:10000"
或者在 Redis 配置文件中添加:
jemalloc.background_thread:true jemalloc.dirty_decay_ms:10000
jemalloc.muzzy_decay_ms:10000