Redis集群·Redis集群提供了一种运行Redis安装的方法,在该安装中,数据会在多个Redis节点之间自动分片。 Redis集群在分区期间还提供了一定程度的可用性,这实际上是在某些节点出现故障或无法通信时有继续工作的能力。但是,如果发生较严重故障(例如,大多数主节点不可用时),集群将停止运行。 实际上,Redis集群能给你带来什么?
Redis集群TCP端口每个Redis集群节点都需要打开两个TCP连接。用于服务客户端的常规RedisTCP端口,例如 第二个值更大一点的端口用于集群总线,也就是使用二进制协议的节点到节点之间的通信通道。节点将集群总线用于故障检测,配置更新,故障转移授权等。客户端永远不要尝试与集群总线端口进行通信,而应始终与普通的Redis命令端口进行通信,但是请确保您在防火墙中同时打开了这两个端口,否则Redis集群节点将无法进行通信。 命令端口和集群总线端口的偏移量是固定的,并且始终为 请注意,对于每个节点,要使Redis集群正常工作,您需要:
如果您没有同时打开两个TCP端口,则集群将无法正常工作。 集群总线使用不同的二进制协议进行节点到节点的数据交换,它更适合于在节点之间使用较少的带宽和较少的处理时间来交换信息。 Redis集群数据分片Redis集群不使用一致性哈希,而是使用一种不同形式的分片,从概念上讲每个key都是我们称为hash槽的一部分。 Redis集群中有16384个hash槽,要计算给定key的hash槽,需将key的CRC16值用16384取模。 Redis集群中的每个节点都负责hash槽的子集,例如,您可能有一个包含3个节点的集群,其中:
这样可以轻松添加和删除集群中的节点。例如,如果我想添加一个新节点D,则需要将一些hash槽从节点A,B,C移到D。类似地,如果我想从集群中删除节点A,则只需移动A所服务的hash槽到B和C。当节点A为空时,我可以将其从集群中完全删除。 因为将hash槽从一个节点移动到另一个节点不需要停止操作,所以添加删除节点或更改节点服务的hash槽的百分比不需要停机。 只要单个命令执行(或整个事务或Lua脚本执行)中涉及的所有key都属于同一个hash槽,Redis集群就支持多key操作。用户可以通过使用称为hash标签的概念来强制多个key成为同一hash槽的一部分。 hash标签记录在Redis集群规范中,注意,如果key的{}中的括号之间有一个子字符串,则仅对字符串中的内容进行hash处理,例如,一个叫{foo}的key和另一个叫{foo} 的 key保证在同一hash槽中,并且可以在以多个key作为参数的命令中一起使用。 Redis集群主备模式为了在主节点子集发生故障或无法与大多数节点通信时保持可用,Redis集群使用主备模型,其中每个hash槽具有从1(主节点本身)到N个副本(N -1个其他备份节点)。 在一个包含节点A,B,C的集群中,如果节点B失败,则集群将无法继续,因为我们不能为 5501-11000 范围内的hash槽提供服务。但是,在创建集群(或稍后)时,我们向每个主节点添加一个备份节点,以便最终集群由作为主节点的A,B,C和作为备份节点的A1,B1,C1组成 ,如果节点B发生故障,系统将能够持续运行。节点B1复制B,并且B发生故障,集群会将节点B1提升为新的主节点,并将继续正常运行。 但是请注意,如果节点B和B1同时失败,则Redis集群无法继续运行。 Redis集群的一致性保证Redis集群无法保证强一致性。 实际上,这意味着在某些情况下,Redis集群可能会丢失系统给客户端的已经确认的写操作。 Redis集群可能丢失写入的第一个原因是因为它使用异步复制。这意味着在写入期
如您所见,B在回复客户端之前不会等待B1,B2,B3的确认,因为这会对Redis造成延迟,因此,如果您的客户端进行了写操作,然后B会确认,但是在它把写操作发送给备份节点之前崩溃了,此时其中一个备份节点(未接收到写操作)可以升级为主节点,这样就永远丢失该写操作。这与配置为每秒将数据刷新到磁盘的大多数数据库所发生的情况非常相似,因此由于过去使用不涉及分布式系统的传统数据库系统的经验,您已经可以对此进行合理推断。同样,您可以通过强制数据库在答复客户端之前刷新磁盘上的数据来提高一致性,但这通常会导致性能过低。在Redis集群下,这相当于同步复制。 基本上,在性能和一致性之间进行权衡是必须的。 Redis集群在需要时可以通过WAIT命令实现同步写,这使得丢失写的可能性大大降低,但是请注意,即使使用同步复制,Redis集群也不实现强一致性:在更复杂的情况下,总是有可能存在一种场景,就是一个无法接收数据的备份节点被选为主节点。 还有一种值得注意的情况,Redis集群也会丢失写操作,这种情况发生在网络分区期间,在该分区中,客户端与少数实例(至少包括主节点)隔离。 以我们的6个节点集群为例,该集群由A,B,C,A1,B1,C1组成,具有3个主节点 发生分区后,可能在分区的一侧有A,C,A1,B1,C1,而在另一侧有B和Z1。 Z1仍然能够对B进行写操作,B将接受其写入。如果分区在很短的时间内恢复正常,则集群将继续正常运行。但是,如果分区持续的时间足以使B1升级为该分区的多数端的主节点,则Z1向B发送的写操作将丢失。 请注意,Z1将能够发送到B的写入量有一个最大的窗口:如果已经有足够的时间使大分区选举出一个主节点,则小分区中的每个主节点都将停止接受写入。该时间是Redis集群的一个非常重要的配置指令,称为节点超时。 在节点超时之后,主节点被视为发生故障,并且可以用其副本之一替换。类似地,在超过指定的时间后,主节点还是无法感知大多数其他主节点,此主节点进入错误状态并停止接受写入。 Redis集群配置参数我们将创建一个集群部署作为例子。在继续之前,让我们介绍一下Redis集群里的redis.conf文件中引入的配置参数。
创建和使用一个Redis集群注意:手动部署Redis集群,了解其某些操作非常重要。 但是,如果要尽快建立集群并运行,请跳过本节和下一节,直接转到使用 create-cluster 脚本创建Redis集群。 要创建集群,我们需要做的第一件事就是让一些空Redis实例运行在集群模式下。基本上,这意味着不能使用常规Redis实例来创建集群,因为需要配置特殊模式,以便Redis实例启用集群特定的功能和命令。 以下是最小的Redis集群配置文件:
启用集群模式的只需要直接打开cluster-enabled命令。每个实例还包含该节点配置存储位置的文件路径,默认情况下为nodes.conf。 该文件不会被人接触。 它只是由Redis集群实例在启动时生成,并在需要时进行更新。 请注意,按预期工作的最小集群要求至少包含三个主节点。 对于您的第一个测试,强烈建议启动一个包含三个主节点和三个备份节点的六个节点集群。为此,输入一个新目录并创建以下目录,该目录以我们将在给定目录中运行的实例的端口号命名。就像是:
在从7000到7005的每个目录中创建一个redis.conf文件。作为配置文件的模板,只需使用上面的小示例,但请确保根据目录名称用正确的端口号替换端口号7000。现在,将您的redis-server可执行文件(从GitHub不稳定分支中的最新资源编译而来)复制到cluster-test目录中,最后在您喜欢的终端应用程序中打开6个终端选项卡。 像这样启动每个实例,每个选项卡一个:
从每个实例的日志中可以看到,由于不存在nodes.conf文件,因此每个节点都会为其分配一个新的ID。
该ID将由该实例永久使用,以使该实例在集群的上下文中具有唯一的名称。每个节点都使用该ID而不是IP或端口记住其他每个节点。 IP地址和端口可能会更改,但是唯一的节点标识符在节点的整个生命周期中都不会改变。我们将此标识符简称为节点ID。 创建集群现在,我们有许多实例正在运行,然后需要通过向节点写入一些有意义的配置来创建集群。 如果您使用的是Redis 5,这很容易完成,这是因为redis-cli中嵌入了Redis集群命令行实用程序,我们可以使用它来创建新集群,检查或重新分片现有集群等。 对于Redis版本3或4,有一个称为redis-trib.rb的较老的工具,它非常相似。您可以在Redis源代码分发的src目录中找到它。 您需要安装redis gem才能运行redis-trib。
第一个示例,即集群创建,将在Redis 5中使用redis-cli以及在Redis 3和4中使用redis-trib来显示。但是,接下来的所有示例都将仅使用redis-cli,因为您可以看到他们语法非常相似,您也可以使用redis-trib.rb help来获取有关语法的信息,从而将一个命令行简单地更改为另一命令行。 重要:请注意,如果需要,可以对Redis 4集群使用Redis 5 redis-cli。 要使用redis-cli为Redis 5创建集群,只需键入:
对于redis 4或者3 请使用redis-trib.rb工具:
此处使用的命令是create,因为我们要创建一个新集群。 选项 显然,满足我们要求的唯一设置是创建具有3个主节点和3个从节点的集群。 Redis-cli将为您提供配置。 键入yes,将接受建议的配置。集群将被配置并加入,这意味着实例将被启动然后彼此
意思就是说至少有一个主节点实例服务于16384个槽位中某一个. 使用create-cluster脚本创建一个Redis集群如果您不想如上所述通过手动配置和执行单个实例来创建Redis集群,则可以使用更简单的系统(但是您将不会学到同样多的操作细节)。 只需检查Redis发行版中的utils / create-cluster目录。内部有一个名为create-cluster的脚本(名称与包含在其中的目录相同),它是一个简单的bash脚本。 为了启动具有3个主节点和3个备份节点的6节点集群,只需键入以下命令:
在步骤2中,当redis-cli希望您接受集群布局时,回复yes。 现在,您可以与集群进行交互,默认情况下,第一个节点将从端口30001开始。 完成后,使用以下命令停止集群:
关于如何运行这个脚本的更多信息,请阅读目录里的README。 集群操作到目前为止,Redis集群的问题之一是缺少客户端库的实现。 据我所知有以下实现:
测试Redis集群的一种简单方法是尝试上述任何客户端,或者仅尝试redis-cli命令。以下是使用后者进行交互的示例:
注意:如果使用脚本创建集群,则节点可能会侦听不同的端口,默认情况下从30001开始。 redis-cli的支持非常基础,因此它始终基于以下事实:Redis集群节点能够将客户端重定向到正确的节点。一个严格的客户端可以做得更好,并且可以在hash槽和节点地址之间缓存映射,以便直接使用与节点的正确连接。仅在集群配置中发生某些更改时(例如,在故障转移之后或系统管理员通过添加或删除节点来更改集群布局之后),才会刷新映射。 使用redis-rb-cluster写一个简单的应用程序在继续展示如何操作Redis集群之前,比如执行故障转移或重新分片之类的操作,我们需要创建一些示例应用程序,或者至少要能够理解简单的Redis集群客户端交互的语义。 通过这种方式,我们可以运行一个示例,同时尝试使节点发生故障或开始重新分片,以了解Redis集群在现实环境下的行为。只是观察一个没有写入任何数据的集群是没有帮助的。 本节说明了redis-rb-cluster的一些基本用法,其中显示了两个示例。 首先是以下内容,它是redis-rb-cluster发行版中的example.rb文件:
该程序做了一件非常简单的事情,它将foo
该程序看起来比较复杂,因为它需要在屏幕上显示错误而不是异常退出,因此,对集群执行的每个操作都应该由错误处理包装。 第14行是程序中的第一个有趣的行。它创建Redis集群对象,使用启动节点列表作为参数,并允许该对象与不同节点建立的最大连接数,最后是超时时间,对于给定的操作多少时间后被视为失败。 启动节点不需要是集群的所有节点。但至少有一个节点是可达的。还要注意,只要能够与第一个节点连接,redis-rb-cluster就会更新此启动节点列表。您应该期望任何其他严格的客户端都应该采取这种行为。 现在我们已经将Redis集群对象实例存储在rc变量中,我们可以像使用普通的Redis对象实例一样使用该对象了。 这恰好发生在第18至26行中:重新启动示例时,我们不想以foo0重新开始,因此我们将计数器存储在Redis本身内。上面的代码旨在读取此计数器,或者如果不存在该计数器,则为其分配零值。 但是请注意这是一个while循环,因为即使集群关闭并返回错误,我们也要一次又一次尝试。普通的应用程序不需要那么小心。 28和37之间开始主循环,在该循环中设置key或显示错误。 注意循环结束时的sleep调用。在测试中,如果您想尽可能快地写入集群,则可以删除sleep(相对来说,这只是一个很繁忙的循环操作,它并没有真正的并行,因此,在最好的条件下,您通常将获得每秒10k个操作))。 通常,为了使示例程序更容易被人看懂,写入速度会减慢。启动应用程序将产生以下输出:
这不是一个非常有趣的程序,我们稍后将使用更好的程序,但是我们已经可以看到程序运行时,在重新分片期间都发生了什么。 集群重新分片现在,我们准备尝试集群重新分片。 为此,请保持example.rb程序运行,以便您查看对程序的运行是否有影响。另外,您可能想注释一下sleep调用,以便在重新分片期间发生一些更严重的写入负载。重新分片基本上意味着将hash槽从一组节点移动到另一组节点,并且像集群创建一样,它使用redis-cli程序完成。 要开始重新分片,只需键入:
您只需要指定一个节点,redis-cli将自动找到其他节点。 当前redis-cli仅能在管理员支持下重新分片,您不能仅仅说将5%的插槽从该节点移到另一个节点(当然这实现起来很简单)。 因此,它会以一个问题开始。 首先是您想做多少重分片:
我们可以尝试重新分派1000个hash槽,如果该示例仍在运行且没有sleep调用,则该hash槽应已包含少量的key。 然后redis-cli需要知道重新分片的目标是什么,也就是将接收hash槽的节点。 我将使用第一个主节点,即127.0.0.1:7000,但是我需要指定实例的节点ID。redis-cli已将其打印在列表中,但是如果需要的话,我也可以使用以下命令找到节点的ID:
所以我的目标节点应该是是 97a3a64667477371c4479320d683e4c8db5858b1。 现在,它会问你要从哪些节点获取这些key。我只输入all,以便从所有其他主节点获取一些hash槽。 最终确认后,您会看到一条消息,表明redis-cli将要从一个节点移动到另一个节点,并且将从一侧移动到另一侧的每个实际的key都会打印出来。 在重新分片过程中,您应该能够看到示例程序运行不受影响。如果需要,您还可以在重新分片期间停止并重新启动它多次。重新分片结束时,可以使用以下命令测试集群的运行状况:
所有插槽都会被覆盖到,但是这次127.0.0.1:7000的主节点将具有更多的hash插槽 一个更有趣的示例应用程序我们之前编写的示例程序不怎么好。它以一种简单的方式写入集群,甚至无需检查写入的内容是否正确。从我们的角度来看,接收写操作的集群可以始终在每个操作里将名为foo的key写到42这个hash槽里,而我们根本不会注意到。因此,在redis-rb-cluster代码仓库中,有一个更有趣的程序,称为consistency-test.rb。它使用一组计数器,默认为1000,并且发送INCR命令以增加计数器的值。但是,该应用程序不仅可以写数据,还可以做两件事:
这意味着该程序是一个简单的一致性检查程序,可以告诉您集群是否丢失了一些写操作,或者它是否接受了我们未收到确认的写操作。在第一种情况下,我们将看到一个计数器的值小于我们之前记住的值,而在第二种情况下,该值将更大。 运行一致性测试应用程序每秒产生一行输出:
该行显示执行的读取和写入的次数,以及错误的数目(由于系统不可用,因此由于错误而无法接受查询)。如果发现不一致,则将新行添加到输出中。例如,如果我在程序运行时手动重置了计数器,就会发生这种情况:
当我将计数器设置为0时,实际值为114,因此程序会报告114的写丢失了(集群无法记住的INCR命令)。该程序作为测试用例更加有趣,因此我们将使用它来测试Redis 集群故障转移。 测试故障转移注意:在此测试过程中,你应打开一个tab标签页并在上面运行一致性测试应用程序。 为了触发故障转移,我们可以做的最简单的事情(也就是在分布式系统中可能发生的语义上最简单的失败)是使单个进程崩溃,在我们的例子中是单个主机崩溃。 我们可以使用以下命令来识别主节点并使其崩溃:
好了,现在7000,7001,7002都是主节点,我们把7002这台机器用DEBUG SEGFAULT命令使其崩溃。
现在我们可以看看这个一致性测试的输出的报告是什么。
如您所见,在故障转移期间,系统无法接受578次读取和577次写入,但是在数据库中并未创建任何不一致的数据。 这听起来可能是个意外,因为在本教程的第一部分中,我们说过Redis集群在故障转移期间会丢失写操作,因为它使用异步复制。我们没有说的是,这其实不太可能发生,因为Redis会给客户端发送回应,并且同样的命令几乎同时会复制到备份节点,因此丢失数据的窗口很小。但是,很难触发这一事实并不意味着它不可能,因此这不会改变Redis集群提供的一致性保证。 现在,我们可以检查故障转移之后的集群设置是什么(请注意,我重新启动了崩溃的实例,以便它作为备份节点重新加入集群):
现在,主节点在端口7000、7001和7005上运行。以前是主节点(在端口7002上运行的Redis实例)现在变成了7005的备份节点。 CLUSTER NODES命令的输出可能看起来很复杂,但实际上非常简单,由以下标记组成:
手动故障转移有时,强制进行故障转移而实际上不会对主节点引起任何问题是很有用的。例如,为了升级主节点之一的Redis进程,最好对其进行故障转移,以将其转变为对可用性的影响最小的备份节点。 Redis集群使用CLUSTER FAILOVER 命令支持手动故障转移,该手动故障转移必须在要进行故障转移的主节点的备份节点之一中执行。 与实际的主服务器故障导致的故障转移相比,手动故障转移是不一样的,但它更安全,因为它们触发的方式避免了此过程中的数据丢失,只有在系统确定新的主节点已经在运行并且替代了旧的主节点的数据复制功能后,才能将客户端从原来的主节点切换到新的主节点。 在执行手动故障转移时在备份节点日志中可以看到:
基本上,连接到我们将要进行故障转移的主节点的客户端都已停止。同时,主节点将其复制偏移发送到备份节点,备份节点会在它这边等待偏移接收完毕。 当复制偏移量完成时,故障转移开始,并且将向旧的主节点通知配置切换。 当客户端在旧的主节点上解锁时,它们将被重定向到新的主节点。 添加新节点添加新节点的基本过程是先添加一个空节点,然后将一些数据移入该节点(如果它是新的主节点),或者告诉它设置为已知节点的副本(如果它是备份节点)。从添加新的主节点开始,我们两者都会展示。在这两种情况下,要执行的第一步都是添加一个空节点。这就像在端口7006中启动一个新节点(现有的6个节点已经从7000到7005使用新节点)一样简单,除了端口号之外,其他节点都使用相同的配置,因此您应该按顺序进行操作以符合我们之前节点使用的设置:
此时这个服务应该运行起来了。现在我们可以使用redis-cli来向已有的集群添加一个节点。
如您所见,我使用add-node命令将新节点的地址指定为第一个参数,并将集群中随机存在的节点的地址指定为第二个参数。实际上,redis-cli在这里对我们没什么用,它只是向节点发送了CLUSTERMEET消息,这也可以手动完成。不过redis-cli会在运行之前检查集群的状态,因此,即使您知道内部结构如何运行,通过redis-cli执行集群操作是仍然是一个好主意。 现在,我们可以连接到新节点,以查看它是否确实加入了集群:
请注意,由于此节点已经连接到集群,因此它已经能够正确重定向客户端查询,通常来说它已经是集群的一部分了。 但是,与其他主节点相比,它有两个特点:
现在可以使用redis-cli的重新分片功能将hash槽分配给该节点。像上一节中已经展示的那样,这里我就不展示了,他们的操作没有区别,只是将空节点作为目标进行重新分片。 添加一个节点作为副本(备份节点)添加一个备份节点可以通过2种方式完成。最常用的是用 redis-cli, 不过要用
请注意,此处的命令行与我们用于添加新主节点的命令行完全相同,因此我们并未指定要向其添加副本的主节点。在这种情况下,redis-cli要做的就是将新节点添加给副本较少的主节点中的随机主节点的副本。但是,您可以使用以下命令行指定想要与新副本一起使用的主节点:
这样我们便将新副本分配给特定的主节点。 将副本添加到特定主节点的一种更手动的方法是将新节点添加为空的主节点,然后使用CLUSTER REPLICATE命令将其转换为副本。 如果将该节点添加为备份节点,但您想将其作为其他主节点的副本进行移动,也一样适用。 例如,为了给节点127.0.0.1:7005添加副本,此节点当前服务的hash槽正在11423-16383范围内,节点ID为3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e,我要做的就是连接到新节点(已经作为空的主节点添加到集群)并在新节点上发送命令:
就是这样。 现在,这组hash槽有了一个新副本,并且集群中的所有其他节点都已经知道(几秒钟后需要更新其配置)。 我们可以使用以下命令进行验证:
节点3c3a0c...现在有了2个备份节点,运行在7002端口(已存在)和7006端口(新加入的) 移除节点为了移除一个备份节点,只需要在redis-cli上使用del-node 命令:
第一个参数只是集群中的一个随机节点,第二个参数是您要删除的节点的ID。您也可以用相同的方法删除主节点,但是要删除主节点,它必须为空。如果主节点不为空,则需要先将数据重新分片到所有其他主节点。 删除主节点的另一种方法是在其一个备份节点上对其执行手动故障转移,并在该节点成为新主节点的备份节点之后删除该节点。显然,这在您想要减少集群中的主节点的实际数量时没什么用,在这种情况下,需要重新分片。 副本迁移在Redis集群里里任何时间你都可以重新配置一个备份节点使其作为另一个主节点的从属节点,使用下列命令:
但是,有一种特殊情况,您希望副本在没有系统管理员帮助的情况下自动从一个主节点移动到另一个主节点。副本的自动重新配置称为副本迁移,它能够提高Redis集群的可靠性。 注意:您可以在Redis集群规范中阅读副本迁移的详细信息,这里我们仅提供一些一般的想法以及您应该从中受益的信息。 在某些情况下,您可能想让您的集群副本从一个主节点移动到另一个主节点的原因是,Redis集群通常具有与给定主节点的副本数量相同的故障容忍性。 例如,如果一个主节点及其副本同时失败,则每个主节点都有一个副本的集群将无法继续工作,这仅仅是因为没有其他实例拥有与该主节点服务的相同的hash槽的副本。但是,尽管网络断裂可能会同时隔离多个节点,但是许多其他类型的故障(例如单个节点本地的硬件或软件故障)是非常值得注意的一类故障,这类故障不太可能同时发生,因此在每个主节点都有一个备份节点的集群中,如果该备份节点在凌晨4点被关闭,而主节点在凌晨6点被关闭。这仍然会导致集群无法运行。 为了提高系统的可靠性,我们可以选择向每个主节点添加副本,但这成本很高。副本迁移允许将更多备份节点添加到少数几个主节点中。因此,您有10个节点,每个节点有1个备份节点,总共20个实例。但是,比如您增加了3个实例作为某些主节点的备份节点,因此某些主节点将具有多个副本了。 使用副本迁移时,如果一个主节点不包含备份节点,则具有多个备份节点的主节点的副本将迁移到孤立的主节点。因此,当您的备份节点在上述示例中的凌晨4点关闭之后,另一个备份节点将接替它;当主节点在凌晨5点也发生故障时,另一个备份节点将被选举成为主节点,以便集群可以继续操作。 所以您应该了解哪些有关副本迁移的知识?
|
|