每当在项目中提到 redis 时,人们都会认为它是一个缓存来使用。但 redis 不仅仅如此,我们可以将其用作速率限制器、消息代理以及数据库……但什么是 redis,为什么它如此之快,我们又是如何使用它的呢?提出所有这些问题让我对这个主题产生了好奇心,所以我想让你也……

让我们首先从什么是缓存开始,这很简单,缓存就像是将常用物品放在你的桌子上,而不是从储藏室中取出来。缓存将频繁访问的数据存储在临时的高速存储层中,通过最小化冗余计算或数据库查询来减少延迟并提高性能。
现在,Redis 是我们的高速存储层,代表远程字典服务器,它是一个单线程、内存中的数据结构存储模型……这意味着与像PostgreSQL、MySQL 这样的数据库不同,它们将数据存储在较慢的机械或固态驱动器上,redis 将所有数据都存储在 RAM 中。这意味着每个读写操作都以内存速度发生,无需担心磁盘输入/输出。
有三个主要因素支撑着这种速度。
首先是内存数据存储
这是最重要的因素,因为从 RAM 中访问数据比从即使是速度最快的 SSD 或 NVMe 驱动器中访问数据要快几个数量级。主内存访问延迟通常在纳秒范围内,而磁盘访问在微秒到毫秒范围内。通过将整个数据集保持在 RAM 中,Redis 消除了数据库系统中最大的瓶颈,即磁盘 I/O。

第二个原因是单线程命令执行
redis 在单个线程上处理所有命令。这种设计避免了多线程的开销。没有需要获取的锁,没有线程间的上下文切换,也没有需要管理的竞争条件。CPU 可以纯粹地专注于顺序执行命令而不受干扰,这对于 Redis 设计的负载(许多小而快的操作)来说效率非常高。
第三个原因是高度优化的 C 代码和数据结构
Redis 是用 ANSI C 编写的,这是一种以性能著称的语言。除了语言本身,它还使用了定制的、高度优化的数据结构。例如,它的简单动态字符串(SDS)以及用于哈希和集合的各种编码(如 ziplists)都是为了最小化常见操作中的内存使用和 CPU 周期而设计的,确保数据不仅存储在 RAM 中,而且以最有效的方式存储。
你可能会问,Redis 必须处理数千个并发客户端连接,并以微秒级的延迟执行命令,是什么架构模式让它能够如此高效地管理这些操作呢?

Redis 的命令处理核心是单线程的。这意味着它使用单个 CPU 核心来处理所有传入的命令,解析它们并执行它们。这个选择是有意为之的,因为它消除了多线程的复杂性和性能开销,例如锁竞争、竞态条件和上下文切换。
为了处理并发,Redis 采用事件驱动的架构,使用 I/O 多路复用机制。主线程运行一个事件循环,该循环使用系统调用 epoll、kqueue 或 IOCP 来高效地观察多个网络套接字
让我们设想一个场景,你是唯一一个知道如何烹饪和切菜的人,但你一次只能切一种食材(单个 Redis 线程)。但你有很多助手(当然是你朋友们)(操作系统的 I/O 多路复用功能,如 kqueue 和 IOCP)。你告诉你的朋友们要看着炉子上的所有锅。一旦有一锅准备好了,他们就应该通知你。所有这些都是为了不浪费你站在锅边的时间。
相反,你切菜,当你的一个助手喊道,“锅沸腾了!!”然后你立即停止正在做的事情,处理那个锅,然后再回去切菜。所以在这个场景中,你是 Redis 的主事件循环,锅是客户端连接,你的朋友们是操作系统的内核,它们高效地通知 Redis 当客户端发送请求或准备好接收响应时。

所以这就是实际过程的样子:
这种非阻塞 I/O 模型确保单个线程永远不会空闲等待网络或磁盘操作。它总是忙于处理事件,这就是它如何通过单个线程实现高吞吐量和并发性的原因。
作为一名工程师,你应该会问到这个问题,那就是 Redis 的主要存储是易失性 RAM。它提供了哪些机制来确保数据持久性和可靠性,使其能够从服务器重启或崩溃中恢复?
Redis 提供了两种不同且互补的持久化机制,以将内存中的数据集保存到非易失性存储中。
这种持久化方法创建数据集的即时快照。它通过之前描述的方式进行子进程的派生。子进程将整个数据集写入磁盘上的单个、紧凑的二进制.rdb 文件。在 CPU 和 I/O 方面效率高。主要优势是生成的文件非常适合备份,并允许在重启时快速恢复数据。主要缺点是可能的数据丢失:如果服务器在两个配置的快照之间崩溃,自上次快照以来的所有写入都将丢失。
此方法记录所有修改数据集的写操作命令。这些命令被追加到 appendonly.aof 文件中。重启时,Redis 按顺序重新执行这些命令以重建原始数据集。持久性由 appendfsync 配置控制:
为了防止 AOF 文件无限增长,Redis 可以在后台自动重写它。它会创建一个子进程,将重写当前数据集所需的最小命令集写入一个新临时 AOF 文件,然后将其原子性地与旧文件交换。
为了最大程度地提高耐用性,通常的做法是同时使用 AOF 进行近似实时持久化和 RDB 进行定期备份。
让我们看看 Redis 在生产级应用中的几个良好用例
“首先,这是众所周知且被每位开发者和工程师广泛使用的,Redis 作为缓存层。

假设你有一个网络应用程序,用户经常查看他们的个人资料,每次从基于磁盘的数据库如 MySQL 中获取数据可能会很慢,因此我们可以使用 Redis 来缓存用户个人资料数据,当用户请求他们的个人资料时,应用程序首先检查 Redis,如果所需数据在 Redis 中,则是一个缓存命中,立即返回,如果数据不在 Redis 中,则是一个缓存未命中,应用程序从主数据库中获取它,存储在 Redis 中,然后将其返回给用户。Redis 中的数据可以有 TTL 或生存时间,因此它可以在一定时间后自动过期,例如 15 到 20 分钟,以确保始终有新鲜的数据。
“第二个场景是使用 Redis 作为数据库,特别适用于对速度和低延迟要求很高的用例,就像构建一个游戏应用。

这里我们需要维护一个实时排行榜,玩家分数不断更新,并且需要立即显示前 10 名玩家。因此,我们可以使用 Redis 的有序集合数据结构来存储玩家分数,每个玩家分数都通过其 ID 作为键添加到有序集合中,分数作为值。这会自动排序分数,我们可以使用单个命令如 ZREVRANGE leaderboard 0 9 快速检索前 10 名玩家。Redis 可以使用 RDB 或 AOF 将此数据写入磁盘,以确保持久性。
Redis 内部使用哪些数据结构和优化来以最小的开销存储复杂的数据类型?
Redis 的内存效率源于其使用定制的、高度优化的数据结构和动态编码策略。
Redis 不使用标准的 C 风格以空字符结尾的字符串。相反,它使用自己的 SDS 结构。SDS(简单动态字符串)是一个包含元数据(如字符串长度和总分配内存)的结构,后面跟着一个包含实际数据的字节数组。这种设计提供了几个优势,它们是:
O(1) 长度查找:长度直接存储在结构体中,避免了扫描整个字符串的需要。Redis 根据数据的大小和内容动态切换数据类型的内部编码以节省内存。例如