Redis底层原理是什么

Redis 的高性能源于其精妙的底层设计,主要包括以下几个方面:

1. 内存存储 (In-Memory Data Store)

  • 核心原理: 所有数据主要存储在 RAM 中。这是 Redis 速度快的最根本原因,避免了传统磁盘数据库的 I/O 瓶颈。

  • 优势: 内存访问速度比磁盘快几个数量级。

  • 代价: 数据量受限于内存大小,成本较高,且需要持久化机制防止断电丢失。

2. 高效的数据结构 (Rich & Optimized Data Structures)

Redis 不仅仅是简单的 Key-Value 存储,其 value 支持多种数据结构,且每种结构都针对特定操作进行了高度优化:

  1. 简单动态字符串(string)、 整数集合(整数 set)
  2. 压缩列表 (小规模 List, Hash, Sorted Set/zset) 
  3. 快速列表(list) 、字典(Key-Value 映射) (set,Hash)、跳跃表(zset)
  • 简单动态字符串 (SDS - Simple Dynamic String):

    • 相比 C 原生字符串,SDS 存储了长度信息,获取字符串长度复杂度为 O(1)。

    • 预留空间 (预分配 & 惰性释放),减少修改字符串时内存重分配次数。

    • 二进制安全,可以存储任意二进制数据(如图片)。

  • 字典 (Hash Table / Dict): Redis 的基石。(set,Hash)

    • 用于实现 Redis 数据库本身 (Key-Value 映射) 和 Hash 类型。

    • 使用 MurmurHash2 等高效哈希算法。

    • 采用 渐进式 rehash:当哈希表需要扩容或缩容时,不是一次性完成,而是分多次、渐进式地将旧桶中的数据迁移到新桶,避免单次操作耗时过长阻塞服务。

    • 使用 链地址法 解决哈希冲突。

  • 跳跃表 (Skip List):

    • 用于实现 有序集合 (Sorted Set)。

    • 在链表基础上添加多级索引,使得范围查询 (如 ZRANGE)、按排名访问的平均时间复杂度达到 O(logN)。

    • 实现比平衡树(如红黑树)简单,且范围查询更高效。

  • 压缩列表 

    • ‌ziplist‌(Redis7.0前):连续内存块存储节点,每个节点记录前驱长度和自身数据,支持快速头尾操作,但存在级联更新风险。

      • 用于在小规模数据时实现 List, Hash, Sorted Set,以节省内存。

      • 是一块连续的内存空间,紧凑存储多个元素及其长度信息。

      • 当元素数量或大小超过阈值时,会自动转换为更高效(但内存占用稍大)的结构(如双向链表、哈希表、跳表)。

      listpack‌(Redis7.0后):改进节点编码方式,消除级联更新问题,进一步提升性能。‌‌

  • 快速列表 (QuickList - Redis 3.2+):

    • 用于实现 List 类型。

    • 是 ZipList 和双向链表的结合体。一个 QuickList 是由多个 ZipList 节点组成的双向链表。

    • 平衡了内存效率和操作 (插入/删除) 性能。

  • 整数集合 (IntSet):

    • 当 Set 中所有元素都是整数且数量较少时使用。

    • 是一块连续内存,按整数大小有序存储。

    • 节省内存,查找效率高 (二分查找)。

  • Stream (Redis 5.0+): 用于实现消息队列,底层主要使用基数树 (Rax Tree / Radix Tree) 来高效存储消息 ID 和内容。

  • 外部数据类型:
    • ‌String类型‌:每个字段单独存储为一个键值对,例如用户信息可能分散存储为user:123:name、user:123:age等键
        •   
        • 为了节省空间,进行了空间预分配。底层通过sds5/8来,用一个len标记长度  

           

    • Hash类型‌:将多个字段存储为一个结构化对象(类似HashMap),例如user:123对应{name: "Alice", age: 30}
    • list
    • set
      • 小数据量且全为整数时,用 整数集合 节省内存;
      • 大数据量或包含字符串时,用 哈希表 保证高效操作。
    • zset

3. 单线程模型 (核心处理逻辑 - Event Loop)

  • 核心原理: Redis 在处理客户端命令、执行操作、返回结果的核心逻辑是单线程的 (在 6.0 之前是完全单线程,6.0 后引入多线程 I/O,但命令执行本身仍是单线程)。

  • 优势:

    • 避免锁竞争: 无需复杂的锁机制(如互斥锁、读写锁),极大地简化了实现,提高了性能。

    • 无上下文切换: 单线程无需在多个线程间切换,减少了 CPU 开销。

    • 操作的原子性: 所有命令执行都是原子的,不存在并发修改问题。

  • 如何高效? 依赖其 非阻塞 I/O 多路复用模型 (I/O Multiplexing):

    • 使用 epoll (Linux)、kqueue (BSD/macOS)、select (跨平台但效率低) 等系统调用。

    • 单线程的 事件循环 (Event Loop) 可以同时监听和管理大量的客户端套接字连接。

    • 当某个连接有可读 (请求到达) 或可写 (可以发送响应) 事件发生时,事件循环会通知 Redis 主线程进行处理。

    • 这使得单线程能够高效处理高并发连接。

  • 瓶颈: 单个命令的执行时间不能过长,否则会阻塞后续所有命令。因此 Redis 命令通常设计得非常快,且避免使用慢查询。

 

4. 多线程 I/O (Redis 6.0+)

  • 背景: 随着网络带宽增加,单线程处理网络 I/O (读取请求、写回响应) 可能成为瓶颈。

  • 原理:

    • 主线程 仍然负责:事件循环、命令解析、命令执行、响应结果的组织。

    • 多线程 (I/O Threads) 负责:读取客户端请求数据 (socket read) 和 写回 响应数据给客户端 (socket write)。

    • 命令执行本身仍然是单线程的,保持了原子性。

  • 优势: 显著提升了网络吞吐量,尤其在需要返回大量数据(如 LRANGEHGETALLKEYS* - 慎用)或客户端数量极多时。

5. 持久化 (Persistence)

为了解决内存数据易失性问题,Redis 提供两种主要持久化方式:

  • RDB (Redis Database):

    • 原理: 在指定时间间隔内,生成内存数据的快照 (Snapshot) 保存到磁盘上的 .rdb 文件。

    • 触发方式: 手动 (SAVE/BGSAVE) 或自动配置 (如 save 900 1)。

    • 过程 (BGSAVE): Fork 一个子进程,由子进程负责将数据写入临时 RDB 文件,写入完成后替换旧文件。父进程继续处理请求。SAVE 会阻塞主线程。

    • 优点: 文件紧凑,恢复速度快。适合备份和灾难恢复。

    • 缺点: 可能丢失最后一次快照之后的数据。fork() 操作在数据量大时可能阻塞主线程(虽然时间通常很短)。

  • AOF (Append Only File):

    • 原理: 记录服务器收到的每一个写操作命令(以 Redis 协议格式),追加写入到文件末尾。

    • 写回策略:

      • always: 每个命令都同步写磁盘,最安全,性能最低。

      • everysec (默认): 每秒同步一次(由后台线程完成),最多丢失 1 秒数据,是安全与性能的折衷。

      • no: 由操作系统决定何时同步,性能最好,但丢失数据风险最高。

    • 重写 (Rewrite): AOF 文件会越来越大。Redis 会 fork 子进程,根据当前内存数据生成一个新的、更小的 AOF 文件(包含重建数据集所需的最少命令序列),替换旧文件。

    • 优点: 数据安全性更高(取决于 fsync 策略),最多丢失 1 秒数据(默认策略)。文件可读性较好(便于人工修复)。

    • 缺点: 文件通常比 RDB 大。恢复速度通常比 RDB 慢。在写入负载高时可能影响性能。

  • 混合持久化 (AOF + RDB - Redis 4.0+):

    • 原理: 在 AOF 重写时,子进程将当前内存数据快照以 RDB 格式写入新的 AOF 文件的开头,然后将重写缓冲区中的增量写命令(AOF 格式)追加到文件末尾。生成的文件包含 [RDB Head] + [AOF Tail]

    • 优点: 结合了 RDB 快速恢复和 AOF 丢失数据少的优点。恢复时先加载 RDB 快照,再重放增量 AOF 命令,速度更快。

6. 高可用与扩展 (High Availability & Scalability)

  • 主从复制 (Replication):

    • 原理: 一个 主节点 (Master) 处理写操作,将写命令异步复制给一个或多个 从节点 (Slave/Replica)。从节点处理读操作。

    • 作用: 数据冗余备份、读写分离提高读吞吐量、故障恢复基础。

    • 过程: 首次全量同步 (RDB 快照传输) + 后续增量同步 (命令传播)。

  • 哨兵 (Sentinel):

    • 原理: 分布式系统,由多个 Sentinel 进程组成,用于监控主节点和从节点的健康状态。

    • 功能: 自动故障转移 (主节点宕机时,选举新主节点并通知客户端)、配置提供者、监控通知。

    • 作用: 实现 Redis 的高可用 (HA)。

  • Redis 集群 (Cluster - Redis 3.0+):

    • 原理: 分布式数据分片方案。将数据自动分割到 16384 个哈希槽 (slot) 中。集群由多个节点 (Node) 组成,每个节点负责一部分槽。

    • 数据路由: 客户端根据 Key 计算 CRC16 校验和再取模 16384 得到所属槽,然后连接到负责该槽的节点。客户端或代理 (如 Redis 集群模式的客户端库) 处理重定向 (MOVEDASK)。

    • 高可用: 每个分片通常是主从结构(主节点 + 一个或多个从节点)。主节点故障时,其从节点会被提升为新主节点。

    • 作用: 实现水平扩展(分摊数据存储和访问压力)和高可用。

7. 其他关键机制

  • 虚拟内存 (VM - 已废弃): 早期版本尝试将不常用数据交换到磁盘,但实现复杂且效果不佳,已被废弃。现在主要依赖内存和淘汰策略。

  • 事务 (Transactions): 通过 MULTIEXECWATCH 等命令实现。它只是将多个命令打包、顺序执行,不保证严格意义上的 ACID 隔离性 (没有回滚,只有乐观锁 WATCH)。满足原子性和隔离性(串行执行)。

  • 管道 (Pipelining): 客户端一次性发送多个命令而无需等待每个命令的回复,服务器依次执行并一次性返回所有结果。大幅减少网络往返时间 (RTT),是提高批量操作性能的关键手段。

  • 发布/订阅 (Pub/Sub): 消息通信模式,生产者 (Publisher) 发送消息到频道 (Channel),消费者 (Subscriber) 订阅频道接收消息。是轻量级的消息队列方案。

  • Lua 脚本: Redis 内置 Lua 解释器。客户端可以发送 Lua 脚本在服务器端原子性地执行多个操作。避免了事务的限制,功能更强大。

  • 内存管理:

    • 使用自己的内存分配器 (如 jemalloc) 替代默认的 libc malloc,减少内存碎片,提高分配效率。

    • 内存淘汰策略 (Eviction Policies): 当内存不足时,根据配置的策略 (maxmemory-policy) 删除 Key 来腾出空间。常见策略有:

      • noeviction: 不淘汰,写操作报错 (默认)。

      • allkeys-lru / volatile-lru: 基于 LRU (最近最少使用) 淘汰所有键 / 只淘汰设置了过期时间的键。

      • allkeys-lfu / volatile-lfu (4.0+): 基于 LFU (最不经常使用) 淘汰。

      • allkeys-random / volatile-random: 随机淘汰。

      • volatile-ttl: 淘汰即将过期 (TTL 最小) 的键。

  • 超时键删除:在 Redis 里面,有三种过期策略分别是:定时过期、惰性过期、定期过期。而 Redis 中同时使用了惰性过期和定期过期两种过期策略。

    定时过期,是指每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即对 key 进行清 除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的 CPU 资源去处理过期的数据,从 而影响缓存的响应时间和吞吐量

    惰性过期:是指被动访问某个 key 的时候,才会判断 key 是否已过期,过期则清除。该策略可以最大 化地节省 CPU 资源,却对内存非常不友好。极端情况可能出现大量的过期 key 没有再次被访问,从 而不会被清除,占用大量内存。
    但是这两种方案都有点极端,Redis 里面还有另外一种折中的方案.

    就是定期过期。(Redis 时间轮算法) 这种方案会周期性轮询 redis 库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制 删除频率。

    这种方式可以通过调整定时扫描的时间间隔和每次扫描耗时,实现在不同情况下使得 CPU 和内存资源 达到最优的平衡效果。

总结

Redis 的卓越性能是其底层多个精妙设计协同作用的结果:内存存储提供了基础速度,精心优化的数据结构保障了各种操作的效率,单线程事件循环 (结合 I/O 多路复用) 避免了并发开销并简化了实现,渐进式 rehash 等机制保证了平滑操作,持久化机制 (RDB/AOF/混合) 解决了数据可靠性问题,而主从复制、哨兵、集群则提供了高可用和扩展能力。理解这些底层原理对于高效使用、调优和运维 Redis 至关重要。

posted @ 2025-06-25 07:05  飘来荡去evo  阅读(68)  评论(0)    收藏  举报