1、Redis 五种基本类型
| 类型 | 底层结构 | 命令示例 | 生产场景 |
|---|---|---|---|
| String | String | SET/GET/INCR | 分布式锁、计数器、Session存储 |
| Hash | ziplist/dict | HSET/HGET/HINCRBY | 用户信息、购物车、配置项 |
| List | ziplist/quicklist | LPUSH/RPOP/LRANGE | 消息队列、最新动态列表 |
| Set | intset/dict | SADD/SMEMBERS/SINTER | 标签系统、好友推荐、去重 |
| ZSet | ziplist/skiplist | ZADD/ZRANK/ZRANGE | 排行榜、延迟队列、优先级队列 |
2、Redis 三种特殊类型

3、Redis 为什么这么快

纯内存访问:数据存储在内存中,读写延迟约 100ns
单线程模型:避免了多线程的上下文切换和锁竞争(Redis 6.0+ 引入多线程仅用于网络 I/O)
IO多路复用:使用epoll机制,单线程处理海量并发连接
高效数据结构:如SDS预分配空间减少内存重分配,跳表实现O(logN)查找
4、Redis I/O 多路复用

原理: Redis使用epoll(Linux)或kqueue(BSD)等系统调用,让单线程能够同时监听多个socket的文件描述符。当有事件就绪时,epoll返回就绪的fd列表,Redis依次处理。
Redis的I/O多路复用是基于Reactor设计模式实现的,它的核心组件被称为文件事件处理器(File Event Handler)。
这个处理器的工作流程像一条流水线:
监听:I/O多路复用程序(底层是epoll等)负责同时监听所有客户端的套接字(Socket)。
入队:当某个套接字有事件发生(如连接请求、数据可读),多路复用程序会将该套接字放入一个事件队列中。
派发:文件事件分派器(Dispatcher) 从这个队列里有序地、逐个地取出事件。
处理:分派器根据事件类型,调用对应的事件处理器(如命令处理器、回复处理器)来执行操作。
值得注意的是,这个分派和处理的过程是单线程的,这也是Redis核心操作是单线程的由来。它保证了命令执行的原子性,避免了锁竞争,也简化了设计。所有事件在队列里排队,逐个处理,上一个处理完,才会处理下一个。
一个常见误区:多路复用虽然是"异步"地通知你事件,但数据的读写操作本身是同步阻塞的(在连接就绪后,读/写数据时还是会阻塞)。它只是把"等待多个数据就绪"的过程变成了阻塞在epoll_wait这一个调用上,从而提升了整体效率


5、缓存穿透及解决方案
定义: 查询不存在的数据,缓存和DB都查不到,导致每次请求都到DB


🚀 方案一:缓存空对象(Cache Null Values)
这是最直接、最容易落地的方案。当数据库查询结果为空时,我们仍然将这个"空结果"缓存起来。
具体做法:查询时,如果缓存未命中,就去查数据库。若数据库返回空,则将 key-null 这样的键值对写入缓存,并设置一个较短的过期时间(如5分钟)。
优点:实现非常简单,几乎无代码侵入。能有效拦截大部分重复的穿透请求。
缺点:会占用额外的内存空间来存储大量空值。
如果攻击者不断变换不存在的Key(如随机生成ID),每个Key都会在缓存中留下一条空记录,形成"内存垃圾",无法根治恶意攻击。
🚀 方案二:布隆过滤器(Bloom Filter)------ 推荐方案
这是应对恶意攻击和大量无效Key最有效的方案。它就像一个"快速判断器",在请求到达缓存之前,就先拦截掉那些绝对不存在的Key。
具体做法:系统启动时,先将数据库中所有存在的Key(如所有合法的用户ID)用一个布隆过滤器存储起来。当请求到达时,先用布隆过滤器判断这个Key是否"可能存在"。如果它判定"肯定不存在",则直接返回,根本不会查询缓存或数据库。
优点:内存占用极小,几亿条数据也只需要几百MB内存。查询速度极快,远快于数据库查询,能彻底防御穿透攻击。
缺点:存在误判率(即可能把不存在的Key误判为存在,但反过来不会)。这意味着偶尔会有个别无效请求穿透到数据库,但概率很低,可以接受。
实现成本稍高,需要维护布隆过滤器的数据同步(使用Redis的Redisson或Google Guava库可以方便地实现)。
🔒 方案三:接口层参数校验(Parameter Validation)
这是最前置、成本最低的一道防线。很多穿透攻击就是利用了API参数的漏洞。
具体做法:在业务逻辑入口处,对用户请求的参数进行合法性校验。比如,自增ID不能为负数或0。UUID或订单号必须符合格式规范。分页查询的pageSize不能超过最大值(如1000)。
优点:能拦截掉大量不规范或恶意的简单攻击,几乎不消耗额外资源。
缺点:无法防御参数格式正确但数据不存在的请求(如一个合法的负ID格式)。
⚖️ 方案四:互斥锁(Mutex Lock)
这个方案主要用于解决"缓存击穿",但在特定场景下也能辅助防御缓存穿透,避免在同一时刻有大量请求去数据库查询同一个不存在的Key。
具体做法:当多个请求同时发现缓存未命中时,只让一个请求获得锁去查询数据库并重建缓存,其他请求则等待或稍后重试。
优点:能有效缓解高并发下对数据库的瞬时压力。
缺点:增加了系统的复杂度和响应延迟。
无法防御穿透:如果是一个不存在的Key,第一个拿到锁的请求查完后缓存了空值,后续请求可以命中空值。但如果攻击者用无数个不同的无效Key,锁就完全失效了。
总结
在实际生产环境中,最佳实践是"接口校验 + 布隆过滤器"的组合。
首先通过接口校验拦截掉格式错误的"低级"攻击。在缓存层之前,架设一个布隆过滤器,拦截掉所有不在数据库中的Key,这是对抗随机恶意穿透的杀手锏。为了确保万无一失,在数据库查询层,可以再配合缓存空对象作为最后一道兜底策略。这样层层设防,既能保证高并发下的系统稳定,又能有效保护后端数据库。
6、缓存击穿及解决方案
定义: 热点Key过期瞬间,大量请求穿透缓存直接访问DB。

🔒 方案一:互斥锁(Mutex Lock)------ 最稳妥的方案
这是最常用、最严谨的方案。它保证在缓存失效时,只有一个线程/进程能获得锁去查询数据库并重建缓存,其他线程必须等待。
具体做法(以Redis的SETNX命令为例):
请求发现缓存过期,尝试执行 SETNX lock_key value 获取锁。
成功获取锁的线程负责查询数据库,写入新缓存(带新TTL),最后释放锁。
获取锁失败的线程则休眠片刻(如50毫秒),然后循环重试,尝试从缓存中获取数据。
优点:逻辑清晰,数据一致性最强,能绝对保证数据库只被查询一次。
缺点:每次请求都会增加一次获取锁的网络开销,并且如果持有锁的线程执行失败(如崩溃),锁可能永远不会释放,需要设置锁的超时时间。
适用场景:对数据一致性要求高的核心业务。
🧱 方案二:逻辑过期(Logical Expiration)------ 性能最高的方案
这种方案不设置物理过期时间,而是给数据添加一个逻辑过期字段。读取时,如果发现数据"逻辑上过期了",就异步去更新缓存,而请求本身直接返回旧数据。
具体做法:
存入缓存时,值包含数据和一个expire时间戳,但Redis的TTL设为永久。
获取缓存时,解析出expire。若未过期,直接返回数据。
若已过期,立即返回旧数据给用户(保证响应快),同时异步(如线程池或消息队列)执行一个任务去数据库查询最新数据并更新缓存。
优点:响应速度极快,请求永远不会因重建缓存而阻塞,吞吐量极高。
缺点:数据一致性较弱,在缓存被异步更新前,会有一段短暂的数据不一致窗口期。
适用场景:高并发、对数据一致性要求不太苛刻的场景(如商品浏览量、点赞数),这也是许多大厂追求极致性能的首选方案。
🕰️ 方案三:永不过期 + 定时更新 ------ 折中方案
这个方案本质上是逻辑过期的简化版。不设TTL,但借助后台守护线程定时刷新缓存。
具体做法:
将热点Key的过期时间设为无限(永不过期)。
启动一个定时任务(如每隔10分钟),主动查询数据库,更新这些热点Key的值。
优点:实现简单,无需复杂的锁机制,也不会有缓存失效的瞬间。
缺点:定时任务存在更新延迟,在任务间隙中如果数据库数据变了,缓存不实时生效。且如果定时任务挂了,缓存就彻底变成死数据了。
适用场景:数据变更不频繁、允许一定延迟的业务(如系统配置、字典数据)

总结与实践建议
在实际开发中,需要根据业务特性做选择:
如果业务逻辑强依赖数据准确性(如金融交易),请毫不犹豫地使用互斥锁方案,用一点性能损失换取数据绝对正确。
如果是超高并发的核心查询接口(如首页推荐、热搜榜),逻辑过期方案是绝佳选择,它能带来极致的用户体验。
无论是哪种方案,提前识别并预热热点Key 都是必要的前置工作,比如在流量高峰前(如秒杀开始前),手动将热点数据加载到缓存中,可以有效避免击穿的发生。
击穿、穿透记忆技巧
口诀:"穿" = 不存在key = 直接穿过。
口诀:"击" = 击打一个点 = 热点Key失效。
7、缓存雪崩及解决方案
定义: 大量缓存同时过期或Redis宕机,导致所有请求涌向DB。

⏰ 方案一:设置不同的过期时间(TTL随机化)
这是最基础、成本最低的解决方案。如果所有缓存的过期时间都设定为相同值(比如统一1小时),那它们就会在同一时刻集体失效。
具体做法:在设置过期时间时,在基础时间上加上一个随机值。例如,如果想让缓存存活1小时,实际过期时间可以设为 3600秒 + 随机(0, 300)秒。
优点:实现极其简单,只需一行代码,就能把集中失效的压力分散到时间轴上,有效避免"雪崩"的峰值冲击。
缺点:只能缓解,不能根治。如果Key的数量级过大,即使错峰,瞬间回源的流量依然可能很高。
🔄 方案二:使用缓存高可用(如Redis Sentinel/Cluster)
雪崩的前提是缓存服务(Redis)本身可用,只是数据失效了。但如果Redis服务本身宕机了,那所有请求也会直接打到数据库,这同样属于雪崩的一种情况。
具体做法:部署Redis主从集群并搭配哨兵(Sentinel) 实现高可用,或者直接使用Redis Cluster(集群) 模式。这样,即使主节点挂掉,从节点可以迅速自动切换,保证缓存服务一直在线。
优点:从基础设施层面保证了缓存服务的稳定性,是应对"缓存服务器宕机"型雪崩的根本手段。
缺点:增加了运维成本和系统架构的复杂度。
⛑️ 方案三:熔断与降级(Hystrix/Sentinel)
这是防御雪崩的"保险丝"。当检测到数据库或缓存服务的响应变慢或出现大量超时时,直接切断部分流量,保护后端系统。
具体做法:
熔断:如果数据库查询的失败率达到阈值(如20%),则自动开启熔断器。在熔断窗口期内,所有对该数据的请求都会直接返回错误或默认值,不再查询数据库,给数据库喘息的机会。
降级:当流量达到警戒线时,对一些非核心服务(如商品评论、积分查询)直接返回兜底数据(比如缓存中的旧数据或空数据),释放资源给核心业务(如下单、支付)。
优点:保障了系统在极端情况下的整体可用性,防止"服务雪崩"的连锁反应(即数据库挂了导致整个系统瘫痪)。
缺点:需要引入额外的中间件(如Spring Cloud的Hystrix或阿里开源的Sentinel),并配置复杂的降级策略。
🔁 方案四:请求限流(Rate Limiting)
限流是降级的"温和版",它不直接返回错误,而是通过"排队等待"或"随机丢弃"的方式来控制并发请求的数量。
具体做法:使用令牌桶或漏桶算法,限制每秒能通过缓存层到达数据库的请求数量(比如限制为 N 个/秒)。多余的请求可以等待(如果设置了超时时间),或者直接返回"系统繁忙,请稍后再试"的提示。
优点:能有效地给数据库"减压",保护数据库不被瞬间流量冲垮。
缺点:会影响一部分用户体验,不适合对实时性要求极高的场景。
📦 方案五:提前预热 + 多级缓存
具体做法:
缓存预热:在系统流量高峰来临前(如电商大促前),提前手动或通过脚本将热点数据加载到Redis中,并设置合理的过期时间(结合随机化)。
多级缓存:不单纯依赖Redis,可以构建 本地缓存(如Guava Cache/Caffeine) + Redis(分布式缓存) + 数据库 的三级结构。当Redis雪崩时,大量请求可以回落到本地缓存(JVM内存)中读取,这部分数据虽然可能"旧一点",但能顶住大部分读流量。
优点:将热点数据尽量靠近用户或应用,极大地降低了雪崩对数据库的影响。
缺点:增加了内存开销,并且本地缓存的数据一致性维护更复杂。

8、布隆过滤器
布隆过滤器(Bloom Filter)是一个精巧的概率型数据结构,用来回答"某个元素在不在一个超大集合里"这个问题。它的高效之处在于,能以极小的内存开销和非常快的速度完成这个判断,但它给出的答案并非100%准确,有其独特的行为逻辑。
两个绝对(两大特征)
"绝对不在":如果布隆过滤器判断一个元素不存在,那么它100%肯定不在集合里。
"可能在":如果它判断一个元素存在,那它只是"可能"存在,有一定的概率是误判,这就是所谓的"假阳性"(False Positive)
三个步骤(工作流程)
其工作原理基于一个位数组和多个独立的哈希函数。
初始化:首先创建一个包含 m 个二进制位的数组(Bit Array),并将所有位初始化为 0。
添加元素:当要添加一个元素时,用 k 个不同的哈希函数分别对它进行计算,得到 k 个数组位置,然后将这些位置上的二进制位全部设置为 1。
查询元素:当要查询一个元素时,同样用这 k 个哈希函数计算位置。如果这些位置有任何一位是 0,就说明该元素绝对不存在;如果所有位都是 1,则说明该元素可能存在。
这种设计带来了一个代价:布隆过滤器不支持删除元素。因为当你把一个位置设为0时,可能会误删掉其他也映射到这个位置的元素的"指纹
在Java应用中,通常不需要直接发送命令,而是通过客户端库来操作。主要分两种场景:单机本地和分布式Redis。
java
场景一:单机本地环境 (Guava)
如果你的应用是单机的,可以使用Google的 Guava 库,它提供了内存中的布隆过滤器实现
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class GuavaBloomFilterDemo {
public static void main(String[] args) {
// 创建布隆过滤器:预计插入10000个整数,误判率为1%
BloomFilter<Integer> filter = BloomFilter.create(
Funnels.integerFunnel(),
10000,
0.01);
// 添加元素
filter.put(10086);
filter.put(10010);
// 查询元素
System.out.println(filter.mightContain(10086)); // true
System.out.println(filter.mightContain(99999)); // false
}
}
java
场景二:分布式环境 (Redisson)
在生产环境,当你的应用是分布式的,就需要使用一个集中式的布隆过滤器,Redisson 是一个很好的选择。它封装了Redis的布隆过滤器命令,使用起来非常直观
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
// ... 其他导入
// 1. 获取RedissonClient实例(此处省略配置过程)
RedissonClient redisson = ...;
// 2. 获取一个名为 "userBloomFilter" 的布隆过滤器对象
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("userBloomFilter");
// 3. 初始化过滤器:预估容量100000L,误判率0.003 (0.3%)
// 此操作会调用 Redis 的 BF.RESERVE 命令
bloomFilter.tryInit(100000L, 0.003);
// 4. 添加元素
bloomFilter.add("user:10086");
bloomFilter.add("user:10010");
// 5. 检查元素是否存在
boolean mightContain1 = bloomFilter.contains("user:10086");
System.out.println(mightContain1); // 输出: true
boolean mightContain2 = bloomFilter.contains("user:99999");
System.out.println(mightContain2); // 输出: false
// 还可以获取过滤器的一些统计信息
long count = bloomFilter.count(); // 近似元素个数
long size = bloomFilter.getSize(); // 位数组大小
布隆过滤器设置多大容量
布隆过滤器的容量设计,需要回答两个问题:"预计存多少元素(n)" 和 "能接受多高的误判率(p)"。
布隆过滤器占用的是bit(位),而不是字节。1 MB = 800万 bit,所以 1000万个元素、1%误判率,只需要 114 MB 内存。这个效率是非常惊人的。
原则一:宁大勿小。容量一旦设置,后期无法动态扩容(RedisBloom 模块从 v2.0 开始支持自动扩容,但性能有损耗)。如果存入了超过预计数量的元素,误判率会急剧上升。
原则二:误判率不是越低越好。误判率每降低一个数量级,内存大约要增加 50%。需要根据业务容忍度来设定,比如:用户ID过滤:误判率 1% 通常就足够了(即使误判,最多多查一次DB)。金融风控黑名单:可能需要 0.01% 甚至更低。
原则三:预留增长空间。如果你的业务数据量年增长 30%,那么容量设计时,n 应该按未来 1-2 年的数据量来估算,而不是当前数据量。
布隆过滤器占的是 Redis 的内存吗

布隆过滤器 不能删除解决方案

9、热key问题及解决方案
定义: 某个Key访问量特别大(如秒杀商品),单节点Redis扛不住。
Redis虽然是内存数据库,但它是单线程的。当海量请求全部集中到同一个Key上时,这个Key所在的Redis节点CPU会瞬间飙升,网络带宽被打满,导致该节点上的所有其他服务都受到影响,甚至引发整个集群的雪崩
解决方案

方案一:本地缓存(Local Cache)------ 最高效、最推荐
这是应对热Key最有效的手段。既然Redis单节点扛不住,那就让业务应用本身(JVM内存) 来分担一部分流量。
具体做法:
在应用服务器(如Java服务)内存中,使用 Caffeine、Guava Cache 等本地缓存框架。当请求到来时,先查本地缓存,命中则直接返回;未命中再查Redis,查到的结果同时写入本地缓存。
本地缓存设置较短的过期时间(如5秒),保证数据不至于太旧。
优点:访问本地内存的耗时是纳秒级,比访问Redis(微秒级)更快。极大减轻了Redis的压力,比如有100台应用服务器,一份热Key的流量就被分散成了100份。
缺点:数据有短暂的不一致窗口期(5秒内)。占用应用服务器的内存,需要合理设置上限。
适用场景:超高并发读取、对一致性要求不高的场景(如商品详情页、用户信息)。
方案二:读写分离(主从副本)------ 分散读压力
利用Redis的主从复制特性,让热Key的读取请求分散到多个只读从节点上。
具体做法:
部署一个主节点(负责写入)和多个从节点(负责读取)。将读请求通过负载均衡策略分发到不同的从节点上。从节点可以无限扩展(受限于内存和网络)。
优点:实现简单,对业务代码无侵入。能线性提升读能力。
缺点:增加了维护成本和资源开销。从节点有复制延迟,可能导致读取到旧数据。
如果热Key的写入也很频繁(如库存扣减),主节点依然是瓶颈。
适用场景:读多写少的热点数据。
方案三:Key分拆(Sharding)------ 把1个Key变成N个Key
这是对抗热Key最"硬核"的手段。与其让一个Key承受所有流量,不如在客户端逻辑上将一个Key拆分成多个副本Key。
具体做法:
假设热Key是 product:123,可以拆分为 product:123:0、product:123:1、product:123:2 ... product:123:9 共10个Key。
每个Key存储完全相同的数据。业务层在读取时,根据请求的一些特征(如用户ID取模、随机数),决定去访问哪一个副本Key。
java
// 伪代码:随机分拆
int shardIndex = ThreadLocalRandom.current().nextInt(10); // 0-9
String shardKey = "product:123:" + shardIndex;
Object value = redisTemplate.opsForValue().get(shardKey);
优点:将流量从1个节点分散到多个节点(在Redis Cluster中,不同Key会落到不同Slot,可能在不同节点)。效果立竿见影。
缺点:写入复杂:更新数据时,需要同时更新所有的副本Key(或者容忍短暂不一致)。内存浪费:同一份数据存了N份。代码有侵入性。
适用场景:读极高、写极少的数据(如配置数据、字典数据)。
方案四:监控与自动处理(提前发现)
热Key问题有时是突发的(如突发热点事件),因此发现热Key的能力和应对同等重要。
具体做法:
Redis 4.0+ 的 --hotkeys 命令:通过 redis-cli --hotkeys 可以分析出Redis实例中的热Key(需要开启LFU淘汰策略)。
使用京东开源的 hotkey:它是一个专门的热Key探测中间件,可以在Worker节点上统计Key的访问频率,当达到阈值时,通过推模式将热Key推送到所有业务服务器,触发本地缓存加载。
抓包分析:使用 tcpdump 或 CacheCloud 等工具分析网络流量。
方案五:二级缓存
二级缓存通常采用 L1(应用内缓存) + L2(Redis集中缓存) 的两层结构,部分超大规模系统甚至会延伸到 L3(数据库) 作为终极兜底。
java
请求入口
│
▼
┌─────────────────────────────────────┐
│ L1: 本地缓存 (Caffeine/Guava/Ehcache) │ ← JVM内存,速度: 纳秒级
│ 特点:快、近、占用应用内存 │
└─────────────────────────────────────┘
│ 未命中
▼
┌─────────────────────────────────────┐
│ L2: 分布式缓存 (Redis Cluster) │ ← 网络调用,速度: 微秒级
│ 特点:大、共享、所有应用实例共用 │
└─────────────────────────────────────┘
│ 未命中
▼
┌─────────────────────────────────────┐
│ L3: 数据库 (MySQL/其他存储) │ ← 速度: 毫秒级
│ 特点:数据最终来源 │
└─────────────────────────────────────┘

10、redis的过期策略
惰性删除(Lazy Expiration)------ 被动策略
核心思想:Key 过期后不做任何处理,任由它继续占用内存。只有当客户端主动访问这个 Key 时,Redis 才会检查它是否过期,如果过期则立即删除并返回空值。
工作流程:
java
客户端请求 GET mykey
↓
检查 mykey 是否设置了过期时间
↓
如果已过期 → 删除 mykey → 返回 nil
↓
如果未过期 → 正常返回 value

定时删除(Timed Deletion)
为 每一个设置了过期时间的 Key 都创建一个定时器(Timer)。当 Key 的过期时间到达的那一刻,定时器立即触发,执行删除操作。
java
SET mykey value EX 10
↓
Redis 内部创建一个定时器,设定 10 秒后触发
↓
10 秒后,定时器触发 → 立即删除 mykey → 内存立即释放

定期删除------ 主动策略
核心思想:Redis 会定期(默认每秒执行 10 次,即 100ms 一次)在后台主动检查并删除一批过期的 Key,防止惰性删除造成的内存堆积。
java
每 100ms(由 hz 参数控制,默认 10,即每秒 10 次):
1. 从 Redis 的过期字典中随机取出 20 个 Key
2. 删除其中已经过期的 Key
3. 统计本轮删除的过期 Key 数量
4. 如果删除比例 > 25%(即删了 5 个以上):
→ 重复步骤 1,继续抽取下一批
如果删除比例 ≤ 25%:
→ 停止本轮删除,等待下一个 100ms




11、Redis 为什么选择「惰性删除 + 定期删除」组合?
java
Key 过期了
│
▼
┌──────────────┐
│ 等待访问? │
└──────────────┘
│
┌───────┴───────┐
│ │
▼ ▼
被访问了 没被访问
│ │
▼ ▼
惰性删除 定期删除
(立即清理) (最多等100ms)
│
▼
┌──────────────┐
│ 定期扫描到? │
└──────────────┘
│
┌───────┴───────┐
│ │
▼ ▼
扫到了 没扫到
│ │
▼ ▼
立即删除 继续等待下一轮
(直到被扫到或被访问)
这个设计的精妙之处在于:
惰性删除:保证了 CPU 不会被过期 Key 拖垮,只在访问时才检查。
定期删除:保证了过期的 Key 即使不再被访问,也不会永久占据内存。
两者互补:惰性删除解决了"访问时清理"的需求,定期删除解决了"不被访问时堆积"的问题。
12、redis的内存淘汰策略


LRU(Least Recently Used,最近最少使用)
核心思想:淘汰最久没有被访问过的 Key。
判断依据:每个 Key 的最后一次访问时间。
举例:Key A 在 10:00 被访问过,Key B 在 10:05 被访问过。如果内存满了,LRU 会淘汰 Key A,因为它更久没被访问。
特点:对"突发流量"敏感。一个 Key 如果今天被访问了一次,之后再也不访问了,它仍然会被 LRU 保留较长时间,因为它的"最近一次访问时间"是今天的。
LFU(Least Frequently Used,最不经常使用)
核心思想:淘汰访问频率最低的 Key。
判断依据:每个 Key 的历史访问次数(带有衰减机制,防止旧数据永远占位)。
举例:Key A 被访问了 100 次,Key B 被访问了 3 次。LFU 会淘汰 Key B。
特点:能更精准地识别"冷热数据"。一个 Key 如果是历史热点(昨天被狂刷),但今天已经冷了,LFU 的衰减机制会让它逐渐被淘汰。
各策略详细解读与选型建议
noeviction ------ 默认策略,最保守
行为:内存满了之后,所有写入命令(如 SET、LPUSH、INCR 等)都会返回错误,但读命令(如 GET)仍然正常。
适用场景:数据绝对不能丢失的场景,比如存储用户订单、交易记录等核心数据。宁可报错,也不删数据。
2allkeys-lru ------ 最推荐、最通用的策略
行为:在所有 Key 中,淘汰最近最少使用的。
适用场景:绝大多数通用缓存场景。比如商品信息缓存、用户会话缓存等,希望热点数据常驻内存,冷数据被淘汰。
为什么推荐:它在绝大多数业务场景下表现最稳定,实现简单,效果良好。
allkeys-lfu ------ 更精准的热点识别(Redis 4.0+)
行为:在所有 Key 中,淘汰访问频率最低的。
适用场景:有明显"冷热"差异的场景,比如热搜榜、推荐算法中的特征数据。一个 Key 如果被访问次数极低,说明它确实不重要,可以优先淘汰。
注意:LFU 比 LRU 更消耗 CPU(需要维护计数器和衰减逻辑),如果对 CPU 敏感,优先选 LRU。
allkeys-random ------ 佛系淘汰
行为:在所有 Key 中,随机淘汰一个。
适用场景:所有 Key 的访问概率几乎相同的场景,比如一些静态配置数据、随机数据池。这时随机淘汰的成本最低。
volatile-lru ------ 分级缓存
行为:只在 设置了过期时间(TTL) 的 Key 中,淘汰最近最少使用的。
适用场景:你需要明确区分"可牺牲数据"和"不可牺牲数据"。比如,把用户购物车数据设为永不过期(不可牺牲),把商品详情页缓存设置为有过期时间(可牺牲)。这样内存满时,只淘汰可牺牲的缓存,不碰核心数据。
volatile-lfu ------ 分级缓存 + 精准热点
行为:只在设置了 TTL 的 Key 中,淘汰访问频率最低的。
适用场景:同 volatile-lru,但需要更精准地识别冷热数据。
volatile-random ------ 分级缓存 + 随机
行为:只在设置了 TTL 的 Key 中,随机淘汰一个。
适用场景:设置了 TTL 的 Key 之间访问概率差不多。
volatile-ttl ------ 按剩余寿命淘汰
行为:只在设置了 TTL 的 Key 中,淘汰剩余存活时间(TTL)最短的,也就是最快过期的。
适用场景:希望尽早释放即将过期的内存,让内存保持清爽。但如果 TTL 最短的 Key 反而是热点,这种策略就误删了热点数据,慎用。
13、redis常用应用场景

14、redis场景命令
