Redis作为开发者面试中的“常青树”,不仅仅因为它的高性能,更因为它背后复杂的原理和机制。从过期删除策略到内存淘汰策略,再到缓存“杀手锏”——大Key问题,本文帮你逐一剖析,让你在面试中信心满满。
一、过期删除策略
Redis中设置了数据的过期时间,用以自动回收无用数据,优化内存使用。那么,Redis的过期删除策略有哪些?它们的原理又是怎样的?
常见过期删除策略
1. 定时删除
- 特点:内存友好,CPU不友好
- 说明:在设置过期时间时,同步设置定时事件,通过事件删除过期数据,因为过期后立即删除,所以过期数据不会占用太多内存空间,删除Key需要占用CPU,过期数据多的情况下会长时间占用CPU
2. 惰性删除
- 特点:CPU友好,内存不友好
- 说明:只有当访问这个key的时候才会判断是否过期,过期后执行删除操作,因此占用CPU最少,缺点就是如果不访问的话,数据会一直在内存中,不会释放,会造成内存的浪费
3. 定期删除
- 特点:CPU占用比较少,也不会过于浪费内存,取决于执行的频率
- 说明:周期性地随机从设置了过期时间的 key 中抽查一批,然后逐个检查这些 key 是否过期,过期就删除 key
Redis 采用方式
Redis 采用的是 定期删除+惰性/懒汉式删除 结合的策略
二、内存淘汰策略
当Redis内存达到最多限制时,必须选择淘汰某些Key。不同场景下,淘汰策略不同,常用的几种如下:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| noeviction(不淘汰) | 内存满了后拒绝写入 | 只读场景或不允许丢失数据 |
| volatile-lru(LRU-有限) | 从设置了过期时间的Key中,采用LRU策略淘汰 | 受限过期Key池中淘汰 |
| allkeys-lru(所有Key) | 任何Key都可能被淘汰,采用LRU策略 | 一般场景,保证内存不溢出 |
| volatile-random(随机淘汰) | 在设置了过期的Key中随机淘汰 | 简单粗暴 |
| allkeys-random(所有Key随机淘汰) | 任意Key随机淘汰 | 高速场景 |
| volatile-ttl(TTL优先) | 设置了较短剩余时间的Key优先淘汰 | 自动优先扔掉快到过期的Key |
| allkeys-lfu | 淘汰整个键值中最少使用的键值 |
原理原理:
- 通过LRU(最近最少使用)算法,淘汰掉较久未用的Key。
- LFU 全称是 Least Frequently Used 翻译为最近最不常用,LFU 算法是根据数据访问次数来淘汰数据的
三、缓存三剑客
缓存雪崩
现象及原因:
当大量缓存数据失效或者Redis服务宕机时,大量的请求就会直接访问数据库,导致数据库访问量剧增,严重的话甚至会导致服务器宕机
由此可知,造成缓存雪崩有三个原因:
- 大量缓存数据同时失效
- 服务器宕机
- 大量请求同时访问
其中 大量请求同时访问是一定满足的,缓存失效和服务器宕机只要满足一个即可
解决方案
解决方式就是避免出现上面的情况:
大量请求同时访问:
- 限流,保证同一时间请求量不超过数据库可以承受的范围
服务器宕机:- 保证服务的高可用(集群)
缓存同时失效:
- 失效时间随机:随机设置失效时间,这样可以避免大量缓存同时到期,缺点是失效时间不固定,问题不易排查。
- 提前预热:将热点数据提前放入缓存中,并设置合理的过期时间。
- 持久缓存策略:设置永不过期,缺点占用内存多
缓存击穿
现象及原因:
现象和缓存雪崩很类似,不过缓存雪崩是大量的数据缓存数据同时失效,缓存击穿失效的是某个访问量高的数据,也就是常说的热点数据
解决方式可以参考缓存雪崩部分
缓存穿透
现象及原因:
缓存穿透指的是服务访问了一个根本不存在的数据,导致缓存无法命中,直接访问了数据库,跟没有缓存一样
解决方案:
解决方案一般有两种:
- 缓存空数据:当访问到不存在的数据时,设置一个默认值或空值,这样第二次访问的时候就会命中缓存,而不会访问到数据库了
- 布隆过滤:使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在
四、bigkey问题
注意bigkey不是指key的值太大,而是key对应的Value过大
1. 什么是 BigKey?
BigKey 是指在 Redis 中存储的数据对象(比如字符串、哈希、列表、集合等)非常大,超过了合理的范围。常见的 BigKey 类型包括:
- 大字符串(String):当一个字符串对象的长度非常大时,它就变成了一个 BigKey。
- 大哈希(Hash):哈希表中的字段和字段值都可以非常大,导致整个哈希对象变得异常庞大。
- 大列表(List)、大集合(Set)、大有序集合(Sorted Set):这些数据结构中,元素的数量或单个元素的大小都可能导致它们变成 BigKey。
一般来说,如果一个数据对象的大小超过了几 MB,尤其是几 GB,就可以被视为 BigKey。
2. BigKey 的危害
影响性能
- 内存占用大:Redis 是一个基于内存的数据存储系统,存储一个非常大的对象会占用大量的内存,这会导致系统内存压力增加,可能引发 OOM(内存溢出)。
- 阻塞操作:在执行涉及到 BigKey 的操作时(比如
GET、SET、DEL等),这些操作会耗费大量的 CPU 时间和 I/O 操作,造成 Redis 的阻塞。例如,删除一个大哈希时,Redis 需要遍历所有字段并删除它们,这会阻塞服务器其他的请求。 - 影响持久化性能:大对象会影响 Redis 的持久化过程,尤其是 RDB 快照和 AOF 重写。持久化时,Redis 必须将这些大对象序列化到磁盘,可能导致磁盘 I/O 占用过多,影响性能。
集群和主从同步问题
在 Redis 集群或主从架构中,BigKey 会导致:
- 同步延迟:复制一个大对象时,网络传输和磁盘 I/O 会非常耗时,导致主从同步延迟增加。
- 数据迁移难度:在进行 Redis 分片或节点迁移时,大对象的移动和同步会占用大量时间,增加分片操作的复杂性。
3. 如何检测 BigKey?
使用 redis-cli 工具
可以通过 redis-cli 提供的一些命令来检测 BigKey:
MEMORY USAGE命令:该命令可以返回指定键的内存使用情况。如果某个键的内存占用过大,就可能是 BigKey。MEMORY USAGE mybigkeySCAN命令配合MEMORY USAGE:如果你想检查整个 Redis 数据库的所有键,可以使用SCAN遍历所有键,并对每个键使用MEMORY USAGE检查其内存占用:`SCAN 0 MATCH * COUNT 1000`- Redis BigKey 工具:Redis 提供了一些开源的工具来帮助检测 BigKey,比如
redis-bigkey或者redis-insight,这些工具能够扫描 Redis 中的所有键,帮助识别大对象。
配置 maxmemory 限制
可以通过设置 Redis 的 maxmemory 限制内存的最大使用量,从而避免过大的键占用过多内存。通过设置内存上限,Redis 会在达到限制时自动执行淘汰策略。
4. 如何避免 BigKey 问题?
合理的数据设计
避免将过大的数据存储在一个键中。对于可能会产生大数据量的场景,可以考虑将数据拆分成多个较小的键。例如:
- 对于大字符串,可以将其拆分成多个小字符串进行存储。
- 对于大哈希表,可以将它拆分成多个小哈希,或将哈希表按字段进行分片。
- 对于大列表,可以使用分片列表或分页的方式存储。
使用 Redis 数据结构优化
- 分布式数据结构:如果必须存储大量数据,可以利用 Redis 的分布式数据结构(如 Redis Cluster),将数据拆分存储在多个节点上,减轻单个节点的内存压力。
- 批量操作:对于需要插入大量数据的操作,可以考虑使用批量操作的方式,减少单次操作的内存占用。
设置合理的过期时间
如果某些数据只是临时数据,可以为它们设置过期时间,这样可以避免过多的 BigKey 在 Redis 中长时间存在。
监控和报警
- 内存监控:定期监控 Redis 的内存使用情况,特别是内存的增长速度,及时发现异常的内存占用。
- 报警机制:配置 Redis 的
INFO MEMORY输出信息并结合监控工具(如 Prometheus、Grafana)来设置报警阈值,及时发现内存异常。
避免频繁的 DEL 操作
删除 BigKey 会对 Redis 性能产生较大影响,尽量避免频繁删除大的数据对象。如果需要删除大量数据,可以考虑使用异步的方式,逐步删除。
解决方案
- 监控大Key:定期检测Key的大小,提前预警。
- 拆分大Key:将大Key拆分成多个小Key存储,减少单个Key的压力。
- 限制Key大小:设计合理的数据存储结构,避免无意识的堆积。