Home
avatar

.𝙃𝙤𝙬𝙚

向下滚动

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,有以下三种淘汰方式:

  1. 惰性删除:key 到期后不会主动删除,等到下次被访问时才检查是否过期,过期则删除。对 CPU 友好,但可能有大量过期 key 占用内存。
  2. 定期删除:每隔一段时间随机抽取一批 key 检查是否过期并删除。是惰性删除的补充,在 CPU 和内存之间取得平衡。
  3. 内存淘汰策略(当内存不足时触发):
    • 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 学习笔记,涵盖数据结构、高性能原理、缓存三连问、过期淘汰、持久化、高可用和分布式锁等核心知识点。

java知识点 Java Redis 缓存