Redis集群教程

本文档是对Redis Cluster的简要介绍,它不使用复杂的方法来理解分布式系统概念。它提供了有关如何设置群集,测试和操作群集的说明,而不涉及Redis群集规范中涵盖的详细信息,而只是从用户的角度描述系统的行为方式。

但是,本教程尝试从最终用户的角度提供有关Redis Cluster的可用性和一致性特征的信息,以简单易懂的方式说明。

请注意,本教程需要Redis 3.0或更高版本。

如果您计划运行严格的Redis群集部署,则更正式的规范是建议的读数,即使不是严格要求。但是,从本文档开始,使用Redis Cluster一段时间,然后才阅读规范是一个好主意。

Redis Cluster 101

Redis Cluster提供了一种运行Redis安装的方法,其中数据 在多个Redis节点之间自动分片

Redis Cluster还在分区期间提供一定程度的可用性,实际上是在某些节点发生故障或无法通信时继续运行的能力。但是,如果发生较大的故障(例如,当大多数主设备不可用时),群集将停止运行。

所以实际上,你对Redis Cluster有什么看法?

  • 能够在多个节点之间自动拆分数据集
  • 当节点的子集遇到故障或无法与群集的其余部分通信,能够继续操作

Redis群集TCP端口

每个Redis群集节点都需要打开两个TCP连接。用于为客户端提供服务的普通Redis TCP端口,例如6379,加上通过向数据端口添加10000获得的端口,因此示例中为16379。

第二个端口用于集群总线,即使用二进制协议的节点到节点通信通道。节点使用集群总线进行故障检测,配置更新,故障转移授权等。客户端永远不应尝试与群集总线端口通信,但始终使用正常的Redis命令端口,但请确保在防火墙中打开两个端口,否则Redis群集节点将无法通信。

命令端口和集群总线端口偏移是固定的,始终为10000。

请注意,对于每个节点,要使Redis群集正常工作,您需要:

  1. 用于与客户端通信的普通客户端通信端口(通常为6379)对所有需要访问群集的客户端以及所有其他群集节点(使用客户端端口进行密钥迁移)开放。
  2. 必须可以从所有其他群集节点访问群集总线端口(客户端端口+ 10000)。

如果不打开两个TCP端口,则群集将无法按预期工作。

集群总线使用不同的二进制协议进行节点到节点的数据交换,这更适合于使用很少的带宽和处理时间在节点之间交换信息。

Redis集群和Docker

目前,Redis群集不支持NATted环境,也不支持重新映射IP地址或TCP端口的一般环境。

Docker使用一种称为端口映射的技术:与程序认为使用的端口相比,在Docker容器内运行的程序可能会使用不同的端口。这对于在同一服务器中同时使用相同端口运行多个容器非常有用。

为了使Docker与Redis Cluster兼容,您需要使用Docker 的主机网络模式。有关更多信息,请查看Docker文档中的--net=host选项。

Redis群集数据分片

Redis Cluster不使用一致的散列,而是使用不同形式的分片,其中每个键在概念上都是我们称之为散列槽的一部分

Redis集群中有16384个散列槽,为了计算给定密钥的散列槽,我们只需采用密钥模数16384的CRC16。

Redis群集中的每个节点都负责哈希槽的子集,例如,您可能拥有一个包含3个节点的群集,其中:

  • 节点A包含从0到5500的散列槽。
  • 节点B包含从5501到11000的散列槽。
  • 节点C包含从11001到16383的散列槽。

这允许轻松添加和删除集群中的节点。例如,如果我想添加一个新节点D,我需要将一些哈希槽从节点A,B,C移动到D.同样,如果我想从群集中删除节点A,我只需移动A服务的哈希槽。到B和C.当节点A为空时,我可以完全从集群中删除它。

因为将哈希槽从一个节点移动到另一个节点不需要停止操作,添加和删除节点,或者更改节点所持有的哈希槽的百分比,所以不需要任何停机时间。

只要涉及单个命令执行(或整个事务或Lua脚本执行)的所有键都属于同一个哈希槽,Redis Cluster就支持多个键操作。用户可以通过使用称为哈希标记的概念强制多个密钥成为同一哈希槽的一部分。

散列标记记录在Redis集群规范中,但要点是如果密钥中{}括号之间有子字符串,则只对字符串内部的内容进行散列,例如this{foo}keyanother{foo}key 保证位于相同的散列槽中,并且可以在具有多个键作为参数的命令中一起使用。

Redis Cluster主从模型

为了在主节点子集发生故障或无法与大多数节点通信时保持可用,Redis Cluster使用主从模型,其中每个散列槽从1(主机本身)到N个副本(N) -1个额外的从节点)。

在具有节点A,B,C的示例群集中,如果节点B发生故障,则群集无法继续,因为我们不再能够在5501-11000范围内提供服务哈希位置的方法。

然而,当创建集群时(或稍后),我们向每个主节点添加一个从节点,以便最终集群由作为主节点的A,B,C和作为从节点的A1,B1,C1组成。 ,如果节点B出现故障,系统就能继续运行。

节点B1复制B,B失败,集群将节点B1升级为新的主节点,并将继续正常运行。

但请注意,如果节点B和B1同时发生故障,Redis Cluster将无法继续运行。

Redis群集一致性保证

Redis Cluster无法保证强一致性。实际上,这意味着在某些条件下,Redis Cluster可能会丢失系统向客户端确认的写入。

Redis Cluster可能丢失写入的第一个原因是它使用异步复制。这意味着在写入期间会发生以下情况:

  • 您的客户端写入主B.
  • 主人B向您的客户回复确定。
  • 主设备B将写入传播到其从设备B1,B2和B3。

正如你所看到的,B在回复客户端之前并没有等待来自B1,B2,B3的确认,因为这对Redis来说是一个过高的延迟惩罚,所以如果你的客户端写了一些内容,B会确认写入,但是在崩溃之前崩溃能够将写入发送到其从属,其中一个从属(没有接收到写入)可以被提升为主,永远丢失写入。

这与配置为每秒将数据刷新到磁盘的大多数数据库所发生的情况非常相似,因此,由于过去使用不涉及分布式系统的传统数据库系统的经验,因此您已经能够推断这种情况。同样,您可以通过在回复客户端之前强制数据库刷新磁盘上的数据来提高一致性,但这通常会导致性能过低。在Redis Cluster的情况下,这相当于同步复制。

基本上需要在性能和一致性之间进行权衡。

Redis Cluster在绝对需要时支持同步写入,通过WAIT命令实现,这使得丢失写入的可能性大大降低,但请注意,即使使用同步复制,Redis Cluster也不会实现强一致性:在更复杂的情况下总是可以实现失败场景,无法接收写入的从站被选为主站。

还有另一个值得注意的情况是,Redis群集将丢失写入,这种情况发生在网络分区中,其中客户端与少数实例(至少包括主服务器)隔离。

以6个节点簇为例,包括A,B,C,A1,B1,C1,3个主站和3个从站。还有一个客户,我们称之为Z1。

在发生分区之后,可能在分区的一侧有A,C,A1,B1,C1,在另一侧有B和Z1。

Z1仍然可以写入B,它将接受其写入。如果分区在很短的时间内恢复,群集将继续正常运行。但是,如果分区持续足够的时间使B1在分区的多数侧被提升为主,则Z1发送给B的写入将丢失。

请注意,Z1将能够发送到B的写入量存在最大窗口:如果分区的多数方面已经有足够的时间将从属设备选为主设备,则少数端的每个主节点都会停止接受写入。

这段时间是Redis Cluster的一个非常重要的配置指令,称为节点超时

节点超时过后,主节点被视为失败,可以由其中一个副本替换。类似地,在节点超时已经过去而主节点无法感知大多数其他主节点之后,它进入错误状态并停止接受写入。

Redis群集配置参数

我们即将创建一个示例集群部署。在继续之前,让我们介绍Redis Cluster在redis.conf文件中引入的配置参数。有些人会很明显,有些人会在你继续阅读时更清楚。

  • cluster-enabled:如果是,则在特定Redis实例中启用Redis群集支持。否则,实例像往常一样作为独立实例启动。
  • cluster-config-file:请注意,尽管有此选项的名称,但这不是用户可编辑的配置文件,而是每次发生更改时Redis群集节点自动保持群集配置(基本上是状态)的文件,为了能够在启动时重新阅读它。该文件列出了集群中其他节点,状态,持久变量等内容。由于某些消息接收,通常会将此文件重写并刷新到磁盘上。
  • cluster-node-timeout:Redis群集节点不可用的最长时间,不会被视为失败。如果主节点的可访问时间超过指定的时间,则其从属节点将进行故障转移。此参数控制Redis群集中的其他重要事项。值得注意的是,在指定时间内无法访问大多数主节点的每个节点都将停止接受查询。
  • cluster-slave-validity-factor:如果设置为零,则从站将始终尝试对主站进行故障切换,而不管主站和从站之间的链路是否保持断开连接的时间长短。如果该值为正,则计算最大断开时间作为节点超时值乘以此选项提供的因子,如果节点是从属节点,则如果主链接断开连接的时间超过指定的时间,则不会尝试启动故障转移。例如,如果节点超时设置为5秒,并且有效性因子设置为10,则从主设备断开超过50秒的从设备将不会尝试故障转移其主设备。请注意,如果没有从站能够对其进行故障转移,则任何不同于零的值都可能导致Redis群集在主站发生故障后不可用。在这种情况下,只有当原始主服务器重新加入群集时,群集才会返回。
  • cluster-migration-barrier:主服务器将保持连接的最小从服务器数,以便另一个从服务器迁移到不再由任何从服务器覆盖的主服务器。有关详细信息,请参阅本教程中有关副本迁移的相应部分。
  • cluster-require-full-coverage:如果设置为yes,则默认情况下,如果任何节点未覆盖某个百分比的密钥空间,则集群将停止接受写入。如果该选项设置为no,即使只能处理有关键子集的请求,群集仍将提供查询。

创建和使用Redis群集

注意:要手动部署Redis群集,了解它的某些操作方面非常重要。但是,如果要启动集群并尽快运行(尽快),请跳过本节和下一节,然后直接使用create-cluster脚本创建Redis集群

要创建集群,我们首先要做的是在集群模式下运行一些空的Redis实例。这基本上意味着不使用普通的Redis实例创建集群,因为需要配置特殊模式,以便Redis实例启用集群特定的功能和命令。

以下是最小的Redis群集配置文件:

port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

正如您所看到的,启用集群模式的只是cluster-enabled 指令。每个实例还包含存储此节点配置的文件路径,默认情况下为nodes.conf。这个文件永远不会被人类接触; 它只是在Redis Cluster实例启动时生成,并在每次需要时更新。

请注意,按预期工作的最小群集需要包含至少三个主节点。对于您的第一次测试,强烈建议启动具有三个主设备和三个从设备的六节点集群。

为此,请输入一个新目录,并创建以我们将在任何给定目录中运行的实例的端口号命名的以下目录。

就像是:

mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005

redis.conf在每个目录中创建一个文件,从7000到7005.作为配置文件的模板,只需使用上面的小例子,但请确保7000根据目录名称用正确的端口号替换端口号。

现在将从GitHub的unstable分支中的最新源编译的 redis-server可执行文件复制到cluster-test目录中,最后在您喜欢的终端应用程序中打开6个终端选项卡。

像这样开始每个实例,每个选项卡一个:

cd 7000
../redis-server ./redis.conf

从每个实例的日志中可以看出,由于不nodes.conf存在任何文件,因此每个节点都会为自己分配一个新ID。

[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1

此特定实例将永久使用此ID,以使实例在群集的上下文中具有唯一名称。每个节点都使用此ID记住每个其他节点,而不是通过IP或端口记住。IP地址和端口可能会发生变化,但唯一的节点标识符永远不会在节点的整个生命周期内发生变化。我们称这个标识符为Node ID

创建集群

现在我们已经运行了许多实例,我们需要通过向节点编写一些有意义的配置来创建我们的集群。

如果您使用的是Redis 5,这很容易实现,因为我们在嵌入的Redis Cluster命令行实用程序的帮助下redis-cli,可以用来创建新集群,检查或重新硬化现有集群等等。

对于Redis版本3或4,有一个名为redis-trib.rb非常相似的旧工具。您可以src在Redis源代码分发的目录中找到它。你需要安装redisgem才能运行redis-trib

gem install redis

第一个例子,即集群创建,将redis-cli在Redis 5和redis-tribRedis 3和4中同时显示。但是所有下面的例子都只会使用redis-cli,因为你可以看到语法非常相似,你可以琐碎通过使用redis-trib.rb help获取有关旧语法的信息将一个命令行更改为另一个命令行。重要提示:请注意,redis-cli如果您愿意,可以使用Redis 5 对抗Redis 4群集而不会出现问题。

要为Redis 5创建集群,redis-cli只需键入:

redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1

使用redis-trib.rb用于Redis的4或3型:

./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

这里使用的命令是create,因为我们想要创建一个新的集群。该选项--cluster-replicas 1意味着我们希望每个创建的主服务器都有一个从服 其他参数是我要用于创建新集群的实例的地址列表。

显然,我们要求的唯一设置是创建一个包含3个主服务器和3个从服务器的集群。

Redis-cli将为您提供配置。键入yes接受建议的配置。将配置并加入群集,这意味着实例将被引导为彼此通信。最后,如果一切顺利,你会看到这样的消息:

[OK] All 16384 slots covered

这意味着至少有一个主实例为16384个可用插槽提供服务。

使用create-cluster脚本创建Redis群集

如果您不想通过如上所述手动配置和执行单个实例来创建Redis群集,则可以使用更简单的系统(但您不会学习相同数量的操作详细信息)。

只需检查utils/create-clusterRedis发行版中的目录即可。create-cluster内部有一个脚本(与其包含的目录同名),它是一个简单的bash脚本。要启动具有3个主服务器和3个从服务器的6节点集群,只需键入以下命令:

  1. create-cluster start
  2. create-cluster create

yesredis-cli实用程序希望您接受群集布局时,在步骤2中回复。

您现在可以与群集交互,默认情况下,第一个节点将从端口30001开始。完成后,使用以下命令停止群集:

  1. create-cluster stop

README有关如何运行脚本的更多信息,请阅读此目录中的内容。

玩群集

在此阶段,Redis Cluster的一个问题是缺少客户端库实现。

我知道以下实现:

  • redis-rb-cluster是由我(@antirez)编写的Ruby实现,作为其他语言的参考。它是原始redis-rb的简单包装器,实现了最小的语义,可以有效地与集群通信。
  • redis-py-cluster redis-rb-cluster到Python的一个端口。支持大多数redis-py功能。正在积极发展。
  • 流行的Predis支持Redis Cluster,最近更新了支持并且正在积极开发中。
  • 最常用的Java客户端Jedis最近添加了对Redis Cluster的支持,请参阅项目README中的Jedis Cluster部分。
  • StackExchange.Redis提供对C#的支持(并且应该适用于大多数.NET语言; VB,F#等)
  • thunk-redis支持Node.js和io.js,它是一个基于thunk / promise的redis客户端,具有流水线和集群。
  • redis-go-cluster是使用Redigo库客户端作为基本客户端的Go语言的Redis集群的实现。通过结果聚合实现MGET / MSET。
  • redis-cli与开始时在GitHub上的Redis的存储库的不稳定分支工具实现了一个非常基本的集群支持-c开关。

测试Redis Cluster的一种简单方法是尝试上述任何客户端或仅使用redis-cli命令行实用程序。以下是使用后者的交互示例:

$ redis-cli -c -p 7000
redis 127.0.0.1:7000> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:7002
OK
redis 127.0.0.1:7002> set hello world
-> Redirected to slot [866] located at 127.0.0.1:7000
OK
redis 127.0.0.1:7000> get foo
-> Redirected to slot [12182] located at 127.0.0.1:7002
"bar"
redis 127.0.0.1:7000> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"

注意:如果使用脚本创建群集,则节点可以侦听不同的端口,默认情况下从30001开始。

redis-cli集群支持非常基础,因此它始终使用Redis Cluster节点能够将客户端重定向到右节点的事实。一个认真的客户端能够做得更好,并在哈希槽和节点地址之间缓存地图,以直接使用与正确节点的正确连接。仅当群集配置中的某些内容发生更改时(例如,在故障转移之后或系统管理员通过添加或删除节点更改群集布局后),才会刷新映射。

使用redis-rb-cluster编写示例应用程序

在继续展示如何操作Redis集群,执行故障转移或重新分片之前,我们需要创建一些示例应用程序,或者至少能够理解简单的Redis集群客户端交互的语义。

通过这种方式,我们可以运行一个示例,同时尝试使节点失败,或者开始重新分片,以了解Redis Cluster在真实条件下的行为方式。看到没有人写入群集时会发生什么并不是很有帮助。

本节介绍了redis-rb-cluster的一些基本用法, 展示了两个示例。第一个是以下内容,是 example.rb redis-rb-cluster发行版中的文件:

   1  require './cluster'
   2
   3  if ARGV.length != 2
   4      startup_nodes = [
   5          {:host => "127.0.0.1", :port => 7000},
   6          {:host => "127.0.0.1", :port => 7001}
   7      ]
   8  else
   9      startup_nodes = [
  10          {:host => ARGV[0], :port => ARGV[1].to_i}
  11      ]
  12  end
  13
  14  rc = RedisCluster.new(startup_nodes,32,:timeout => 0.1)
  15
  16  last = false
  17
  18  while not last
  19      begin
  20          last = rc.get("__last__")
  21          last = 0 if !last
  22      rescue => e
  23          puts "error #{e.to_s}"
  24          sleep 1
  25      end
  26  end
  27
  28  ((last.to_i+1)..1000000000).each{|x|
  29      begin
  30          rc.set("foo#{x}",x)
  31          puts rc.get("foo#{x}")
  32          rc.set("__last__",x)
  33      rescue => e
  34          puts "error #{e.to_s}"
  35      end
  36      sleep 0.1
  37  }

应用程序做了一件非常简单的事情,它将表单中的键设置foonumber一个接一个。因此,如果您运行该程序,结果是以下命令流:

  • SET foo0 0
  • SET foo1 1
  • SET foo2 2
  • 等等...

该程序看起来比它应该通常更复杂,因为它被设计为在屏幕上显示错误而不是以异常退出,因此对集群执行的每个操作都由begin rescue块包装。

14行是该程序中第一个有趣的行。它创建Redis Cluster对象,使用启动节点列表作为参数,允许此对象对不同节点采用的最大连接数,最后在给定操作被认为失败后超时。

启动节点不需要是群集的所有节点。重要的是至少有一个节点是可达的。另请注意,只要redis-rb-cluster能够与第一个节点连接,它就会更新此启动节点列表。您应该期望与任何其他严肃的客户端这样的行为。

既然我们已将Redis Cluster对象实例存储在rc变量中,我们就可以使用该对象,就好像它是一个普通的Redis对象实例一样。

这正是第18到26行所发生的事情:当我们重新启动示例时,我们不想再次启动foo0,因此我们将计数器存储在Redis本身中。上面的代码用于读取此计数器,或者如果计数器不存在,则将其赋值为零。

但请注意它是如何循环,因为我们想要反复尝试,即使群集已关闭并返回错误。普通应用程序不需要那么小心。

28到37之间的行启动主循环,其中设置了键或显示错误。

注意sleep循环结束时的调用。在你的测试中,如果你想尽可能快地写入集群,你可以移除睡眠(相对于这是一个繁忙的循环,当然没有真正的并行性,所以你通常会获得10k的操作/秒)最好的条件)。

通常,写入速度会降低,以便人类更容易遵循示例应用程序。

启动应用程序会生成以下输出:

ruby ./example.rb
1
2
3
4
5
6
7
8
9
^C (I stopped the program here)

这不是一个非常有趣的程序,我们稍后会使用更好的程序,但我们已经可以看到程序运行时重新分片期间会发生什么。

重新整理群集

现在我们准备尝试集群重新分片了。为此,请保持example.rb程序运行,以便您可以查看是否对运行的程序有一些影响。此外,您可能想要评论该sleep 调用,以便在重新分片期间有一些更严重的写入负载。

重新分片基本上意味着将散列槽从一组节点移动到另一组节点,并且像集群创建一样,它是使用redis-cli实用程序完成的。

要开始重新分片,只需输入:

redis-cli --cluster reshard 127.0.0.1:7000

您只需指定一个节点,redis-cli将自动找到其他节点。

目前redis-cli只能通过管理员支持重新加载,你不能只说将5%的插槽从这个节点移动到另一个节点(但这实现起来非常简单)。所以它从问题开始。首先是你要做多少大的重新分数:

How many slots do you want to move (from 1 to 16384)?

我们可以尝试重新刷新1000个哈希槽,如果示例仍在没有睡眠调用的情况下运行,那么它应该已包含非常少量的密钥。

然后redis-cli需要知道重新分片的目标是什么,即接收哈希槽的节点。我将使用第一个主节点,即127.0.0.1:7000,但我需要指定实例的节点ID。这已由redis-cli打印在列表中,但如果需要,我总能使用以下命令找到节点的ID:

$ redis-cli -p 7000 cluster nodes | grep myself
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5460

好的,我的目标节点是97a3a64667477371c4479320d683e4c8db5858b1。

现在,您将被问到要从哪些节点获取这些密钥。我只是输入all以便从所有其他主节点获取一些哈希槽。

在最终确认之后,您将看到redis-cli将从一个节点移动到另一个节点的每个插槽的消息,并且将为从一侧移动到另一侧的每个实际键打印一个点。

重新分片正在进行中时,您应该能够看到您的示例程序不受影响地运行。如果需要,您可以在重新分片期间多次停止并重新启动它。

在重新分片结束时,您可以使用以下命令测试群集的运行状况:

redis-cli --cluster check 127.0.0.1:7000

所有的插槽都会像往常一样被覆盖,但这次127.0.0.1:7000的主机将有更多的散列槽,大约6461。

编写重新分析操作的脚本

可以自动执行重新分片,而无需以交互方式手动输入参数。这可以使用如下命令行:

redis-cli reshard : --cluster-from  --cluster-to  --cluster-slots  --cluster-yes

如果您可能经常重新设置,则允许构建一些自动操作,但是目前无法redis-cli自动重新平衡群集,检查跨群集节点的密钥分发以及根据需要智能地移动插槽。此功能将在未来添加。

更有趣的示例应用程序

我们早期写的示例应用程序不是很好。它以简单的方式写入集群,甚至不检查写入的内容是否正确。

从我们的观点来看,集群接收写入可能只是总是写的关键foo,以42每一个动作,我们不会在所有通知。

所以在redis-rb-cluster存储库中,有一个更有趣的应用程序被调用consistency-test.rb。它使用一组计数器,默认为1000,并发送INCR命令以递增计数器。

然而,该应用程序不仅仅是编写,而是执行另外两项操作:

  • 使用INCR更新计数器时,应用程序会记住写入。
  • 它还在每次写入之前读取一个随机计数器,并检查该值是否与我们预期的值相比,将其与内存中的值进行比较。

这意味着该应用程序是一个简单的一致性检查器,并且能够告诉您集群是否丢失了一些写入,或者它是否接受了我们未收到确认的写入。在第一种情况下,我们将看到一个计数器的值小于我们记忆的值,而在第二种情况下,该值将更大。

运行一致性测试应用程序每秒产生一行输出:

$ ruby consistency-test.rb
925 R (0 err) | 925 W (0 err) |
5030 R (0 err) | 5030 W (0 err) |
9261 R (0 err) | 9261 W (0 err) |
13517 R (0 err) | 13517 W (0 err) |
17780 R (0 err) | 17780 W (0 err) |
22025 R (0 err) | 22025 W (0 err) |
25818 R (0 err) | 25818 W (0 err) |

该行显示执行的R eads和W rites的数量,以及错误的数量(由于系统不可用,因错误而无法接受查询)。

如果发现某些不一致,则会在输出中添加新行。这就是发生的情况,例如,如果我在程序运行时手动重置计数器:

$ redis-cli -h 127.0.0.1 -p 7000 set key_217 0
OK

(in the other tab I see...)

94774 R (0 err) | 94774 W (0 err) |
98821 R (0 err) | 98821 W (0 err) |
102886 R (0 err) | 102886 W (0 err) | 114 lost |
107046 R (0 err) | 107046 W (0 err) | 114 lost |

当我将计数器设置为0时,实际值为114,因此程序报告114丢失写入(群集未记住的INCR命令)。

作为测试用例,该程序更有趣,因此我们将使用它来测试Redis Cluster故障转移。

测试故障转移

注意:在此测试期间,您应该打开一个选项卡,并运行一致性测试应用程序。

为了触发故障转移,我们可以做的最简单的事情(也就是在分布式系统中可能出现的语义上最简单的故障)是使单个进程崩溃,在我们的例子中是单个进程。

我们可以使用以下命令识别集群并使其崩溃:

$ redis-cli -p 7000 cluster nodes | grep master
3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385482984082 0 connected 5960-10921
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 master - 0 1385482983582 0 connected 11423-16383
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5959 10922-11422

好吧,7000,7001和7002都是主人。让我们用DEBUG SEGFAULT命令崩溃节点7002 :

$ redis-cli -p 7002 debug segfault
Error: Server closed the connection

现在我们可以查看一致性测试的输出,看看它报告了什么。

18849 R (0 err) | 18849 W (0 err) |
23151 R (0 err) | 23151 W (0 err) |
27302 R (0 err) | 27302 W (0 err) |

... many error warnings here ...

29659 R (578 err) | 29660 W (577 err) |
33749 R (578 err) | 33750 W (577 err) |
37918 R (578 err) | 37919 W (577 err) |
42077 R (578 err) | 42078 W (577 err) |

正如您在故障转移期间所看到的,系统无法接受578次读取和577次写入,但数据库中未创建任何不一致。这可能听起来很意外,因为在本教程的第一部分中我们说过Redis Cluster在故障转移期间可能会丢失写入,因为它使用异步复制。我们没有说的是,这不太可能发生,因为Redis将回复发送到客户端,并且同时复制到从属的命令,因此有一个非常小的窗口丢失数据。但是,它很难触发并不意味着它是不可能的,因此这不会改变Redis集群提供的一致性保证。

我们现在可以检查故障转移后的群集设置是什么(请注意,在此期间我重新启动了崩溃的实例,以便它作为从属服务器重新加入群集):

$ redis-cli -p 7000 cluster nodes
3fc783611028b1707fd65345e763befb36454d73 127.0.0.1:7004 slave 3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 0 1385503418521 0 connected
a211e242fc6b22a9427fed61285e85892fa04e08 127.0.0.1:7003 slave 97a3a64667477371c4479320d683e4c8db5858b1 0 1385503419023 0 connected
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5959 10922-11422
3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7005 master - 0 1385503419023 3 connected 11423-16383
3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385503417005 0 connected 5960-10921
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385503418016 3 connected

现在主设备正在端口7000,7001和7005上运行。以前是主设备,即在端口7002上运行的Redis实例,现在是7005的从设备。

CLUSTER NODES命令的输出可能看起来令人生畏,但它实际上非常简单,并且由以下标记组成:

  • 节点ID
  • IP:端口
  • 旗帜:主人,奴隶,我自己,失败,......
  • 如果它是从属,则为主节点的节点ID
  • 最后一个待处理PING的时间仍在等待回复。
  • 收到最后一个PONG的时间。
  • 此节点的配置时期(请参阅群集规范)。
  • 此节点的链接的状态。
  • 插槽服务......

手动故障转移

有时强制进行故障转移而不会在主服务器上造成任何问题。例如,为了升级其中一个主节点的Redis进程,最好对其进行故障转移,以便将其转换为从属,对可用性的影响最小。

Redis Cluster使用CLUSTER FAILOVER 命令支持手动故障转移,该命令必须在要故障转移的主的一个从站中执行。

手动故障转移是特殊的,与实际主故障导致的故障转移相比更安全,因为它们以避免数据丢失的方式发生,通过仅在系统确定新的主服务器时将客户端从原始主服务器切换到新主服务器master处理了旧的复制流。

这是您在执行手动故障转移时在从属日志中看到的内容:

# Manual failover user request accepted.
# Received replication offset for paused master manual failover: 347540
# All master replication stream processed, manual failover can start.
# Start of election delayed for 0 milliseconds (rank #0, offset 347540).
# Starting a failover election for epoch 7545.
# Failover election won: I'm the new master.

基本上连接到我们正在故障的主服务器的客户端被停止。同时主设备将其复制偏移量发送给从设备,该设备等待到达其侧面的偏移量。达到复制偏移量时,将启动故障转移,并通知旧主服务器有关配置开关的信息。在旧主服务器上取消阻止客户端时,会将它们重定向到新主服务器。

添加新节点

添加新节点基本上是添加空节点然后将一些数据移入其中的过程,以防它是新的主节点,或者告诉它设置为已知节点的副本,以防它是从属节点。

我们将展示两者,从添加新的主实例开始。

在这两种情况下,执行的第一步是添加空节点

这很简单,只需要在端口7006中启动一个新节点(我们已经在7000到7005之间使用现有的6个节点),其他节点使用相同的配置,端口号除外,所以你应该按顺序做什么符合我们用于以前节点的设置:

  • 在终端应用程序中创建一个新选项卡。
  • 输入cluster-test目录。
  • 创建一个名为的目录7006
  • 在里面创建一个redis.conf文件,类似于用于其他节点但使用7006作为端口号的文件。
  • 最后启动服务器 ../redis-server ./redis.conf

此时服务器应该正在运行。

现在我们可以像往常一样使用redis-cli,以便将节点添加到现有集群。

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000

如您所见,我使用add-node命令将新节点的地址指定为第一个参数,并将集群中随机存在节点的地址指定为第二个参数。

实际上,redis-cli在这方面做的很少帮助我们,它只是向节点发送了一个CLUSTER MEET消息,这也可以手动完成。但是redis-cli在运行之前也会检查集群的状态,所以即使你知道内部是如何工作的,也总是通过redis-cli执行集群操作是个好主意。

现在我们可以连接到新节点以查看它是否真正加入了集群:

redis 127.0.0.1:7006> cluster nodes
3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385543178575 0 connected 5960-10921
3fc783611028b1707fd65345e763befb36454d73 127.0.0.1:7004 slave 3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 0 1385543179583 0 connected
f093c80dde814da99c5cf72a7dd01590792b783b :0 myself,master - 0 0 0 connected
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543178072 3 connected
a211e242fc6b22a9427fed61285e85892fa04e08 127.0.0.1:7003 slave 97a3a64667477371c4479320d683e4c8db5858b1 0 1385543178575 0 connected
97a3a64667477371c4479320d683e4c8db5858b1 127.0.0.1:7000 master - 0 1385543179080 0 connected 0-5959 10922-11422
3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7005 master - 0 1385543177568 3 connected 11423-16383

请注意,由于此节点已连接到群集,因此它已能够正确地重定向客户端查询,并且通常是群集的一部分。然而,与其他大师相比,它有两个特点:

  • 它没有数据,因为它没有分配的哈希槽。
  • 因为它是没有分配插槽的主设备,所以当从设备想要成为主设备时,它不参与选举过程。

现在可以使用resharding功能为此节点分配哈希槽redis-cli。显示这一点基本没用,就像我们在上一节中所做的那样,没有区别,它只是一个重新分区,具有空节点的目标。

将新节点添加为副本

添加新副本可以通过两种方式执行。显而易见的是再次使用redis-cli,但使用--cluster-slave选项,如下所示:

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave

请注意,此处的命令行与我们用于添加新主服务器的命令行完全相同,因此我们不指定要添加副本的主服务器。在这种情况下,会发生的事情是redis-cli会将新节点作为随机主副本的副本添加到副本较少的主服务器中。

但是,您可以使用以下命令行准确指定要使用新副本定位的主控制器:

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave --cluster-master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e

这样我们就可以将新副本分配给特定的主副本。

将副本添加到特定主节点的更手动方法是将新节点添加为空主节点,然后使用CLUSTER REPLICATE命令将其转换为副本 。如果节点作为从属节点添加,但您想将其作为不同主节点的副本移动,则此方法也有效。

例如,为了添加当前服务于11423-16383范围内的哈希槽的节点127.0.0.1:7005的副本,其具有节点ID 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e,我需要做的就是连接新节点(已经添加为空主)并发送命令:

redis 127.0.0.1:7006> cluster replicate 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e

而已。现在我们为这组哈希槽提供了一个新的副本,并且群集中的所有其他节点都已知道(需要几秒钟后才能更新它们的配置)。我们可以使用以下命令进行验证:

$ redis-cli -p 7000 cluster nodes | grep slave | grep 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e
f093c80dde814da99c5cf72a7dd01590792b783b 127.0.0.1:7006 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617702 3 connected
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617198 3 connected

节点3c3a0c ...现在具有两个从设备,在端口7002(现有的一个)和7006(新的一个)上运行。

删除节点

要删除从属节点,只需使用del-noderedis-cli命令:

redis-cli --cluster del-node 127.0.0.1:7000 ``

第一个参数只是集群中的随机节点,第二个参数是要删除的节点的ID。

您也可以以相同的方式删除主节点,但是为了删除主节点,它必须为空。如果主服务器不为空,则需要将数据从其重新分配给所有其他主节点。

删除主节点的另一种方法是在其中一个从节点上执行手动故障转移,并在节点变为新主节点的从节点后将其删除。显然,当你想减少集群中实际的主数量时,这没有用,在这种情况下,需要重新分片。

副本迁移

在Redis群集中,只需使用以下命令,就可以随时使用不同的主服务器重新配置从属服务器进行复制:

CLUSTER REPLICATE 

但是,有一种特殊情况,您希望副本在没有系统管理员帮助的情况下自动从一个主服务器移动到另一个主服务器。副本的自动重新配置称为副本迁移,并且能够提高Redis群集的可靠性。

注意:您可以在Redis群集规范中阅读副本迁移的详细信息,这里我们仅提供有关一般概念的一些信息以及您应该从中获益的信息。

您可能希望让群集副本在特定条件下从一个主服务器移动到另一个主服务器的原因是,通常Redis群集与附加到给定主服务器的副本数量一样可以抵御故障。

例如,如果主服务器及其副本同时发生故障,则每个主服务器具有单个副本的群集无法继续操作,原因很简单,因为没有其他实例可以拥有主服务器所服务的散列插槽的副本。然而,虽然netsplits可能同时隔离多个节点,但许多其他类型的故障(如单个节点本地的硬件或软件故障)是一类非常值得注意的故障,不太可能同时发生,所以有可能在你的集群中每个主人都有一个奴隶,奴隶在凌晨4点被杀死,主人在早上6点被杀死。这仍然会导致群集无法再运行。

为了提高系统的可靠性,我们可以选择为每个主站添加额外的副本,但这很昂贵。副本迁移允许向少数主服务器添加更多从服务器。所以你有10个主人,每个主人有1个奴隶,总共20个实例。但是,例如,您添加了3个实例作为某些主服务器的从属服务器,因此某些主服务器将拥有多个服务器。

对于副本迁移,发生的情况是,如果主站没有从站,则来自具有多个从站的主站的副本将迁移到孤立主站。所以在你的奴隶在我们上面的例子中凌晨4点关闭之后,另一个奴隶将占据它的位置,当主人在凌晨5点失败时,仍然有一个奴隶可以被选举,以便集群可以继续操作。

那么您应该简要了解复制品迁移的内容?

  • 群集将尝试从给定时刻具有最大副本数的主服务器迁移副本。
  • 要从副本迁移中受益,您只需要向群集中的单个主服务器添加一些副本,这与主服务器无关。
  • 有一个配置参数可以控制所调用的副本迁移功能cluster-migration-barrier:您可以在redis.confRedis Cluster提供的示例文件中阅读有关它的更多信息。

升级Redis群集中的节点

升级从属节点非常简单,因为您只需要停止节点并使用更新版本的Redis重新启动它。如果有客户端使用从节点缩放读取,则如果给定的节点不可用,它们应该能够重新连接到其他从属节点。

升级大师有点复杂,建议的过程是:

  1. 使用CLUSTER FAILOVER触发主站到其中一个从站的手动故障转移(请参阅本文档的“手动故障转移”部分)。
  2. 等待主人变成奴隶。
  3. 最后像对奴隶一样升级节点。
  4. 如果您希望master成为刚刚升级的节点,请触发新的手动故障转移,以便将升级后的节点转回主节点。

按照此过程,您应该逐个升级一个节点,直到升级所有节点。

迁移到Redis群集

愿意迁移到Redis群集的用户可能只有一个主服务器,或者可能已经使用预先存在的分片设置,其中密钥在N个节点之间分配,使用一些内部算法或由其客户端库或Redis代理实现的分片算法。

在这两种情况下都可以轻松迁移到Redis群集,但最重要的细节是应用程序使用多键操作,以及如何使用。有三种不同的情况:

  1. 不使用多个键操作或事务,或涉及多个键的Lua脚本。密钥是独立访问的(即使通过事务或Lua脚本分组多个命令,关于相同的密钥,一起访问)。
  2. 使用涉及多个密钥的多个密钥操作,事务或Lua脚本,但仅使用具有相同散列标记的密钥,这意味着一起使用的密钥都具有{...}恰好相同的子字符串。例如,在同一个哈希标记的上下文中定义了以下多键操作:SUNION {user:1000}.foo {user:1000}.bar
  3. 涉及多个密钥的多个密钥操作,事务或Lua脚本与不具有显式或相同的哈希标记的密钥名称一起使用。

第三种情况不由Redis Cluster处理:应用程序需要进行修改,以便不使用多键操作或仅在相同散列标记的上下文中使用它们。

案例1和案例2都有涵盖,因此我们将重点关注这两个案例,这两个案例以相同的方式处理,因此文档中不会有任何区别。

假设您已将预先存在的数据集拆分为N个主服务器,如果您没有预先存在的分片,则N = 1,为了将数据集迁移到Redis集群,需要执行以下步骤:

  1. 阻止你的客户。目前无法自动实时迁移到Redis群集。您可以在应用程序/环境的上下文中编排实时迁移。
  2. 使用BGREWRITEAOF命令为所有N个主服务器生成仅附加文件,并等待完全生成AOF文件。
  3. 将您的AOF文件从aof-1保存到某个地方的aof-N。此时,您可以根据需要停止旧实例(这非常有用,因为在非虚拟化部署中,您经常需要重用相同的计算机)。
  4. 创建由N个主服务器和零从服务器组成的Redis群集。你稍后会添加奴隶。确保所有节点都使用仅附加文件进行持久化。
  5. 停止所有群集节点,将其仅附加文件替换为预先存在的仅附加文件,第一个节点为aof-1,第二个节点为aof-2,最多为aof-N。
  6. 使用新的AOF文件重新启动Redis群集节点。他们会抱怨根据他们的配置,有些密钥不应该存在。
  7. 使用redis-cli --cluster fix命令来修复群集,以便根据每个节点具有权威性的哈希槽来迁移密钥。
  8. 最后使用redis-cli --cluster check以确保您的群集正常。
  9. 重新启动已修改的客户端以使用支持Redis群集的客户端库。

还有一种方法可以将数据从外部实例导入Redis群集,即使用该redis-cli --cluster import命令。

该命令将正在运行的实例的所有密钥(从源实例中删除密钥)移动到指定的预先存在的Redis群集。但请注意,如果您使用Redis 2.8实例作为源实例,操作可能会很慢,因为2.8没有实现迁移连接缓存,因此您可能希望在执行此类操作之前使用Redis 3.x版本重新启动源实例。

 

你可能感兴趣的:(分布式)