Redis 核心知识|java知识点
Redis 数据结构
Redis 支持以下核心数据结构:
- String(字符串):最基本的数据类型,可存储字符串、整数或浮点数。典型场景包括缓存对象、计数器、分布式锁。
- List(列表):有序的字符串列表,支持左右插入。可实现简单的消息队列、最新消息排行。
- Set(集合):无序且不重复的字符串集合。适合做标签、共同好友、去重。
- Hash(哈希):键值对集合,适合存储对象,如用户信息、商品属性。
- ZSet(有序集合):带分数的有序集合,每个元素关联一个分数用于排序。典型场景为排行榜、延迟队列。
- Bitmap(位图):基于字符串的位操作,适合统计用户签到、在线状态等二值数据。
Redis 为什么这么快
Redis 的高性能可以从三个维度来解释:
- 基于内存操作:数据存储在内存中,读写速度远超磁盘 IO。
- 优化的数据结构:Redis 在底层对数据结构做了大量优化,如 SDS(简单动态字符串)、ziplist、quicklist、skiplist 等,在特定场景下性能极高。
- 单线程模型 + IO 多路复用:Redis 6 之前采用单线程执行命令,避免了线程切换和锁竞争的开销。底层使用 Reactor 事件处理模型 + 非阻塞 IO(epoll/kqueue),单个线程可以高效处理大量并发连接。
Redis 6 之后的变化
Redis 并非完全的单线程。命令执行仍然是单线程,但 Redis 6 之后引入了多线程 IO:网络 IO 阶段使用多个线程并行读写数据,减少网络 IO 瓶颈。这样设计的原因是 Redis 的性能瓶颈往往在网络 IO 而非 CPU,多线程 IO 可以在不引入锁竞争的前提下提升吞吐量。
原子性与 Lua 脚本
Redis 单线程执行命令,同一时间只会执行一个命令,不存在并发修改,因此单条 Redis 命令天然是原子性的。
Lua 脚本也能保证原子性:当 Redis 执行一个 Lua 脚本时,整个脚本作为一条命令执行,不会被其他命令打断。但要注意:如果不是通过 Lua 脚本执行的多条 Redis 命令,在多条命令之间可能会被其他客户端的命令插入,无法保证原子性。
缓存穿透、击穿与雪崩
缓存穿透
问题:大量请求的 key 在缓存和数据库中都不存在,每次请求都会穿透缓存直达数据库,可能压垮数据库。
解决方案:
- 使用布隆过滤器:在缓存前增加一层布隆过滤器,快速判断 key 是否可能存在。
- 缓存空值:将查询结果为空的 key 也缓存起来,设置较短的过期时间。
- 接口层做参数校验和限流。
缓存击穿
问题:某个热点 key 在高并发场景下突然过期,大量请求同时打到数据库上,造成数据库压力骤增。
解决方案:
- 热点数据预热:提前加载热点数据,设置较长的过期时间或不过期。
- 加互斥锁:只允许一个线程回源数据库查询,其他线程等待该线程回写缓存后读缓存。
- 逻辑过期:缓存不设物理过期时间,在 value 中记录逻辑过期时间,发现过期时异步更新。
- 限流、降级兜底。
缓存雪崩
问题:大面积的 key 同时过期,或者 Redis 节点宕机,导致大量请求直接打到数据库。
解决方案:
- 随机化过期时间:在基础过期时间上加随机偏移,避免同时失效。
- Redis 集群:部署哨兵或集群模式,避免单点故障。
- 二级缓存:本地缓存(如 Caffeine)作为二级缓存兜底。
- 后台线程定期续命热点数据。
- 服务限流与降级。
过期淘汰机制
Redis 中设置了过期时间的 key,有以下三种淘汰方式:
- 惰性删除:key 到期后不会主动删除,等到下次被访问时才检查是否过期,过期则删除。对 CPU 友好,但可能有大量过期 key 占用内存。
- 定期删除:每隔一段时间随机抽取一批 key 检查是否过期并删除。是惰性删除的补充,在 CPU 和内存之间取得平衡。
- 内存淘汰策略(当内存不足时触发):
noeviction:不删除,内存不足时返回错误。allkeys-lru:从所有 key 中淘汰最近最少使用的。volatile-lru:从设置了过期时间的 key 中淘汰最近最少使用的。allkeys-lfu:从所有 key 中淘汰使用频率最低的。volatile-ttl:从设置了过期时间的 key 中淘汰剩余过期时间最短的。allkeys-random:随机淘汰。
持久化
Redis 持久化主要有两个用途:容灾恢复和数据备份。
RDB(快照持久化)
在某个时间点将内存中的全部数据写入磁盘,生成一个紧凑的二进制快照文件(dump.rdb)。
- 触发方式:手动执行
SAVE(阻塞)或BGSAVE(fork 子进程,非阻塞);也可配置自动触发(如save 900 1表示 900 秒内有 1 次修改则触发)。 - 优点:文件小、恢复速度快,适合全量备份和灾难恢复。
- 缺点:两次快照之间的数据可能丢失(非实时),数据安全性较低。
AOF(追加日志持久化)
将每个写命令追加记录到 AOF 文件中。数据恢复时重放 AOF 文件中的命令即可还原数据。
- 刷盘策略:
always:每条命令都刷盘,数据最安全但性能最差。everysec:每秒刷盘一次,推荐的折中方案,最多丢失 1 秒数据。no:由操作系统决定何时刷盘(通常约 30 秒),性能最好但数据安全性最差。
- AOF Rewrite(重写):AOF 文件会不断增长,Redis 提供重写机制来压缩 AOF 文件。重写时 Redis 读取当前数据状态,生成一个最小命令集的 AOF 文件来替代旧文件。可通过
auto-aof-rewrite-percentage和auto-aof-rewrite-min-size配置自动触发条件。 - 优点:数据安全性高(
everysec最多丢 1 秒),可读性好。 - 缺点:文件通常比 RDB 大,恢复速度较慢。
在实际生产环境中,通常同时开启 RDB 和 AOF,以 RDB 做定期全量备份,以 AOF 做增量恢复保障。
高可用
哨兵模式(Sentinel)
哨兵集群负责监控 Redis 主从节点的运行状态。当主节点发生故障时,哨兵通过选举算法(Raft)从从节点中选出新的主节点,实现自动故障转移。建议部署奇数个哨兵节点(如 3 个或 5 个),防止脑裂问题。
Redis Cluster
Redis Cluster 是分布式集群方案,数据被自动分片到多个节点(最多 16384 个槽位)。每个主节点负责一部分槽位,每个主节点可以有从节点做备份。支持自动故障转移和水平扩展。
缓存读写策略
旁路缓存(Cache Aside)
最常用的策略。读请求先查缓存,命中则返回;未命中则查数据库并回写缓存。写请求先更新数据库,然后删除缓存(而非更新缓存),下次读取时再重建缓存。
读写穿透(Read/Write Through)
缓存层统一代理数据库的读写操作。应用只和缓存交互,缓存负责同步读写数据库。实现复杂,实际使用较少。
异步缓存写入(Write Behind)
先更新缓存,通过异步批量的方式将变更写回数据库。写性能最高,但存在数据丢失风险。适合写多读少且允许短暂不一致的场景,如计数器。
分布式锁
使用 Redis 实现分布式锁的基本方式是 SET key value NX EX timeout(不存在时设置,并附带过期时间)。
常见坑点:
- 锁过期但业务未完成:需要续期机制(如 Redisson 的 WatchDog)。
- 释放别人的锁:锁的 value 必须使用唯一标识(如 UUID),释放时先比较再删除,需要 Lua 脚本保证原子性。
- Redis 主从切换丢锁:主节点加锁后未同步到从节点就宕机,从节点升主后锁丢失。可使用 RedLock 算法(多节点)降低风险,但在网络分区场景下仍有争议。
本文内容整理自 Redis 学习笔记,涵盖数据结构、高性能原理、缓存三连问、过期淘汰、持久化、高可用和分布式锁等核心知识点。
