使用 Redis 集群进行扩展

使用 Redis 集群进行水平扩展

Redis 堆栈 Redis 社区版

Redis 使用称为 Redis 集群的部署拓扑进行水平扩展。 本主题将教您如何在生产环境中设置、测试和作 Redis Cluster。 您将从最终用户的角度了解 Redis Cluster 的可用性和一致性特征。

如果您计划运行生产 Redis 集群部署,或者想要更好地了解 Redis 集群内部的工作原理,请参阅 Redis 集群规范。要了解 Redis Enterprise 如何处理扩展,请参阅使用 Redis Enterprise 进行线性扩展

Redis 集群 101

Redis 集群提供了一种运行 Redis 安装的方法,其中数据会自动在多个 Redis 节点之间分片。 Redis Cluster 还在分区期间提供了一定程度的可用性 — 实际上,当某些节点发生故障或无法通信时,能够继续运行。 但是,如果发生较大的故障(例如,当大多数 master 不可用时),集群将变得不可用。

因此,使用 Redis Cluster,您可以:

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

Redis 集群 TCP 端口

每个 Redis 集群节点都需要两个开放的 TCP 连接:一个用于为客户端提供服务的 Redis TCP 端口(例如 6379)和第二个端口(称为集群总线端口)。 默认情况下,集群总线端口是通过向数据端口添加 10000 来设置的(例如,16379);但是,您可以在cluster-port配置。

Cluster bus 是一种使用二进制协议的节点到节点的通信通道,由于以下原因,它更适合在节点之间交换信息 带宽和处理时间少。 节点使用集群总线进行故障检测、配置更新、故障转移授权等。 客户端永远不应尝试与集群总线端口通信,而应使用 Redis 命令端口。 但是,请确保在防火墙中打开两个端口,否则 Redis 集群节点将无法通信。

要使 Redis 集群正常工作,您需要为每个节点:

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

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

Redis 集群和 Docker

目前,Redis Cluster 不支持 NAT 环境,通常 重新映射 IP 地址或 TCP 端口的环境。

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

要使 Docker 与 Redis Cluster 兼容,您需要使用 Docker 的主机联网模式。 请参阅--net=host选项了解更多信息。

Redis 集群数据分片

Redis Cluster 不使用一致性哈希,而是使用不同形式的分片 其中每个键在概念上都是我们所说的哈希槽的一部分。

Redis Cluster 中有 16384 个哈希槽,要计算哈希值 slot 中,我们只需取 key 模数的 CRC16 16384.

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

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

这使得添加和删除集群节点变得容易。例如,如果 我想添加新的节点 D,我需要从节点 A、B、C 移动一些哈希槽 到 D。同样,如果我想从集群中删除节点 A,我可以直接 将 A 提供的哈希槽移动到 B 和 C。一旦节点 A 为空, 我可以将其从集群中完全删除。

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

Redis Cluster 支持多个 key作,只要单个命令执行(或整个事务,或 Lua 脚本)中涉及的所有 key execution)属于同一个哈希槽。用户可以强制使用多个键 成为同一哈希槽的一部分。

哈希标签记录在 Redis 集群规范中,但要点是 如果键中的 {} 括号之间有子字符串,则只有 字符串内部经过哈希处理。例如,键user:{123}:profileuser:{123}:account保证位于同一哈希槽中,因为它们共享相同的哈希标签。因此,您可以在同一个多键作中对这两个键进行作。

Redis Cluster 主副本模型

在主节点子集发生故障或发生故障时保持可用 无法与大多数节点通信,Redis 集群使用 master-replica 模型,其中每个哈希槽都有从 1(主节点本身)到 N 副本(N-1 个额外的副本节点)。

在我们的示例集群中,如果节点 A、B、C 失败,则集群不会失败 能够继续,因为我们不再有办法在 范围 5501-11000。

但是,在创建集群时(或稍后创建),我们会添加一个副本 node 分配给每个 master,这样最终的集群就由 A、B、C 组成 ,以及 A1、B1、C1 等副本节点。 这样,如果节点 B 发生故障,系统可以继续。

节点 B1 复制 B,而 B 发生故障,集群会将节点 B1 提升为新的 master 并继续正常运行。

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

Redis 集群一致性保证

Redis Cluster 不保证强一致性。在实践中 这意味着在某些情况下,Redis 可能会 Cluster 将丢失系统向客户端确认的写入。

Redis Cluster 丢失写入的第一个原因是它使用 异步复制。这意味着在写入以下内容 发生:

  • 您的 Client 端写入主服务器 B。
  • 主 B 对您的客户回复 OK。
  • 主服务器 B 将写入传播到其副本 B1、B2 和 B3。

如您所见,B 之前不会等待 B1、B2、B3 的确认 回复客户端,因为这将是一个令人望而却步的延迟损失 对于 Redis,因此如果您的客户端写入了某些内容,B 会确认写入, 但在能够将写入发送到其副本之前崩溃,其中一个 副本(未收到写入)可以提升为主副本,从而丢失 永远写。

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

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

Redis Cluster 在绝对需要时支持同步写入, 通过WAIT命令。这使得丢失写入的时间大大减少 可能。但是,请注意,Redis Cluster 不实现强一致性 即使使用同步复制:在更多 复杂的故障场景,即无法接收写入的副本 将被选为主服务器。

还有另一种值得注意的情况,即 Redis Cluster 将丢失写入作,即 发生在客户端与少数 的实例中至少包含一个 Master。

以我们的 6 个节点集群为例,由 A、B、C、A1、B1、C1 组成, 具有 3 个主服务器和 3 个副本。还有一个客户端,我们将其称为 Z1。

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

Z1 仍然能够写入 B,B 将接受其写入。如果 partition 在很短的时间内修复,集群将正常继续。 但是,如果分区持续的时间足够,以便将 B1 提升为主 在分区的多数侧,Z1 发送到 B 的写入 在此期间,将会丢失。

注意:
Z1 将能够的写入量有一个最大窗口 发送到 B:如果 partition 选择一个副本作为 master,每个 master 节点都处于少数状态 side 将停止接受写入。

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

节点超时过后,主节点被视为失败, ,并且可以替换为其副本之一。 同样,在节点超时后,没有主节点能够 为了感知大多数其他 Master 节点,它会进入 Error 状态 并停止接受写入。

Redis 集群配置参数

我们即将创建一个示例集群部署。 在继续之前,我们先介绍 Redis Cluster 引入的配置参数 在redis.conf文件。

  • 已启用集群<yes/no>:如果是,则在特定 Redis 实例中启用 Redis 集群支持。否则,实例将照常作为独立实例启动。
  • 集群配置文件<filename>:请注意,尽管此选项的名称如此,但这不是用户可编辑的配置文件,而是 Redis 集群节点在每次发生更改时自动保留集群配置(基本上是状态)的文件,以便能够在启动时重新读取它。该文件列出了集群中的其他节点、它们的状态、持久变量等内容。通常,由于某些消息接收,此文件会在磁盘上被重写和刷新。
  • 集群节点超时<milliseconds>:Redis 集群节点在不被视为失败的情况下不可用的最长时间。如果主节点在超过指定时间内无法访问,则其副本将对其进行故障转移。此参数控制 Redis Cluster 中的其他重要内容。值得注意的是,在指定时间内无法访问大多数主节点的每个节点都将停止接受查询。
  • cluster-slave-validity-factor (集群从属有效性因子)<factor>:如果设置为零,则副本将始终认为自身有效,因此将始终尝试故障转移主服务器,而不管主服务器和副本服务器之间的链接保持断开连接的时间长短。如果该值为正数,则最大断开连接时间的计算方法是节点超时值乘以此选项提供的系数,如果节点是副本,则当主链路断开连接的时间超过指定时间时,它不会尝试启动故障转移。例如,如果节点超时设置为 5 秒,有效性因子设置为 10,则与 master 断开连接超过 50 秒的副本将不会尝试故障转移其 master。请注意,如果没有能够对其进行故障转移的副本,则任何不为零的值都可能导致 Redis 集群在主节点发生故障后不可用。在这种情况下,只有当原始 master 重新加入集群时,集群才会恢复可用。
  • 集群迁移屏障<count>:主服务器将保持连接的最小副本数,以便另一个副本迁移到不再被任何副本覆盖的主服务器。有关更多信息,请参阅本教程中有关副本迁移的相应部分。
  • cluster-require-full-coverage 集群需要完全覆盖<yes/no>:如果将此设置为 yes(默认情况下),则当任何节点未覆盖一定百分比的密钥空间时,集群将停止接受写入。如果该选项设置为 no,则集群仍将提供查询,即使只能处理有关键子集的请求。
  • cluster-allow-reads-when -down (集群允许读取停机时)<yes/no>:如果将其设置为 no,则默认情况下,当集群被标记为失败时,Redis 集群中的节点将停止提供所有流量,无论是当节点无法达到主节点的仲裁数量还是未达到完全覆盖时。这可以防止从不知道集群中更改的节点读取可能不一致的数据。此选项可以设置为 yes 以允许在 fail 状态期间从节点进行读取,这对于希望优先考虑读取可用性但仍希望防止不一致写入的应用程序非常有用。当使用只有一个或两个分片的 Redis Cluster 时,也可以使用它,因为它允许节点在 master 发生故障但无法自动故障转移时继续提供写入服务。

创建和使用 Redis 集群

要创建和使用 Redis 集群,请执行以下步骤:

但是,首先,请熟悉创建集群的要求。

创建 Redis 集群的要求

要创建集群,您首先需要的是让一些空的 Redis 实例在集群模式下运行。

至少在redis.conf文件:

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

要启用集群模式,请将cluster-enabled指令设置为yes. 每个实例还包含一个文件的路径,其中 此节点的配置已存储,默认情况下为nodes.conf. 此文件永远不会被人类接触;它只是在启动时生成 通过 Redis Cluster 实例,并在每次需要时更新。

请注意,按预期工作的最小集群必须包含 至少 3 个主节点。对于部署,我们强烈建议 一个六节点集群,具有 3 个主节点和 3 个副本节点。

您可以通过创建以下名为 在实例的端口号之后,您将在任何给定目录中运行。

例如:

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

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

您可以按如下方式启动每个实例,每个实例都在单独的终端选项卡中运行:

cd 7000
redis-server ./redis.conf

从日志中可以看到,每个节点都会为自己分配一个新 ID:

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

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

创建 Redis 集群

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

您可以手动配置和执行单个实例,也可以使用 create-cluster 脚本。 让我们回顾一下如何手动执行此作。

要创建集群,请运行:

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

此处使用的命令是 create,因为我们要创建一个新集群。 选项--cluster-replicas 1意味着我们希望为每个创建的 master 创建一个副本。

其他参数是我想要使用的实例的地址列表 以创建新集群。

redis-cli将提出一个配置。通过键入 yes 接受建议的配置。 集群将被配置并加入,这意味着实例将被 自力更生地开始彼此交谈。最后,如果一切顺利,您将看到如下消息:

[OK] All 16384 slots covered

这意味着至少有一个 master 实例为每个 16384 个可用插槽。

如果您不想通过配置和执行来创建 Redis 集群 如上所述,单个实例手动作,则有一个更简单的 系统(但您不会了解相同数量的作细节)。

查找utils/create-cluster目录中。 有一个名为create-clusterinside (与目录同名 它包含在 Bash 中),它是一个简单的 bash 脚本。为了开始 具有 3 个主节点和 3 个副本的 6 节点集群只需键入以下内容 命令:

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

回复yes在步骤 2 中,当redis-cli公用事业公司希望您接受 群集布局。

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

  1. create-cluster stop

请阅读README在此目录中了解有关如何作的更多信息 以运行脚本。

与集群交互

要连接到 Redis 集群,您需要一个集群感知型 Redis 客户端。 请参阅所选客户端的文档以确定其集群支持。

您还可以使用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:7002> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"
注意:
如果您使用该脚本创建了集群,则您的节点可能会侦听 在不同的端口上,默认从 30001 开始。

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

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

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

通过这种方式,我们可以运行一个示例,同时尝试创建节点 失败或启动重新分片,以查看 Redis Cluster 在实际 世界条件。看到没有人时会发生什么并不是很有帮助 正在写入群集。

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

   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  }

该应用程序执行了一件非常简单的事情,它在foo<number>number,一个接一个。因此,如果您运行该程序,则结果是 以下命令流:

  • 设置 foo0 0
  • 设置 foo1 1
  • 设置 foo2 2
  • 等等......

该程序看起来比通常应该的要复杂,因为它的设计目的是 显示错误,而不是退出并显示异常,因此每个 对集群执行的作由begin rescue块。

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

启动节点不需要是集群的所有节点。重要的 问题是至少有一个节点是可访问的。另请注意,redis-rb-cluster 一旦能够与 第一个节点。您应该预料到任何其他认真的客户都会有这样的行为。

现在,我们已经将 Redis Cluster 对象实例存储在 rc 变量中, 我们已准备好使用该对象,就像它是普通的 Redis 对象实例一样。

这正是第 18 行到第 26 行中发生的情况:当我们重新启动示例 我们不想重新开始foo0,所以我们将 counter 存储在里面 Redis 本身。上面的代码旨在读取此计数器,或者如果 counter 不存在,则为其分配值 0。

但是请注意它是一个 while 循环,因为我们想一次又一次地尝试 如果集群已关闭并返回错误。普通应用程序不需要 要小心。

28 和 37 之间的行开始设置键的主循环,或者 将显示错误。

请注意sleep调用。在测试中,您可以删除 sleep (如果要尽快写入集群)(相对 当然,这是一个没有真正并行性的 busy 循环,所以 在最佳条件下,您将获得通常 10k ops/second 的 10k ops/s)。

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

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

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 个哈希槽进行重新分片,这些哈希槽应该已经包含一个非 少量的键,如果示例在没有 sleep 的情况下仍在运行 叫。

那么 redis-cli 需要知道重新分片的目标是什么,也就是 将接收哈希槽的节点。 我将使用第一个主节点,即 127.0.0.1:7000,但我需要 ,指定实例的 Node ID。这已经打印在 list 的 URL 中,但我总是可以找到具有以下 ID 的节点 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 --cluster reshard <host>:<port> --cluster-from <node-id> --cluster-to <node-id> --cluster-slots <number of slots> --cluster-yes

如果您可能经常重新分片,这允许构建一些自动化, 但是目前没有办法redis-cli自动 重新平衡集群,检查密钥在集群中的分布 节点,并根据需要智能移动插槽。将添加此功能 在未来。

--cluster-yes选项指示集群管理器自动应答 “yes” 添加到命令的提示符中,允许它在非交互模式下运行。 请注意,也可以通过设置REDISCLI_CLUSTER_YES环境变量。

一个更有趣的示例应用程序

我们早期编写的示例应用程序不是很好。 它以一种简单的方式写入集群,甚至不需要检查什么 书面是正确的事情。

从我们的角度来看,接收写入的集群总是可以 写入密钥foo42到每一次作,我们不会注意到 都。

因此,在redis-rb-cluster存储库中,还有一个更有趣的应用程序 称为consistency-test.rb.它使用一组计数器(默认为 1000),并发送INCR命令以增加计数器。

但是,应用程序不仅仅是编写,还会执行另外两项作:

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

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

运行 consistency-test 应用程序会每 第二:

$ 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) |

该线显示执行的 Reads 和 W仪式的数量,而 错误数(由于系统被 不可用)。

如果发现一些不一致,则会将新行添加到输出中。 例如,如果我在 程序正在运行:

$ 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 集群故障转移。

测试故障转移

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

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

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

$ 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 集群在故障转移期间可能会丢失写入,因为它使用异步 复制。我们没有说的是,这种情况不太可能发生 ,因为 Redis 将回复发送到客户端,并将要复制的命令 到副本中,大约同时,所以有一个非常小的窗口来 丢失数据。然而,它难以触发的事实并不意味着它 是不可能的,因此这不会更改提供的一致性保证 由 Redis 集群。

现在,我们可以检查故障转移后的集群设置是什么(请注意, 同时,我重新启动了崩溃的实例,以便它重新加入 cluster 作为副本):

$ 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 NODEScommand 可能看起来令人生畏,但实际上非常简单,并且由以下标记组成:

  • 节点 ID
  • IP:端口
  • 标志: master, replica, myself, fail, ...
  • 如果是副本,则为 master 的 Node ID
  • 仍在等待回复的最后一个待处理 PING 的时间。
  • 上次收到 PONG 的时间。
  • 此节点的配置纪元(请参阅 Cluster 规范)。
  • 指向此节点的链接的状态。
  • 已投放的插槽...

手动故障转移

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

Redis Cluster 支持手动故障转移,使用CLUSTER FAILOVER命令,该命令必须在您想要的 master 的副本之一中执行 进行故障转移。

手动故障转移很特殊,与以下原因导致的故障转移相比更安全 实际主服务器故障。它们以一种避免 进程,仅将客户端从原始 Master 切换到新的 Master 当系统确定新的 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.

基本上,连接到我们正在故障转移的 master 的 Client 端会停止。 同时,master 将其复制偏移量发送到副本,该 等待到达其一侧的偏移量。当达到复制偏移量时, 故障转移开始,旧主服务器将收到有关配置的通知 开关。当客户端在旧主服务器上被取消阻止时,它们将被重定向 到新的主人。

注意:
要将副本提升为主服务器,必须首先被集群中的大多数主服务器称为副本。 否则,它将无法赢得故障转移选举。 如果副本刚刚添加到集群中(请参阅将新节点添加为副本),则可能需要等待一段时间,然后再发送CLUSTER FAILOVER命令,以确保集群中的 master 知道新的副本。

添加新节点

添加新节点基本上是添加一个空节点,然后 将一些数据移动到其中,以防它是新的 master,或者告诉它 setup 作为已知节点的副本(如果它是副本)。

我们将同时展示两者,首先添加新的主实例。

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

这就像在端口 7006 中启动一个新节点一样简单(我们已经使用了 对于我们现有的 6 个节点,从 7000 到 7005),具有相同的配置 用于除端口号以外的其他节点,因此您应该做什么 do 以符合我们对前面节点使用的设置:

  • 在终端应用程序中创建新选项卡。
  • 输入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 命令,指定了 new 节点作为第一个参数,并且 cluster 作为第二个参数。

实际上,这里的 redis-cli 对我们帮助很小,它只是 发送了CLUSTER MEETmessage 传递给节点,这也是可能的 以手动完成。但是,redis-cli 还会检查 cluster 之前进行作,因此最好执行 cluster作 始终通过 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

请注意,由于此节点已连接到集群,因此它已经 能够正确重定向客户端查询,并且通常说是 集群。但是,与其他母版相比,它有两个特点:

  • 它不保存任何数据,因为它没有分配的哈希槽。
  • 因为它是没有分配槽的主节点,所以当副本想要成为主节点时,它不会参与选举过程。

现在,可以使用重新分片将哈希槽分配给此节点 的特点redis-cli. 像我们已经展示的那样,基本上是没有用的 did 在上一节中,没有区别,只是重新分片 将空节点作为目标。

将新节点添加为副本

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

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

请注意,这里的命令行与我们之前添加的命令行完全相同 一个新的 master,所以我们没有指定要添加到哪个 master 副本。在这种情况下,redis-cli 会将新的 node 作为副本较少的 Master 中随机 Master 的副本。

但是,您可以准确指定要将 new replica 替换为以下命令行:

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

这样,我们将新副本分配给特定的 master。

将副本添加到特定 master 的更手动的方法是添加新的 node 作为空 master 节点,然后使用CLUSTER REPLICATE命令。如果节点已添加为副本,这也适用 但您希望将其作为其他 master 的副本移动。

例如,为了为节点 127.0.0.1:7005 添加副本,即 当前提供范围为 11423-16383 的哈希槽,其节点 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 `<node-id>`

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

您也可以以相同的方式删除主节点,但是为了 删除 Master 节点,它必须为空。如果 master 不为空,则需要 将数据从它重新分片到之前的所有其他主节点。

删除主节点的另一种方法是对其执行手动故障转移 在其其中一个副本上,并在该节点变成 新主。显然,当您想减少实际 集群中的 master 数量,在这种情况下,需要重新分片。

在一种特殊情况下,您想要删除失败的节点。 您不应使用del-node命令,因为它会尝试连接到所有节点,您将遇到 “connection refused” 错误。 相反,您可以使用call命令:

redis-cli --cluster call 127.0.0.1:7000 cluster forget `<node-id>`

此命令将执行CLUSTER FORGET命令。

副本迁移

在 Redis 集群中,您可以重新配置副本以使用 不同的 master 随时只需使用此命令:

CLUSTER REPLICATE <master-node-id>

但是,在一种特殊情况下,您希望副本从一个 master 自动切换到另一个 MASTER,无需系统管理员的帮助。 副本的自动重新配置称为副本迁移,它是 能够提高 Redis 集群的可靠性。

注意:
您可以在 Redis 集群规范中阅读副本迁移的详细信息,这里我们只提供有关 一般想法以及您应该做什么才能从中受益。

您可能希望让集群副本从一个 master 移动的原因 对于另一个人来说,通常是 Redis 集群是 抗故障能力作为附加到给定 master 的副本数。

例如,每个 master 都有一个副本的集群无法继续 作,如果 Master 及其 Replica 同时失败,则仅仅是因为 没有其他实例可以拥有 master 的哈希槽的副本 服务。然而,虽然网络分裂可能会隔离许多节点 同时,还有许多其他类型的故障,例如硬件或软件故障 本地的故障是一类非常值得注意的故障,这些故障不太可能发生 同时发生,因此有可能在集群中 每个 Master 都有一个副本,副本在凌晨 4 点被杀死,Master 被杀死 早上 6 点。这仍然会导致集群无法再运行。

为了提高系统的可靠性,我们可以选择添加额外的 副本,但这很昂贵。副本迁移允许 将更多副本添加到几个 Master。所以你有 10 个 master 和 1 个副本 每个实例,总共 20 个实例。但是,例如,您添加 3 个实例 更多作为你的一些 master 的复制品,所以某些 master 会有更多 而不是单个副本。

对于副本迁移,如果 master 没有 replicas 中,来自具有多个 replica 的 master 的副本将迁移到 孤儿大师。因此,在副本在凌晨 4 点宕机后,如示例中所示 我们在上面制作了,另一个复制品将取代它,当 master 也会失败,则仍然有一个副本可以被选中,以便 集群可以继续运行。

那么,简而言之,您应该了解哪些副本迁移呢?

  • 集群将尝试从给定时刻具有最大副本数的主服务器迁移副本。
  • 要从副本迁移中受益,您只需将更多副本添加到集群中的单个 master 中,什么 master 并不重要。
  • 有一个配置参数控制副本迁移功能,称为cluster-migration-barrier:您可以在示例中阅读更多相关信息redis.conf文件。

升级 Redis 集群中的节点

升级副本节点很容易,因为您只需停止节点并重新启动 它与 Redis 的更新版本一起使用。如果有客户端使用 副本节点,它们应该能够重新连接到不同的副本(如果给定的 一个不可用。

升级 master 稍微复杂一些,建议的程序是:

  1. CLUSTER FAILOVER触发 Master 到其副本之一的手动故障转移。 (请参阅本主题中的手动故障转移
  2. 等待 master 变为副本。
  3. 最后,像升级副本一样升级节点。
  4. 如果您希望 master 成为您刚刚升级的节点,请触发新的手动故障转移,以便将升级后的节点恢复为主节点。

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

迁移到 Redis 集群

愿意迁移到 Redis Cluster 的用户可能只有一个 Master,或者 可能已经在使用预先存在的分片设置,其中 keys 在 N 个节点之间拆分,使用一些内部算法或分片算法 由他们的客户端库或 Redis 代理实现。

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

  1. 不使用多个密钥作、交易或涉及多个密钥的 Lua 脚本。键是独立访问的(即使通过事务或 Lua 脚本将多个命令(大约相同的键)组合在一起访问)。
  2. 使用涉及多个 key 的多个 key作、交易或 Lua 脚本,但只能使用具有相同 hash 标签的 key,这意味着一起使用的 key 都有一个{...}子字符串。例如,以下多个键作是在同一哈希标记的上下文中定义的:SUNION {user:1000}.foo {user:1000}.bar.
  3. 多密钥作、交易或涉及多个密钥的 Lua 脚本与没有显式或相同哈希标签的密钥名称一起使用。

第三种情况不是由 Redis Cluster 处理的:应用程序需要 被修改以不使用多键作或仅在 同一 hash 标签的上下文。

本文介绍了案例 1 和 2,因此我们将重点介绍已处理的两种案例 以同样的方式,因此在文档中不会进行区分。

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

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

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

该命令将正在运行的实例的所有键(从 源实例)添加到指定的预先存在的 Redis 集群中。然而 注意,如果您使用 Redis 2.8 实例作为源实例,则作 可能会很慢,因为 2.8 没有实现 migrate 连接缓存,所以你 可能需要重启 Redis 3.x 版本之前的源实例 以执行此作。

注意:
从 Redis 5 开始,如果不是为了向后兼容,Redis 项目不再使用 slave 这个词。不幸的是,在这个命令中,slave 这个词是协议的一部分,所以只有当这个 API 被自然弃用时,我们才能删除此类事件。

了解更多信息