Redis - 基本架构:一个键值数据库到底由什么组成
2026/5/31 8:57:20 网站建设 项目流程

文章目录

  • 引言
  • 从数据模型与操作接口入手
  • 数据放内存还是放磁盘
  • 访问框架:客户端怎么和数据库说话
  • 索引模块:从 key 找到 value
  • 操作模块:不同操作的具体逻辑
  • 存储模块:重启之后怎么办
  • 从 SimpleKV 到 Redis:演进了什么
  • 小结

引言

很多人接触 Redis 的方式都很相似:项目里需要缓存,于是装了 Redis,写了几条SETGET,再后来用上 List、Hash、Sorted Set,逐渐熟悉了各种命令。但若是有人追问一句"Redis 内部到底是怎么把这些数据组织起来的",多半会有些卡壳。

要把 Redis 真正用顺手,光记命令是不够的。一个能跑得快、扛得住、又能容错的键值数据库,背后必然是若干模块协同的结果。与其陷在一个个细节里打转,不如先搭一个最小可用的"小白鼠"——一个名叫 SimpleKV 的简化键值数据库,把它完整剖一遍,再去看 Redis 在哪些地方做了加强。这种自顶向下的视角能帮我们快速建立起系统观,后面研究单线程模型、持久化、集群时也不至于迷路。

从数据模型与操作接口入手

要构造一个键值数据库,最先要回答的两个问题是:能存什么样的数据,能对数据做什么操作。看似平淡的两个问题,却决定了它适合什么场景。

最基础的数据模型是 key-value 对。SimpleKV 的 key 限定为 String,value 可以是 String 或整型这种基础类型。但实际生产中,不同的键值数据库在 value 上分歧很大:Memcached 的 value 只能是 String,而 Redis 把 value 拓展成了 String、Hash、List、Set、Sorted Set 等多种类型。这一差异看似微小,却直接决定了它能不能胜任排行榜、消息队列、好友关系这些场景。

操作接口的设计同样关键。SimpleKV 至少要支持 PUT、GET、DELETE 三种基础操作:

  • PUT:写入或更新一个键值对(部分系统称作 SET)
  • GET:根据 key 读取 value
  • DELETE:删除整条记录

实际业务中常常还需要范围查询(SCAN)和存在性判断(EXISTS)。比如查询某个用户一段时间内的访问记录,本质上就是按 key 区间扫描;判断某个用户是不是黑名单里的人,则只需要确认 key 是否存在。

数据模型决定了"能放什么",操作接口决定了"能怎么取"。理解了这两点,就能解释为什么 Redis 适合缓存、秒杀、分布式锁,而不适合做多用户年龄均值这种聚合计算——它的接口集合从来就不是为复杂分析设计的。

数据放内存还是放磁盘

接下来要做一个根本性选择:键值对存哪里。

放内存里读写在百纳秒级别,但断电就丢;放磁盘上数据安全,可读写动辄几毫秒,整体性能会被拉得很低。这是一道经典的取舍题,答案完全取决于业务诉求。Memcached 和 Redis 选择内存优先,因为它们的核心使命是缓存——速度第一,丢失可接受;而像 RocksDB 这类存储引擎则把数据落在磁盘上,更适合做底层存储。SimpleKV 跟 Redis 保持一致,把数据放在内存里。

确定了存储介质后,一个完整的键值数据库还需要四个关键模块:访问框架、索引模块、操作模块、存储模块。下面逐一拆解。

访问框架:客户端怎么和数据库说话

访问框架决定了应用程序如何跟数据库交互,主要有两种风格。

一种是动态链接库形式,比如libsimplekv.so。应用进程把库链进来,调用接口就直接在本进程的内存里读写数据。RocksDB 用的就是这种模式,性能极致,但应用和数据库共生死。

另一种是通过网络框架对外提供服务,客户端通过 Socket 连接,按约定的协议发送请求。Memcached、Redis 都走这条路。好处显而易见:不限部署位置、可以服务多个客户端、便于横向扩展;代价是引入了网络处理、协议解析、并发模型这一连串新问题。

客户端发出PUT hello world,网络框架接收数据包、按协议解析出操作类型和键值,再交给后续模块处理。这里就埋下了一个核心设计题:网络连接、请求解析、数据存取,是用一个线程串起来,还是拆成多线程甚至多进程?这是 I/O 模型的设计取舍。Redis 选了"单线程 + 多路复用"的路子,看起来反直觉,但配合内存访问的速度,反而避开了多线程并发控制带来的复杂性和锁开销。

索引模块:从 key 找到 value

请求解析出来后,接下来要在内存里找到这个 key 对应的位置。索引模块就是干这个的。

常见的索引结构有哈希表、B+ 树、字典树、跳表等等,不同结构在性能、内存占用、并发控制上各有特点。内存键值数据库通常首选哈希表——内存随机访问的速度很快,正好匹配哈希表 O(1) 的复杂度。Memcached、Redis 都用哈希表做主索引,RocksDB 则用跳表。

Redis 这里有一处值得注意的设计:哈希表只是负责定位 key 到 value 的入口。但 Redis 的 value 本身可能是一个集合(Hash、List、Sorted Set),找到 value 入口之后,还要进入这个集合内部继续操作。所以 Redis 的 value 自身又有一套底层数据结构——压缩列表、跳表、整数数组等等——这些结构进一步保证了集合操作的效率。

也就是说,Redis 的"快"是两层索引共同作用的结果:外层全局哈希表做 O(1) 的 key 定位,内层根据 value 类型选用合适的高效结构做 value 内部访问。

操作模块:不同操作的具体逻辑

定位到位置后,不同操作的执行逻辑也有差异:

  • GET / SCAN:根据存储位置直接返回数据
  • PUT:找位置,分配内存空间,写入数据
  • DELETE:删除记录,释放内存空间

PUT 和 DELETE 都涉及内存的申请和释放,这就把责任递给了内存分配器。SimpleKV 直接用 glibc 的malloc/free,简单但有代价:键值大小不一时容易产生内存碎片,浪费空间。Redis 在这块下了不少功夫,提供了 jemalloc、libc、tcmalloc 多种分配器选项,目的就是在大量小对象的随机分配场景下尽量减少碎片、提高利用率。

存储模块:重启之后怎么办

内存数据库最大的痛点是断电丢数据。SimpleKV 通过简单的文件持久化解决重启后的恢复问题,方案有两种:

  • 每写一条数据都落盘:可靠性高但性能差
  • 周期性把内存数据批量写入磁盘:性能好但可能丢一段时间的数据

Redis 在这条路上走得更深,提供了两套互补的机制:AOF 日志记录每条写命令、RDB 快照定期保存内存全量状态。两者各有优劣,4.0 之后还能混合使用,把恢复速度和数据可靠性兼顾起来。这部分是 Redis 持久化的核心命题,后面会单独展开。

从 SimpleKV 到 Redis:演进了什么

把 SimpleKV 完整搭出来之后,再回看 Redis,差异就清晰了:

模块SimpleKVRedis
访问方式动态链接库网络框架(基于 RESP 协议)
value 类型String、整型String、Hash、List、Set、Sorted Set、Stream、HyperLogLog、Bitmap 等
索引结构哈希表全局哈希表 + 多种 value 内部结构
持久化简单文件落盘AOF + RDB 双机制
部署形态单机单机 + 主从 + 哨兵 + 集群
并发控制未设计单线程 + I/O 多路复用
内存管理glibc malloc/free多分配器可选,过期与淘汰策略完善

这张对比表其实就是后续课程的路线图。Redis 比 SimpleKV 多出来的每一项能力,背后都对应着一个设计取舍:网络框架带来扩展性的同时也带来了 I/O 模型的设计;丰富的 value 类型背后是不同的数据结构;持久化机制的多样化是为了在性能和可靠性之间留更多调节余地;集群和主从是高可用的延伸。

小结

构造一个最简单的键值数据库 SimpleKV,看似只是一次纸上谈兵,实际上是把后续学习 Redis 的所有维度都铺好了脚手架。

理解 Redis 的关键不在于死记每个命令的复杂度,而在于建立这个系统观:知道它由哪些模块组成、每个模块要解决什么问题、为什么选了当前的方案而不是别的。带着这个全局视角,再去深入任何一个细节——单线程模型也好、跳表实现也好、AOF 重写机制也好——都能很快定位到它在整体架构中的位置,理解它存在的理由。

接下来要展开的每一个主题,都可以放回这张地图上对照着看。当你能在脑子里随时调出这张地图,你就真正开始掌握 Redis,而不只是会用它。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询