当前位置: 首页 > 科技观察

跟着这12张图,重温Redis

时间:2023-03-21 21:56:39 科技观察

大家好,我是小林。在网上看到一篇很不错的Redis文章,内容很精炼,图片也很好,所以分享给大家阅读阅读,主要讲“Redis入门,Redis架构,Redis持久化”这三个方面。好了,废话不多说,走吧!什么是Redis?Redis(REmoteDIctionaryService)是一个开源的键值对数据库服务器。Redis更准确的描述是数据结构服务器。Redis的这种特殊性质使其在开发人员中很受欢迎。Redis对数据的处理不是通过迭代或排序的方式,而是从一开始就按照数据结构进行组织。早期,它的使用方式与Memcached非常相似,但随着Redis的改进,它在许多其他用例中变得可行,包括发布-订阅机制、流和队列。首先,Redis是一个内存数据库,用作另一个“真实”数据库(如MySQL或PostgreSQL)之前的缓存,以帮助提高应用程序性能。它通过利用内存的高访问速度来卸载核心应用程序数据库,例如:不经常更改且经常被请求的数据任务关键型和频繁更改的数据上述数据的示例可以包括会话或数据缓存以及Leaderboard或仪表板的聚合分析。但是,对于许多用例场景,Redis可以提供足够的保证,使其可以用作成熟的主数据库。结合Redis插件及其各种高可用性(HA)设置,Redis作为数据库对于某些场景和工作负载变得非常有用。另一个重要方面是Redis模糊了缓存和数据存储之间的界限。这里需要理解的重要一点是,读取和操作内存中的数据比使用SSD或HDD作为存储的传统数据库要快得多。最初,人们最常将Redis与Memcached进行比较,后者在当时缺乏任何非易失性持久性。这是两个当前缓存之间的功能细分。MemcachedRedis亚毫秒延迟YesYesDeveloperEaseofUseYesYesDataPartitioningYesYesSupportsWideRangeofProgrammingLanguagesYesYesAdvancedDataStructures-YesMultithreadingArchitectureYes-Snapshots-YesReplication(Replication)-YesTransactions-YesPublish/Subscribe-是LuaScripting-是Geospatialsupport-是尽管有多种配置方法可以将数据持久化到磁盘,但在最初引入持久化时,Redis使用快照将数据异步复制到内存中以进行持久化。不幸的是,这种机制的缺点是快照之间可能会丢失数据。自2009年成立以来,Redis已经变得非常成熟。我们将介绍它的大部分架构和拓扑,以便您可以将Redis添加到您的数据存储系统库中。Redis体系结构在开始讨论Redis内部结构之前,让我们先讨论各种Redis部署及其权衡。我们将主要关注以下设置:单个Redis实例Redis高可用性RedisSentinelRedis集群根据您的用例和规模,决定使用哪种设置。单个Redis实例单个Redis实例是部署Redis最直接的方式。它允许用户设置和运行小型实例,帮助他们快速发展和加速服务。然而,这种部署并非没有缺点。例如,如果这个实例发生故障或变得不可用,则所有客户端对Redis的调用都将失败,从而降低系统的整体性能和速度。如果有足够的内存和服务器资源,这个实例可以非常强大。主要用于缓存的场景可以通过最少的设置获得显着的性能提升。如果有足够的系统资源,您可以在运行应用程序的同一台机器上部署此Redis服务。在系统内管理数据时,了解一些Redis概念至关重要。发送到Redis的命令首先在内存中处理。那么,如果在这些实例上设置了持久化,就会在某个时间间隔有一个fork进程来生成数据持久化的RDB(Redis数据的非常紧凑的时间点表示)快照或者AOF(append-onlyfiles)。这两个过程让Redis可以拥有长期存储,支持各种复制策略,并启用更复杂的拓扑结构。如果Redis未设置为持久化数据,则数据将在重启或故障转移时丢失。如果在重启时启用了持久化,它会将RDB快照或AOF中的所有数据加载回内存,然后该实例可以支持新的客户端请求。话虽如此,让我们看看您可能会使用的一些更分布式的Redis设置。Redis高可用性Redis的另一种流行设置是主从部署,其中从属部署与主部署保持数据同步。当数据写入主实例时,它会将这些命令的副本发送到从部署客户端的输出缓冲区,从而达到数据同步的效果。部署中可以有一个或多个实例。这些实例可以帮助扩展Redis读取操作或在main丢失时提供故障转移。我们现在已经进入了一个分布式系统,所以在这个拓扑中有很多新的东西需要考虑。过去简单的事情现在变得复杂了。Redis复制的每个主实例都有一个复制ID和一个偏移量。这两条数据对于确定副本可以继续其复制过程或是否需要进行完全同步的时间点至关重要。对于主要Redis部署上发生的每个操作,此偏移量都会增加。更具体地说,当Redis副本实例仅落后主实例几个偏移量时,它会接收来自主实例的剩余命令,并在其数据集上重播它们,直到同步完成。如果两个实例无法就复制ID达成一致,或者主实例不知道偏移量,则副本将请求完全同步。此时主创建一个新的RDB快照并将其发送到副本。在此传输之间,主实例会缓冲快照截止点和当前偏移量之间的所有中间更新命令,以便在快照同步之前不会将它们发送到副本实例。完成后,复制可以正常继续。如果一个实例具有相同的复制ID和偏移量,则它们具有完全相同的数据。现在您可能想知道为什么需要复制ID。当Redis实例被提升为主实例或从头开始重新启动为主实例时,它会获得一个新的复制ID。这用于推断这个新提升的副本实例是从哪个先前的主实例复制的。这允许它能够执行部分同步(与其他副本节点),因为新主节点记住了它的旧复制ID。例如,两个实例(master和slave)具有相同的复制ID,但偏移量相差几百个命令,这意味着如果在这些偏移量之后的命令在实例上重放,它们将具有相同的数据集。现在,如果复制ID完全不同,并且我们不知道新降级(或重新加入)的从节点(没有共同祖先)的先前复制ID,该怎么办?我们将需要执行昂贵的完全同步。相反,如果我们知道之前的复制ID,我们可以推断出如何使数据同步,因为我们能够推断出它们共享的共同祖先,并且偏移量对于部分同步同样有意义。RedisSentinelSentinel是一个分布式系统。与所有分布式系统一样,Sentinel有几个优点和缺点。Sentinel的设计方式是一组哨兵进程协同工作以协调状态,为Redis提供高可用性。毕竟,您不希望保护您免受故障影响的系统有自己的单点故障。Sentinel负责一些事情。首先,它确保当前的主从实例健康且响应迅速。这是必要的,因为哨兵(连同其他哨兵进程)可以在主和/或从丢失的情况下发出警报并采取行动。其次,它起到了服务发现的作用,就像其他系统中的Zookeeper和Consul一样。所以当一个新的客户端试图写入Redis时,Sentinel会告诉客户端当前的master实例是什么。因此,Sentinel会持续监控可用性并将该信息发送给客户端,以便他们在进行故障转移时可以做出反应。以下是它的职责:监控-确保主实例和从实例按预期工作。通知-将Redis实例中的事件通知系统管理员。故障转移管理——如果主实例变得不可用并且足够数量(仲裁)的节点同意这是真的,则哨兵节点可以启动故障转移。配置管理-Sentinel节点还充当当前主Redis实例的发现服务。以这种方式使用RedisSentinel可以进行故障检测。此检测涉及多个哨兵进程同意当前主实例不再可用。此协议进程称为Quorum。这提高了健壮性并防止一台机器行为不当和失去对主Redis节点的访问。此设置并非没有缺点,因此我们将介绍使用RedisSentinel时的一些建议和最佳实践。您可以通过多种方式部署RedisSentinel。老实说,要提出任何明智的建议,我需要您系统的更多背景信息。作为一般指南,我建议在每个应用程序服务器旁边运行一个Sentinel节点(如果可能),这样您也不必担心Sentinel节点与使用Redis的实际客户端之间的网络可达性差异。您可以将Sentinel与Redis实例一起运行,甚至可以在独立节点上运行,只是通过不同的处理方式使事情变得更加复杂。我建议至少运行三个具有至少两个法定人数的节点。这是一个简单的图表,它分解了集群中的服务器数量以及相关的法定人数和可容忍的可持续故障。NumberofServersQuorumNumberOfToleratedFailures110220321431532642743这将因系统而异,但总体思路保持不变。让我们花点时间考虑一下这样的设置会出现什么问题。如果你运行这个系统足够长的时间,你会遇到所有这些。如果Sentinel节点超过法定人数怎么办?如果网络分裂使旧的主实例成为少数怎么办?这些写入会发生什么?(剧透:当系统完全恢复时它们会丢失)如果哨兵节点和客户端节点(应用程序节点)的网络拓扑错位会怎样?没有持久性保证,特别是持久保存到磁盘的操作(见下文)是异步的。还有一个烦人的问题,当客户端发现一个新的主节点时,我们会丢失多少写入未知主节点的数据?Redis建议在建立新连接时查询新的master。根据系统配置,这可能意味着大量数据丢失。如果您强制主实例将写入复制到至少一个副本实例,则有多种方法可以减轻损失程度。请记住,所有Redis复制都是异步的,这有其权衡。因此,它需要独立跟踪确认,并且如果至少一个副本实例不确认它们,主实例将停止接受写入。RedisCluster我相信你们中的许多人都想过当您不能将所有数据存储在一台机器上的内存中时会发生什么。目前单台服务器最大可用RAM为24TIB,目前AWS在线列出。当然,这很多,但是对于某些系统来说,这还不够,即使对于缓存层也是如此。RedisCluster允许水平扩展Redis。首先,让我们避开一些术语;一旦我们决定使用RedisCluster,我们就决定将存储的数据分散到多台机器上,这称为分片。所以集群中的每个Redis实例都被认为是整个数据的一个分片。这就带来了一个新问题。如果我们将密钥推送到集群,我们如何知道哪个Redis实例(分片)保存该数据?有几种方法可以做到这一点,但RedisCluster使用算法分片。为了找到给定密钥的分片,我们对密钥取模分片总数进行散列。然后,使用确定性哈希函数,这意味着给定的键将始终映射到同一个分片,我们可以推断出未来将在何处读取特定的键。当我们稍后想向系统添加新分片时会发生什么?这个过程称为重新分片。假设键“foo”之前映射到分片0,在引入新分片后它可能映射到分片5。然而,如果我们需要快速扩展系统,将数据移动到新的分片映射,这将是缓慢且不切实际的。它还会对Redis集群的可用性产生不利影响。RedisCluster为这个问题设计了一个解决方案叫做Hashslot,所有的数据都映射到它上面。有16K个哈希槽。这为我们提供了一种在集群中传播数据的合理方法,因为我们添加新的分片时,我们只需在系统之间移动哈希槽。通过这样做,我们只需要将hashlots从一个分片移动到另一个分片,并简化向集群添加新主实例的过程。这可以在没有任何停机时间且对性能影响最小的情况下实现。让我们通过一个例子来谈谈。M1包含从0到8191的哈希槽。M2包含从8192到16383的哈希槽。因此,要映射“foo”,我们采用键(foo)的确定性哈希并根据哈希槽的数量(16K)对其进行修改,生成M2的映射。现在假设我们添加一个新实例M3。新映射将是:M1包含从0到5460的哈希槽。M2包含从5461到10922的哈希槽。M3包含从10923到16383的哈希槽。现在映射到M1中映射的哈希槽的所有键在M2中需要感动。但是散列槽的各个键的散列不需要移动,因为它们已经被划分到散列槽中了。因此,这种级别的误导解决了算法分片的重新分片问题。八卦协议Redis集群使用八卦来确定整个集群的健康状况。在上图中,我们有3M个节点和3个S节点。所有这些节点都在不断地进行通信,以了解哪些分片可用并准备好为请求提供服务。如果足够多的分片认为M1没有响应,它们可以决定将M1的副本S1提升为主节点以保持集群健康。触发此操作所需的节点数是可配置的,并且必须正确完成。如果操作不当并且在分区的两侧相等时未能打破平局,则可能导致集群被拆分。这种现象称为脑裂。作为一般规则,您必须拥有奇数个主节点和两个副本才能获得最稳健的设置。Redis持久化模型如果我们要使用Redis来存储任何类型的数据,同时需要安全存储,了解Redis如何做到这一点很重要。在许多用例中,即使您丢失了存储在Redis中的数据,也不是世界末日。将其用作缓存,或者如果它支持实时分析,那么在数据丢失的情况下,这并不是世界末日。在其他情况下,我们需要一些关于数据持久性和恢复的保证。NoPersistenceNoPersistence:如果您愿意,可以完全禁用持久性。这是运行Redis的最快方式,并且没有持久性保证。RDB文件RDB(Redis数据库):RDB持久化以指定的时间间隔执行数据集的时间点快照。这种机制的主要缺点是数据在快照之间丢失。此外,这种存储机制还依赖于主进程的分叉,这可能会导致较大数据集中服务请求的瞬间延迟。话虽如此,RDB文件在内存中的加载速度比AOF快得多。AOFAOF(AppendOnlyFile):AOF持久化记录服务器收到的每一次写操作,这些操作会在服务器开始重构原始数据集时再次执行。这种持久性方法保证比RDB快照更持久,因为它是一个仅附加文件。随着操作的发生,我们将它们缓冲到日志中,但它们尚未持久化。如果需要,此日志与我们为重放而运行的实际命令保持一致。然后,如果可能(当此运行可配置时),我们使用fsync将其刷新到磁盘并将其持久化。缺点是格式不紧凑,使用的磁盘比RDB文件多。为什么不两者兼而有之?RDB+AOF:AOF和RDB可以组合在同一个Redis实例中。如果您愿意,以速度换取持久性是一种权衡。我认为这是一种可以接受的设置Redis的方式。如果重启,请记住,如果两者都启用,Redis将使用AOF重建数据,因为它是最完整的。分叉现在我们了解了持久性的类型,让我们讨论如何在像Redis这样的单线程应用程序中实际执行它。在我看来,Redis最酷的部分是它如何利用分叉和写时复制来有效地促进数据持久化。分叉是操作系统通过创建自身副本来创建新进程的一种方式。通过这种方式,您可以获得一个新的进程ID和一些其他信息和句柄,因此新分叉的进程(子进程)可以与原始进程父进程通信。现在事情变得有趣了。Redis是一个分配大量内存的进程,那么它是如何进行复制而不会耗尽内存的呢?当你fork一个进程时,父进程和子进程共享内存,在那个子进程中Redis启动快照(Redis)进程。这是通过一种称为写时复制的内存共享技术实现的——该技术在创建分叉时将引用传递给内存。如果在子进程持久化到磁盘时没有任何变化,则不会进行新的分配。如果发生更改,内核会跟踪对每个页面的引用,如果对一个页面有多个更改,则会将更改写入新页面。子进程完全不知道变化并且有一个一致的内存快照。因此,我们能够在仅使用一小部分内存的情况下非常快速有效地获取潜在GB内存的时间点快照!