什么是 Redis?

Redis(REmote DIctionary Service)是一个开源的键值对数据库服务器。

Redis 更精确的形容是一个数据结构服务器。Redis 的这种非凡性质让它在开发人员中很受欢迎。

Redis不是通过迭代或者排序形式解决数据,而是一开始就依照数据结构形式组织。晚期,它的应用很像 Memcached,但随着 Redis 的改良,它在许多其余用例中变得可行,包含公布-订阅机制、流(streaming)和队列。

次要来说,Redis 是一个内存数据库,用作另一个“实在”数据库(如 MySQL 或 PostgreSQL)后面的缓存,以帮忙进步应用程序性能。它通过利用内存的高速访问速度,从而加重外围应用程序数据库的负载,例如:

  • 不常常更改且常常被申请的数据
  • 工作关键性较低且常常变动的数据

上述数据的示例能够包含会话或数据缓存以及仪表板的排行榜或汇总剖析。

然而,对于许多用例场景,Redis 都能够提供足够的保障,能够将其用作成熟的主数据库。再加上 Redis 插件及其各种高可用性(HA)设置,Redis 作为数据库对于某些场景和工作负载变得十分有用。

另一个重要方面是 Redis 含糊了缓存和数据存储之间的界线。这里要了解的重要一点是,相比于应用 SSD 或 HDD 作为存储的传统数据库,读取和操作内存中数据的速度要快得多。

最后,Redis 最常被比作 Memcached,后者过后不足任何非易失性长久化。

这是以后两个缓存之间的性能细分。

尽管当初领有多种配置形式将数据长久化到磁盘,但过后首次引入长久化时,Redis 是应用快照形式,通过异步拷贝内存中的数据形式来做长久化。可怜的是,这种机制的毛病是可能会在快照之间失落数据。

Redis 自 2009 年成立到当初曾经变的很成熟。咱们将介绍它的大部分架构和拓扑,以便你能够将 Redis 增加到你的数据存储系统库中。

Redis 架构

在开始探讨 Redis 内部结构之前,让咱们先讨论一下各种 Redis 部署及其衡量取舍。

咱们将次要关注以下这些设置:

  • 单个 Redis 实例
  • Redis 高可用性
  • Redis 哨兵
  • Redis 集群

依据你的用例和规模,决定应用哪一种设置。

单个 Redis 实例

单个 Redis 实例是最间接的 Redis 部署形式。它容许用户设置和运行小型实例,从而帮忙他们疾速倒退和减速服务。然而,这种部署并非没有毛病。例如,如果此实例失败或不可用,则所有客户端对 Redis 的调用都将失败,从而升高零碎的整体性能和速度。

如果有足够的内存和服务器资源,这个实例能够很弱小。次要用于缓存的场景可能会以起码的设置取得显著的性能晋升。给定足够的系统资源,你能够在利用程序运行的同一机器上部署此 Redis 服务。

在管理系统内的数据方面,理解一些 Redis 概念是必不可少的。发送到 Redis 的命令首先在内存中解决。而后,如果在这些实例上设置了持久性,则在某个工夫距离上会有一个fork过程,来生成数据长久化 RDB(Redis 数据的十分紧凑的工夫点示意)快照或 AOF(仅附加文件)。

这两个流程能够让 Redis 领有长期存储,反对各种复制策略,并启用更简单的拓扑。如果 Redis 未设置为长久化数据,则在重新启动或故障转移时数据会失落。如果在重启时启用了长久化,它会将 RDB 快照或 AOF 中的所有数据加载回内存,而后实例能够反对新的客户端申请。

话虽如此,让咱们看看你可能会用到的更多分布式 Redis 设置。

Redis 高可用性

Redis 的另一个风行设置是主从部署形式,从部署放弃与主部署之间数据同步。当数据写入主实例时,它会将这些命令的正本发送到从部署客户端输入缓冲区,从而达到数据同步的成果。从部署能够有一个或多个实例。这些实例能够帮忙扩大 Redis 的读取操作或提供故障转移,以防 main 失落。

咱们当初曾经进入了一个分布式系统,因而须要在此拓扑中思考许多新事物。以前简略的事件当初变得复杂了。

Redis 复制

Redis 的每个主实例都有一个复制 ID 和一个偏移量。这两条数据对于确定正本能够持续其复制过程的工夫点或确定它是否须要进行残缺同步至关重要。对于主 Redis 部署上产生的每个操作,此偏移量都会减少。

更明确地说,当 Redis 正本实例仅落后于主实例几个偏移量时,它会从主实例接管残余的命令,而后在其数据集上重放,直到同步实现。如果两个实例无奈就复制 ID 达成统一,或者主实例不晓得偏移量,则正本将申请全量同步。这时主实例会创立一个新的 RDB 快照并将其发送到正本。

在此传输之间,主实例会缓冲快照截止和以后偏移之间的所有两头更新指令,这样在快照同步完后,再将这些指令发送到正本实例。这样实现后,复制就能够失常持续。

如果一个实例具备雷同的复制 ID 和偏移量,则它们具备完全相同的数据。当初你可能想晓得为什么须要复制 ID。当 Redis 实例被晋升为主实例或作为主实例从头开始重新启动时,它会被赋予一个新的复制 ID。

这用于推断此新晋升的正本实例是从先前哪个主实例复制进去的。这容许它可能执行局部同步(与其余正本节点),因为新的主实例会记住其旧的复制 ID。

例如,两个实例(主实例和从实例)具备雷同的复制 ID,但偏移量相差几百个命令,这意味着如果在实例上重放这些偏移量前面的命令,它们将具备雷同的数据集。当初,如果复制 ID 齐全不同,并且咱们不晓得新降级(或重新加入)从节点的先前复制 ID(没有独特先人)。咱们将须要执行低廉的全量同步。

相同,如果咱们晓得以前的复制 ID,咱们就能够推断如何使数据同步,因为咱们可能推断出它们共享的独特先人,并且偏移量对于局部同步再次有意义。

Redis 哨兵(Sentinel)

Sentinel 是一个分布式系统。与所有分布式系统一样,Sentinel 有几个长处和毛病。Sentinel 的设计形式是,一组哨兵过程协同工作以协调状态,从而为 Redis 提供高可用性。毕竟,你不心愿爱护你免受故障影响的零碎有本人的单点故障。

Sentinel 负责一些事件。首先,它确保以后的主实例和从实例失常运行并做出响应。这是必要的,因为哨兵(与其余哨兵过程)能够在主节点和/或从节点失落的状况下收回警报并采取行动。其次,它在服务发现中发挥作用,就像其余零碎中的 Zookeeper 和 Consul 一样。所以当一个新的客户端尝试向 Redis 写货色时,Sentinel 会通知客户端以后的主实例是什么。

因而,哨兵一直监控可用性并将该信息发送给客户端,以便他们可能在他们的确进行故障转移时对其做出反馈。

以下是它的职责:

  • 监控——确保主从实例按预期工作。
  • 告诉——告诉系统管理员 Redis 实例中的事件。
  • 故障转移治理——如果主实例不可用并且足够多的(法定数量)节点批准这是真的,Sentinel 节点能够启动故障转移。
  • 配置管理——Sentinel 节点还充当以后主 Redis 实例的发现服务。

以这种形式应用 Redis Sentinel 能够进行故障检测。此检测波及多个哨兵过程批准以后主实例不再可用。这个协定过程称为 Quorum。这能够进步鲁棒性并避免一台机器行为异样导致无法访问主 Redis 节点。

此设置并非没有毛病,因而咱们将在应用 Redis Sentinel 时介绍一些倡议和最佳实际。

你能够通过多种形式部署 Redis Sentinel。诚实说,要提出任何理智的倡议,我须要无关你的零碎的更多背景信息。作为个别领导,我倡议在每个应用程序服务器旁边运行一个哨兵节点(如果可能的话),这样你也不须要思考哨兵节点和理论应用 Redis 的客户端之间的网络可达性差别。

你能够将 Sentinel 与 Redis 实例一起运行,甚至能够在独立节点上运行,只不过它会依照别的形式解决,从而会让事件变得更简单。我倡议至多运行三个节点,并且至多具备两个法定人数(quorum)。这是一个简略的图表,合成了集群中的服务器数量以及相干的法定人数和可容忍的可继续故障。

这会因零碎而异,但总体思路是不变的。

让咱们花点工夫思考一下这样的设置会呈现什么问题。如果你运行这个零碎足够长的工夫,你会遇到所有这些。

  1. 如果哨兵节点超出法定人数怎么办?
  2. 如果网络决裂将旧的主实例置于多数群体中怎么办?这些写入会产生什么?(剧透:当零碎完全恢复时它们会失落)
  3. 如果哨兵节点和客户端节点(应用程序节点)的网络拓扑错位会产生什么?

没有持久性保障,特地是长久化到磁盘的操作(见下文)是异步的。还有一个麻烦的问题,当客户发现新的 primary 时,咱们失去了多少写给一个不晓得的 primary?Redis 倡议在建设新连贯时查问新的主节点。依据系统配置,这可能意味着大量数据失落。

如果你强制主实例将写入复制到至多一个正本实例,有几种办法能够加重损失水平。请记住,所有 Redis 复制都是异步的,这是有其衡量的思考。因而,它须要独立跟踪确认,如果至多有一个正本实例没有确认它们,主实例将进行承受写入。

Redis 集群

我置信很多人都想过当你无奈将所有数据存储在一台机器上的内存中时会产生什么。目前,单个服务器中可用的最大 RAM 为 24TIB,这是目前 AWS 线上列出来的。当然,这很多,但对于某些零碎来说,这还不够,即便对于缓存层也是如此。

Redis Cluster 容许 Redis 的程度扩大。

首先,让咱们解脱一些术语束缚;一旦咱们决定应用 Redis 集群,咱们就决定将咱们存储的数据扩散到多台机器上,这称为分片。所以集群中的每个 Redis 实例都被认为是整个数据的一个分片。

这带来了一个新的问题。如果咱们向集群推送一个key,咱们如何晓得哪个 Redis 实例(分片)保留了该数据?有几种办法能够做到这一点,但 Redis Cluster 应用算法分片。

为了找到给定 key 的分片,咱们对 key 进行哈希解决,并通过对总分片数量取模。而后,应用确定性哈希函数,这意味着给定的 key 将始终映射到同一个分片,咱们能够推断未来读取特定 key 的地位。

当咱们之后想在零碎中增加一个新的分片时会产生什么?这个过程称为从新分片。

假如键 'foo' 之前映射到分片 0, 在引入新分片后它可能会映射到分片 5。然而,如果咱们须要疾速扩大零碎,挪动数据来达到新的分片映射,这将是迟缓且不切实际的。它还对 Redis 集群的可用性产生不利影响。

Redis Cluster 为这个问题设计了一种解决方案,称为 Hashslot,所有数据都映射到它。有 16K 哈希槽。这为咱们提供了一种在集群中流传数据的正当形式,当咱们增加新的分片时,咱们只需在零碎之间挪动哈希槽。通过这样做,咱们只须要将 hashlot 从一个分片挪动到另一个分片,并简化将新的主实例增加到集群中的过程。

这能够在没有任何停机工夫和最小的性能影响的状况下实现。让咱们通过一个例子来谈谈。

  • M1 蕴含从 0 到 8191 的哈希槽。
  • M2 蕴含从 8192 到 16383 的哈希槽。

因而,为了映射 “foo”,咱们采纳一个确定性的键(foo)散列,并通过散列槽的数量(16K)对其进行批改,从而失去 M2 的映射。当初假如咱们增加了一个新实例 M3。新的映射将是:

  • M1 蕴含从 0 到 5460 的哈希槽。
  • M2 蕴含从 5461 到 10922 的哈希槽。
  • M3 蕴含从 10923 到 16383 的哈希槽。

当初映射到 M2 的 M1 中映射哈希槽的所有键都须要挪动。然而散列槽的各个键的散列不须要挪动,因为它们曾经被划分到散列槽中。因而,这一级别的误导(misdirection)解决了算法分片的从新分片问题。

Gossiping 协定

Redis Cluster 应用 gossiping 来确定整个集群的健康状况。在上图中,咱们有 3 个 M 个节点和 3 个 S 节点。所有这些节点一直地进行通信以理解哪些分片可用并筹备好为申请提供服务。

如果足够多的分片批准 M1 没有响应,他们能够决定将 M1 的正本 S1 晋升为主节点以放弃集群衰弱。触发此操作所需的节点数量是可配置的,并且必须正确执行此操作。如果操作不当并且在分区的两边相等时无奈突破平局,则可能会导致集群被拆分。这种景象称为裂脑。作为个别规定,必须领有奇数个主节点和两个正本,以实现最持重的设置。

Redis 长久化模型

如果咱们要应用 Redis 存储任何类型的数据同时要求平安保留,理解 Redis 是如何做到这一点很重要。在许多用例中,如果你失落了 Redis 存储的数据,这并不是世界末日。将其用作缓存或在其反对实时剖析的状况下,如果产生数据失落,则并非世界末日。

在其余场景中,咱们心愿围绕数据持久性和复原有一些保障。

无长久化

无长久化:如果你违心,能够齐全禁用长久化。这是运行 Redis 的最快形式,并且没有持久性保障。

RDB文件

RDB(Redis 数据库):RDB 长久化以指定的工夫距离执行数据集的工夫点快照。

这种机制的次要毛病是快照之间的数据会失落。此外,这种存储机制还依赖于主过程的 fork,在更大的数据集中,这可能会导致服务申请的霎时提早。话虽如此,RDB 文件在内存中的加载速度要比 AOF 快得多。

AOF

AOF(Append Only File):AOF 长久化记录服务器接管到的每个写入操作,这些操作将在服务器启动时再次被执行,重建原始数据集。

这种持久性的办法可能确保比 RDB 快照更长久,因为它是一个仅附加文件。随着操作的产生,咱们将它们缓冲到日志中,但它们还没有被长久化。该日志与咱们运行的理论命令统一,以便在须要时进行重放。

而后,如果可能,咱们应用 fsync 将其刷新到磁盘(当此运行可配置时),它将被长久化。毛病是格局不紧凑,并且比 RDB 文件应用更多的磁盘。

为什么不兼得?

RDB + AOF:能够将 AOF 和 RDB 组合在同一个 Redis 实例中。如果你违心的话,能够以速度换取长久化是一种折衷办法。我认为这是设置 Redis 的一种可承受的形式。在重启的状况下,请记住如果两者都启用,Redis 将应用 AOF 来重建数据,因为它是最残缺的。

Forking

当初咱们理解了长久化的类型,让咱们讨论一下咱们如何在像 Redis 这样的单线程应用程序中理论执行它。

在我看来,Redis 最酷的局部是它如何利用 forking 和写时复制来高效地促成数据长久化。

Forking 是操作系统通过创立本身副原本创立新过程的一种形式。这样,你将取得一个新的过程 ID 和一些其余信息和句柄,因而新 forking 的过程(子过程)能够与原始过程父过程通信。

当初事件变得乏味了。Redis 是一个调配了大量内存的过程,那么它如何在不耗尽内存的状况下进行复制呢?

当你 fork 一个过程时,父过程和子过程共享内存,并且在该子过程中 Redis 开始快照(Redis)过程。这是通过一种称为写时复制的内存共享技术实现的——该技术在创立分叉时传递对内存的援用。如果在子过程长久化到磁盘时没有产生任何更改,则不会进行新的调配。

在产生更改的状况下,内核会跟踪对每个页面的援用,如果某个页面有多个更改,则将更改写入新页面。子过程齐全不晓得更改以及具备统一的内存快照的事件。因而,在只应用了一小部分内存的状况下,咱们可能十分疾速无效地取得潜在千兆字节内存的工夫点快照!