Redis(Remote Dictionary Server)是一个开源(BSD 许可)的内存数据库,常用作缓存、消息代理或流式引擎。它拥有丰富的数据结构支持,包括基本数据结构如 String、List、Set、Hash、SortedSet,以及概率数据结构如 Bloom Filter 和 HyperLogLog。
Redis 非常快。我们可以用 Redis 自带的工具运行 Redis 基准测试。吞吐量可以达到每秒近 10 万次请求。
在这篇文章中,我们将讨论 Redis 在架构设计上为什么这么快。
Redis 是一个内存键值存储。有几个重要功能:
- 值使用的数据结构
- 数据结构允许的操作
- 数据持久化
- 高可用性
下面是 Redis 架构的高层示意图。让我们逐一了解。

有两种类型的客户端可以访问 Redis:一种支持与 Redis 数据库的连接,另一种在前者之上构建并支持对象映射。
Redis 支持广泛的语言,使其能够用于各种应用程序。此外,OM 客户端库允许我们建模、索引和查询文档。
Redis 对值数据类型有丰富的支持,包括 Strings、Lists、Sets、Hashes 等。因此,Redis 适用于广泛的业务场景。根据数据类型,Redis 支持不同的操作。
基本操作类似于关系型数据库,支持 CRUD(创建 - 读取 - 更新 - 删除):
- GET:检索键的值
- PUT:创建新的键值对或更新现有键
- DELETE:删除键值对
数据结构和操作是 Redis 如此高效的重要原因。我们将在后面的章节中详细介绍。
Redis 将数据保存在内存中。内存中的数据读写通常比磁盘读写快 1,000 到 10,000 倍。详见下图。

然而,如果服务器宕机,所有数据都会丢失。因此 Redis 也设计了磁盘持久化以实现快速恢复。
Redis 有 4 种持久化选项:
AOF(Append Only File) AOF 类似于提交日志,记录每个写入 Redis 的操作。因此当服务器重启时,可以重放写入操作并重建数据集。
RDB(Redis Database) RDB 按预定义间隔执行时间点快照。
AOF 和 RDB 结合 这种持久化方法结合了 AOF 和 RDB 的优点,我们稍后会详细介绍。
无持久化 Redis 中可以完全禁用持久化。这有时用于 Redis 作为较小数据集缓存的场景。
Redis 使用主从复制来实现高可用性。我们可以配置多个副本用于读取,以处理并发读取请求。这些副本在重启后会自动连接到主节点,并持有主节点实例的精确副本。
当不使用 Redis 集群时,Redis Sentinel 提供高可用性,包括故障转移、监控和配置管理。
Redis 常用作缓存,可能包含敏感数据,因此设计为在受信任环境中通过受信任客户端访问。Redis 安全模块负责管理访问控制层并授权对数据执行的有效操作。
Redis 还提供用于配置和管理集群的管理界面。持久化、复制和安全配置都可以通过管理界面完成。
现在我们已经介绍了 Redis 架构的基本组件,我们将深入探讨使 Redis 快速的设计细节。
Redis 不是市场上唯一的内存数据库产品。但它如何实现微秒级数据访问延迟并成为许多公司的热门选择?
一个重要的原因是,将数据存储在内存中允许更灵活的数据结构。这些数据结构不需要像普通磁盘数据结构那样经历序列化和反序列化过程,因此可以针对快速读写进行优化。
Redis 使用哈希表来保存所有键值对。哈希表中的元素保存指向键值对条目的指针。下图说明了全局哈希表的结构。
使用哈希表,我们可以用 O(1) 时间复杂度查找键值对。

与所有哈希表一样,当键的数量不断增长时,可能会出现哈希冲突,这意味着不同的键落入同一个哈希桶中。Redis 通过链接同一哈希桶中的元素来解决这个问题。当链变得太长时,Redis 会利用两个全局哈希表执行重新哈希。
下图展示了 Redis 如何实现常见数据结构。String 类型只有一种实现:SDS(Simple Dynamic Strings)。List、Hash、Set 和 SortedSet 都有两种类型的实现。
注意 Redis 7.0 将 List 实现改为 quicklist,ZipList 被 listpack 取代。

除了这 5 种基本数据结构,Redis 后来添加了更多数据结构以支持更多场景。下图列出了基本数据结构允许的操作和使用场景。
这些数据类型涵盖了网站的大多数使用场景。例如,地理空间数据存储坐标,可用于 Uber 等叫车应用;HyperLogLog 计算海量数据的基数,适合统计大型网站的独立访客数;Stream 用于消息队列,可以弥补 List 的问题。

现在让我们看看为什么这些底层实现是高效的。
Redis SDS 存储字节序列。它以二进制方式操作存储在 buf 数组中的数据,因此 SDS 不仅可以存储文本,还可以存储音频、视频和图像等二进制数据。
SDS 上的字符串长度操作具有 O(1) 时间复杂度,因为长度记录在 len 属性中。SDS 预分配空间,free 属性记录未来使用的可用空间。因此 SDS API 是安全的,没有溢出风险。
下图显示了 SDS 的属性。

ZipList 类似于数组。数组的每个元素保存一条数据。然而,与数组不同,ZipList 在头部有 3 个字段:
- zlbytes - 列表的长度
- zltail - 列表末尾的偏移量
- zllen - 列表中的条目数
ZipList 在末尾还有一个 zlend,表示列表的结束。
在 ZipList 中,定位第一个或最后一个元素是 O(1) 时间复杂度,因为我们可以通过头部字段直接找到它们。定位其他元素需要逐个遍历元素,时间复杂度是 O(N)。

本文为学习目的的个人翻译,译文仅供参考。
原文链接:A Crash Course in Redis。
版权归原作者或原刊登方所有。本文为非官方译本;如有不妥,请联系删除。