目录
2.5.Redis 集群主从模型(master-slave)
4.2.使用 create-Cluster 脚本创建 Redis 群集
4.4.使用 redis-rb-cluster 编写示例应用程序
1.前提说明
Redis 集群教程: Redis 集群的简介和安装指南。
本文基于Redis 6.0.9版本,前提至少 Redis 3.0或更高版本。
本文是对 Redis 集群的简单介绍,它不使用难以理解的分布式系统概念。它提供了关于如何设置集群、测试和操作集群的说明,没有涉及 Redis 集群规范中所涵盖的细节,只是从用户的角度描述了系统的行为。
然而,本教程试图从最终用户的角度提供关于 Redis 集群的可用性和一致性特征的信息,以一种简单易懂的方式说明。
如果您计划运行一个严肃的 Redis 集群部署,建议使用更多的 Redis 集群形式规范,即使不是严格要求。 不过,最好从这个文档开始,稍后再使用 Redis 集群,稍后再阅读规范。
2.简介
2.1.Redis Cluster 101
Redis Cluster 提供了一种运行 Redis 安装的方法,在这种安装中,数据将自动分片到多个 Redis 节点上。
Redis Cluster 还在分区期间提供一定程度的可用性,即在某些节点失败或无法通信时继续操作的能力。但是,在发生更大的故障时(例如,当大多数主节点不可用时) ,集群将停止运行。
那么,实际上,你从Redis 集群中得到了什么?
- 在多个节点之间自动分割数据集的能力
- 当节点子集出现故障或无法与集群的其余部分通信时,继续操作的能力
2.2.Redis 集群 TCP 端口
每个 Redis 集群节点都需要打开两个 TCP 连接。普通的 Redis TCP 端口用于服务客户端,例如6379,加上通过向数据端口添加10000所获得的端口,即示例中的16379。
第二个高端口用于集群总线,即使用二进制协议的节点到节点通信通道。集群总线被节点用于故障检测、配置更新、故障转移授权等等。客户端永远不应该尝试与集群总线端口通信,但总是使用普通的 Redis 命令端口,但是要确保在防火墙中打开两个端口,否则 Redis 集群节点将无法通信。
命令端口和集群总线端口偏移量是固定的,始终为10000。
请注意,要使 Redis 集群正常工作,您需要为每个节点:
- 普通客户端通信端口(通常为6379)用于与客户端通信,以便向需要到达集群的所有客户端以及所有其他集群节点开放(使用客户端端口进行键迁移)
- 群集总线端口(客户端端口+ 10000)必须可以从所有其他群集节点访问。
如果不同时打开两个 TCP 端口,集群将无法正常工作。
集群总线采用一种不同的二进制协议进行节点间的数据交换,这种协议更适合节点间的信息交换,而且带宽和处理时间都很少。
2.3.Redis Cluster 和 Docker
目前 Redis 集群不支持 NATted 环境,并且在 IP 地址或 TCP 端口被重新映射的一般环境中。
使用了一种称为端口映射的技术: 在 Docker 容器中运行的程序可能会使用与程序认为使用的不同的端口。这对于在同一服务器中同时使用相同的端口运行多个容器非常有用。
为了使 Docker 与 Redis 集群兼容,您需要使用 Docker 的主节点网络模式。有关更多信息,请查看Docker 文档中的 --net=host 选项。
2.4.Redis 集群数据分片
集群没有使用一致哈希分片,而是一种不同形式的分片,其中每个键在概念上都是我们称之为散列槽的一部分。
Redis集群中有16384个哈希槽,要计算给定键的哈希槽,我们只需对键的CRC16取模16384。
集群中的每个节点负责哈希插槽的子集,例如,你可能有一个集群有3个节点,其中:
- 节点 a 包含0到5500的哈希插槽
- 节点 b 包含从5501到11000的哈希槽
- 节点 c 包含从11001到16383的哈希插槽
这允许在集群中轻松地添加和移除节点。例如,如果我想添加一个新的节点 d,我需要将一些散列槽复制节点 a,b,c 移动到 d。当节点 a 为空时,我可以将其完全从集群中删除。
因为将散列槽从一个节点移动到另一个节点不需要停止操作,所以添加和移除节点或更改节点持有的散列槽的百分比不需要任何停机。
Redis Cluster 支持多个键操作,只要单个命令执行(或整个事务,或 Lua 脚本执行)中涉及的所有键都属于同一个散列槽。用户可以使用称为散列标记的概念强制多个键成为同一散列槽的一部分。
哈希标签在 Redis 集群规范中有文档记录,但是要点是,如果在一个键的{}括号中有一个子字符串,那么只有字符串中的内容被哈希化,例如this{foo}key和another{foo}key保证在同一个哈希槽中,并且可以在一个命令中一起使用多个键作为参数。
2.5.Redis 集群主从模型(master-slave)
为了在主节点子集发生故障或无法与大多数节点通信时保持可用性,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 群集将无法继续运行。
2.6.Redis 集群一致性保证
Redis 集群不能保证强一致性。实际上,这意味着在某些条件下,Redis Cluster 可能会丢失系统向客户端承认的写操作。
Redis Cluster 可能丢失写的第一个原因是它使用了异步复制。这意味着在写的过程中会发生以下情况:
- 客户端写给主节点B。
- 主节点B 向客户端答复 “OK”。
- 主节点B将写操作传播到其复制节点B1,B2和B3。
正如你所看到的,B在回复客户端之前不会等待B1,B2,B3的确认,因为这会对Redis造成极高的延迟影响,因此,如果您的客户端写了一些东西,B会确认写,但是在崩溃之前崩溃,由于能够将写操作发送到其复制节点设备,因此一个复制节点设备(未接收到写操作)可以升级为主节点设备,从而永远丢失写操作
这非常类似于大多数配置为每秒将数据刷新到磁盘的数据库所发生的情况,所以这是一个你已经能够推理的场景,因为传统数据库系统过去的经验不涉及分布式系统。类似地,您可以通过强制数据库在回答客户端之前将数据刷新到磁盘来提高一致性,但这通常会导致极低的性能。这相当于 Redis 集群中的同步复制。
基本上,需要在性能和一致性之间进行权衡。
Redis 集群支持在绝对需要时进行同步写操作,通过 WAIT 命令实现。这使得写作失败的可能性大大降低。然而,请注意,即使在使用同步复制时,Redis Cluster 也不能实现强一致性: 在更复杂的故障情况下,总是有可能选择不能接收写操作的复制节点作为主节点。
还有另一个值得注意的场景,Redis Cluster 将丢失写操作,这种情况发生在网络分区中,客户端被隔离,少数实例至少包括一个主节点。
以我们的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 的一个非常重要的配置指令,称为节点超时(node timeout)。
节点超时后,主节点将被认为是失败的,并且可以被其中的一个复制节点替换。类似地,当节点超时时间过去后,主节点无法感知大多数其他主节点,它将进入错误状态并停止接受写操作。
3.Redis 集群配置参数
我们将创建一个示例集群部署。在继续之前,让我们介绍 Redis Cluster 在 Redis.conf 文件中引入的配置参数。有些是显而易见的,有些则会随着你的继续阅读而变得更加清晰。
- cluster-enabled
<yes/no>
- 如果
yes
,则在特定的 Redis 实例中启用 Redis 集群支持。否则,该实例通常作为独立实例启动
- 如果
- cluster-config-file
<filename>
- 请注意,尽管这个选项的名称是这样的,但它不是一个用户可编辑的配置文件,而是一个 Redis Cluster 节点在每次发生变化时自动保存集群配置(基本上是状态)的文件,以便在启动时能够重新读取。该文件列出了集群中的其他节点、它们的状态、持久变量等等。通常,由于某些消息接收,这个文件会被重写并刷新到磁盘上
- cluster-node-timeout
<milliseconds>
- Redis Cluster 节点可以不可用的最大时间量,而不将其视为失败。如果主节点在超过指定的时间段内无法访问,则其复制节点将对其进行故障转移。此参数控制 Redis 集群中的其他重要事项。值得注意的是,每个在指定时间内无法到达大多数主节点的节点都将停止接受查询
- cluster-slave-validity-factor
<factor>
- 如果设置为零,则复制节点将始终认为自己有效,因此始终尝试故障转移主节点,而不管主节点和复制节点之间的链接保持断开的时间有多长。如果该值为正数,则计算最大断开时间,将节点超时值乘以该选项提供的因子,如果节点是复制节点,则如果主链接断开的时间超过指定的时间量,则不会尝试启动故障转移。例如,如果节点超时设置为5秒,有效性因子设置为10,从主节点断开连接超过50秒的复制节点将不会尝试故障转移其主节点。请注意,如果没有复制节点设备能够故障转移,任何不同于零的值都可能导致 Redis Cluster 在主节点设备故障后不可用。在这种情况下,只有当原始主节点重新加入集群时,集群才会恢复可用
- cluster-migration-barrier
<count>
- 主节点将保持连接的最小数量的复制节点,以便另一个复制节点服务迁移到不再被任何复制节点服务覆盖的主节点。有关更多信息,请参见本教程中关于复制节点迁移的适当部分
- cluster-require-full-coverage
<yes/no>
- 如果将其设置为 yes,因为缺省情况下,如果任何节点没有覆盖某个百分比的键空间,集群将停止接受写操作。如果将该选项设置为 no,即使只能处理关于键子集的请求,集群仍将提供查询
- cluster-allow-reads-when-down
<yes/no>
- 如果将其设置为 no,因为缺省情况下,当集群被标记为失败时,无论是节点达不到 quorum 或是没有满足完全覆盖时,Redis 集群中的节点将停止为所有通信服务。这可以防止从不知道集群中的更改的节点读取可能不一致的数据。可以将此选项设置为 yes,以允许在故障状态期间复制节点读取,这对于希望优先考虑读取可用性但仍希望防止写入不一致的应用程序非常有用。当使用只有一个或两个分片的 Redis 集群时,也可以使用它,因为它允许节点在主节点失败但无法自动故障转移时继续执行写操作
4.创建和使用 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
正如您所看到的,启用集群模式的只是启用了集群的指令。每个实例还包含存储该节点配置的文件的路径,默认情况下是 nodes.conf。它只是在启动时由 Redis 集群实例生成,并在每次需要时更新。
请注意,正常工作的最小集群要求至少包含三个主节点。对于您的第一个测试,强烈建议启动一个包含三个主节点和三个复制节点的六个节点集群。
为此,请输入一个新目录,并创建以实例的端口号命名的下列目录,我们将在任何给定的目录中运行这些目录。
比如:
mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005
在每个目录中创建一个 redis.conf 文件,从7000到7005。作为配置文件的模板,只需使用上面的小例子,但请确保根据目录名用正确的端口号替换端口号7000。
现在将 redis-server 可执行文件(从GitHub 不稳定分支中的最新源代码编译而成)复制到集群测试目录中,最后在您喜欢的终端应用程序中打开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 地址和端口可能会改变,但是在节点的整个生命周期中,唯一的节点标识符永远不会改变。我们简单地称这个标识符为节点 ID。
4.1.创建集群
现在我们已经有了一个运行的数量集群,我们需要通过向节点写入一些有意义的配置来创建我们的集群。
如果您使用的是 Redis 5或更高版本,那么完成这个任务非常容易,因为我们可以通过嵌入到 Redis-cli 中的 Redis Cluster 命令行实用程序来完成,这个程序可以用于创建新的集群、检查或重新分割现有集群等等。
对于Redis版本3或4,有一个称为redis-trib.rb的旧工具,它非常相似。 您可以在Redis源代码分发的src目录中找到它。 您需要安装redis gem才能运行redis-trib。
gem install redis
第一个示例,即集群创建,将在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-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 4或3类型使用 Redis-trib. rb:
./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个可用插槽中的每一个提供服务。
4.2.使用 create-Cluster 脚本创建 Redis 群集
如果您不想像上面解释的那样通过手动配置和执行单个实例来创建一个 Redis 集群,那么还有一个更简单的系统(但是您不会了解相同数量的操作细节)。
只需检查 Redis 发行版中的 utils/create-cluster 目录。内部有一个名为 create-cluster 的脚本(与包含它的目录同名) ,它是一个简单的 bash 脚本。为了启动一个包含3个主节点和3个复制节点的6个节点集群,只需输入以下命令:
create-cluster start
create-cluster create
在步骤2中,当 redis-cli 实用程序希望您接受集群布局时,回复 yes。
您现在可以与集群进行交互,第一个节点默认从端口30001开始。完成后,使用以下命令停止集群:
create-cluster stop
有关如何运行脚本的更多信息,请阅读此目录中的 README。
4.3.与集群玩耍
在此阶段,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。
- ioredis是流行的Node.js客户端,为Redis Cluster提供了强大的支持。
- 当使用-c开关启动时,redis-cli实用程序将实现基本的集群支持。
测试 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节点能够将客户端重定向到正确节点的事实。 认真的客户可以做得更好,并且可以在哈希槽和节点地址之间缓存映射,以直接使用与正确节点的正确连接。 仅在集群配置中发生某些更改时(例如,在故障转移之后或系统管理员通过添加或删除节点来更改集群布局之后),才刷新映射。
4.4.使用 redis-rb-cluster 编写示例应用程序
在展示如何操作 Redis 集群、进行故障转移或重新分片之前,我们需要创建一些示例应用程序,或者至少能够理解简单的 Redis 集群客户端交互的语义。
通过这种方式,我们可以运行一个示例,同时尝试让节点失败,或者启动一个重新分片,以了解在现实世界条件下 Redis 集群的行为。如果没有人写入集群,那么查看发生了什么并没有多大帮助。
本节通过两个示例说明了 redis-rb-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>形式的键设置为数字,一个接一个。 因此,如果您运行该程序,结果将是以下命令流:
- SET foo0 0
- SET foo1 1
- SET foo2 2
- And so forth...
该程序看起来比通常应有的复杂,因为它被设计为在屏幕上显示错误而不是异常退出,因此,对群集执行的每个操作都由begin
rescue
块包装。
第14行是程序中第一个有趣的行。它创建 Redis Cluster 对象,使用启动节点列表作为参数,该对象允许针对不同节点进行的最大连接数,最后给定的操作被认为失败后的超时。
启动节点不必是集群的所有节点。 重要的是至少一个节点是可到达的。 还要注意,只要能够与第一个节点连接,redis-rb-cluster就会更新此启动节点列表。 您应该期望任何其他认真的客户都采取这种行为。
现在我们已经将 Redis Cluster 对象实例存储在 rc 变量中,我们可以使用这个对象了,就像它是一个普通的 Redis 对象实例一样。
这正是第18行到第26行中所发生的情况: 当我们重新启动示例时,我们不希望用 foo0重新启动,因此我们将计数器存储在 Redis 本身中。上面的代码设计用于读取此计数器,或者如果该计数器不存在,则将其赋值为零。
但是请注意它是一个 while 循环,因为即使集群关闭并返回错误,我们也要一次又一次地尝试。普通的应用程序不需要如此小心。
在28和37之间的行开始设置键或显示错误的主节点循环。
请注意循环结束时的 sleep 调用。在您的测试中,如果您想要尽可能快地写入集群,您可以删除休眠(相对于这是一个繁忙的循环,当然没有真正的并行性,所以在最好的条件下,您通常会得到10k 的 ops/秒)。
通常写入速度会放慢,以便示例应用程序更容易被人类跟踪。
启动应用程序将生成以下输出:
ruby ./example.rb
1
2
3
4
5
6
7
8
9
^C (I stopped the program here)
这不是一个非常有趣的程序,我们稍后会使用一个更好的程序,但是我们已经可以看到当程序运行时分片期间发生了什么。
4.5.重新分片集群
现在我们准备尝试集群重新分片。要做到这一点,请保持 example.rb 程序运行,以便您可以查看是否对程序的运行有一些影响。此外,您可能希望对休眠调用进行注释,以便在重新分片期间进行更严重的写加载。
重新分片基本上意味着将散列槽从一组节点移动到另一组节点,与集群创建一样,它是使用 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.0.1:7000的主节点插槽会有更多的插槽,大约是6461
4.6.编写重新分片操作的脚本
重新分片可以自动执行,而不需要以交互方式手动输入参数。可以使用如下命令行:
redis-cli --cluster reshard <host>:<port> --cluster-from <node-id> --cluster-to <node-id> --cluster-slots <number of slots> --cluster-yes
如果您可能经常重新分片,那么这允许构建一些自动化,但是目前 redis-cli 没有办法自动重新平衡集群,检查跨集群节点的键分布,并根据需要智能地移动插槽。这个功能将在未来添加。
4.7.一个更有趣的示例应用程序
我们早期编写的示例应用程序不是很好。它以一种简单的方式写入集群,甚至不检查所写内容是否正确。
从我们的角度来看,接收写操作的集群总是可以将键 foo 写到 42,而我们根本不会注意到这一点。
因此,在 redis-rb-cluster 存储库中,有一个更有趣的应用程序,称为一致性测试。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) |
该行显示执行的读和写操作的数量,以及错误的数量(由于系统不可用,因此不接受查询)。
如果发现一些不一致,则向输出中添加新行。例如,如果我在程序运行时手动重置计数器,就会发生这种情况:
$ 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 集群故障转移。
4.8.测试故障转移
注意: 在这个测试过程中,您应该打开一个标签,并运行一致性测试应用程序。
为了触发故障转移,我们可以做的最简单的事情(这也是分布式系统中可能发生的语义上最简单的故障)是崩溃一个进程,在我们的例子中是崩溃一个主节点。
我们可以识别一个 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是 master:
$ 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命令的输出可能看起来令人生畏,但实际上非常简单,并且由以下标记组成:
- Node ID
- ip:port
- flags: master, slave, myself, fail, ...
- 如果是复制节点,则为主节点的节点ID
- 最后一个未决PING的时间仍在等待答复。
- 最后收到的PONG时间.
- 此节点的配置时期(请参阅集群规范)。
- 到该节点的链接状态。
- 服务的插槽
4.9.手动故障转移
有时,强制执行故障转移而不对主节点造成任何实际问题是有用的。例如,为了升级其中一个主节点的 Redis 进程,最好对其进行故障转移,以便将其转换为对可用性影响最小的复制节点。
Redis 群集使用 CLUSTER FAILOVER 命令支持手动故障转移,必须在要进行故障转移的主节点的一个复制节点上执行该命令。
手动故障转移是特殊的,与实际主节点故障导致的故障转移相比,手动故障转移更加安全,因为手动故障转移的发生方式避免了过程中的数据丢失,只有在系统确信新主节点处理了来自旧主节点的所有复制流时,才将客户端从原始主节点切换到新主节点。
以下是执行手动故障转移时从日志中看到的内容:
# 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.
基本上连接到我们正在失败的主节点的客户端被停止。与此同时,主节点将其复制偏移量发送给复制节点,复制节点等待到达其侧的偏移量。到达复制偏移量时,故障转移开始,并通知旧主节点有关配置开关。当旧主节点上的客户端被解除阻塞时,它们被重定向到新主节点。
4.10.添加一个新节点
添加一个新节点基本上就是添加一个空节点,然后移动一些数据到节点中,以防它是一个新的主节点,或者告诉它设置为一个已知节点的复制节点,以防它是一个复制节点
我们将从添加一个新的主节点实例开始,同时展示两者。
在这两种情况下,要执行的第一步是添加一个空节点。
这就像在端口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
请注意,由于此节点已经连接到集群,因此它已经能够正确重定向客户端查询,并且通常来说是集群的一部分。 但是,与其他masters相比,它有两个特点:
- 它不保存数据,因为它没有分配的哈希插槽
- 因为它是一个没有分配插槽的主节点,所以当一个复制节点设备想要成为一个主节点时,它不会参与选举过程
现在可以使用 redis-cli 的重新分片特性为这个节点分配散列槽。像我们在前一节中已经做的那样展示这一点基本上是没有用的,没有什么区别,它只是将空节点作为目标进行重新分片。
4.11.添加一个新节点作为复制节点
添加一个新的复制节点可以通过两种方式执行。最明显的方法是再次使用 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添加一个复制节点,该复制节点具有一个 Node 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(新的一个)上。
4.12.移除一个节点
要删除一个复制节点,只需使用 del-node 命令 redis-cli:
redis-cli --cluster del-node 127.0.0.1:7000 `<node-id>`
第一个参数只是集群中的一个随机节点,第二个参数是要删除的节点的 ID。
您也可以用同样的方法删除主节点,但是为了删除主节点,它必须是空的。如果主节点不是空的,那么您需要在之前将数据从主节点重新分片到所有其他主节点。
移除主节点的另一种方法是对其一个复制节点执行手动故障转移,并在节点变成新主节点的复制节点后移除该节点。显然,当您希望减少集群中主控程序的实际数量时,这没有帮助,在这种情况下,需要进行重新分片。
4.13.复制品迁移
在 Redis 集群中,只要使用以下命令,就可以在任何时候将复制节点重新配置为与不同的主节点进行复制:
CLUSTER REPLICATE <master-node-id>
然而,有一个特殊的场景,你希望复制节点从一个主节点自动移动到另一个主节点,而不需要系统管理员的帮助。复制节点的自动重新配置称为复制节点迁移,可以提高 Redis 集群的可靠性。
注意: 您可以阅读 Redis 集群规范中关于复制节点迁移的详细信息,这里我们只提供一些关于总体思想的信息,以及为了从中受益应该做些什么。
在某些条件下,您可能希望让集群复制节点从一个主节点控制器移动到另一个主节点控制器的原因是,通常 Redis 集群对失败的抵抗力与追加到给定主节点控制器的复制节点数量一样。
例如,如果主节点和复制节点同时失败,那么每个主节点都有一个复制节点的集群就不能继续操作,这仅仅是因为没有其他实例拥有主节点的哈希插槽的复制节点。然而,虽然网络分割可能同时隔离多个节点,但是许多其他类型的故障,比如单个节点本地的硬件或软件故障,是一类非常引人注目的故障,它们不太可能同时发生,因此,在每个主节点都有复制节点的集群中,复制节点可能在凌晨4点被杀死,主节点可能在凌晨6点被杀死。这仍然会导致集群不能再运行。
为了提高系统的可靠性,我们可以选择向每个主节点添加额外的复制节点,但这是昂贵的。复制节点迁移允许添加更多的复制节点到少数主节点。所以你有10个主节点,每个主节点有一个复制节点,总共有20个实例。然而,您添加,例如,3个实例更多的复制节点的一些主节点,所以某些主节点将有多于一个复制节点服务。
在进行复制节点迁移时,如果主节点没有复制节点,则拥有多个复制节点的主节点的复制节点将迁移到孤立的主节点。因此,当你的复制节点在凌晨4点下山,就像我们上面举的例子一样,另一个复制节点将取代它的位置,当主节点在凌晨5点也失败时,仍然有一个复制节点可以被选举出来,这样集群就可以继续运行。
那么,简而言之,关于复制迁移你应该知道些什么呢?
- 群集将尝试在给定时刻从具有最大复制节点数的主节点迁移复制节点。
- 为了从复制节点迁移中受益,您只需将更多复制节点添加到群集中的单个主节点中,哪个主节点都没有关系。
- 有一个配置参数可控制复制节点迁移功能,称为cluster-migration-barrier:您可以在Redis Cluster随附的示例redis.conf文件中了解有关此功能的更多信息。
4.13.升级 Redis 集群中的节点
升级复制节点很容易,因为只需要停止节点并使用升级版本的 Redis 重新启动它。如果有客户端使用复制节点扩展读操作,那么在给定的复制节点不可用的情况下,它们应该能够重新连接到不同的复制节点。
升级masters 要稍微复杂一些,建议的步骤是:
- 使用 CLUSTER FAILOVER 触发将主节点的手动故障转移到其中一个复制节点(请参阅本文档的“手动故障转移”部分)
- 等待主节点服务变成复制节点服务
- 最后,像对复制节点服务那样升级节点
- 如果希望主节点是刚刚升级的节点,请触发一个新的手动故障转移,以便将升级后的节点恢复为主节点
按照这个步骤,您应该一个接一个地升级节点,直到所有的节点都得到升级。
4.14.迁移到 Redis 集群
愿意迁移到 Redis 集群的用户可能只有一个主节点,或者可能已经使用了一个已经存在的分片设置,其中键在 n 个节点之间分割,使用一些内部算法或由客户端库或 Redis 代理实现的分片算法。
在这两种情况下,很容易迁移到 Redis 集群,但是最重要的细节是应用程序是否使用了多个键操作,以及如何使用。有三种不同的情况:
- 不使用多键操作或事务或涉及多个键的Lua脚本。 键是独立访问的(即使通过事务或将多个命令(大约是同一键)组合在一起的Lua脚本访问)。
- 使用了涉及多个键的多个键操作,事务或Lua脚本,但仅对具有相同哈希标签的键使用,这意味着一起使用的所有键都有一个恰好是相同的{...}子字符串。 例如,以下多个键操作是在同一哈希标记的上下文中定义的:SUNION {user:1000} .foo {user:1000} .bar。
- 涉及多个键的多个键操作,事务或Lua脚本与不具有显式或相同哈希标签的键名称一起使用。
第三种情况不是由 Redis Cluster 处理的: 应用程序需要进行修改,以便不使用多键操作或只在相同的哈希标签上下文中使用它们。
案例1和案例2已经涵盖在内了,所以我们将重点讨论这两个,以相同方式处理的案例,所以在文档中不会做任何区分。
假设你已经把你已经存在的数据集划分成 n 个主节点数据集,如果你没有已经存在的分片,n = 1,为了将你的数据集迁移到 Redis 集群,需要以下步骤:
- 停止您的客户。 当前无法自动实时迁移到Redis Cluster。 您可能能够在应用程序/环境的上下文中编排实时迁移
- 使用BGREWRITEAOF命令为所有N个母版生成一个仅追加文件,并等待AOF文件完全生成。
- 将AOF文件从aof-1保存到aof-N。 此时,您可以根据需要停止旧实例(这很有用,因为在非虚拟化部署中,您经常需要重用同一台计算机)。
- 创建由N个主节点和零个复制节点组成的Redis集群。 稍后将添加复制节点。 确保所有节点都使用仅追加文件来保持持久性。
- 停止所有群集节点,将它们的仅追加文件替换为您现有的仅追加文件,第一个节点aof-1,第二个节点aof-2,直到aof-N。
- 使用新的AOF文件重新启动Redis Cluster节点。 他们会抱怨有些钥匙根据其配置不应该放在那儿。
- 使用redis-cli --cluster fix命令来修复集群,以便将根据每个节点是否具有权威性的哈希槽迁移键。
- 最后使用redis-cli --cluster检查,以确保您的集群正常。
- 重新启动您的客户端,使其修改为使用支持Redis Cluster的客户端库。
还有一种将数据从外部实例导入Redis群集的替代方法,即使用redis-cli --cluster import命令。
该命令将正在运行的实例的所有键(从源实例中删除键)移动到指定的预先存在的Redis群集。 但是请注意,如果您将Redis 2.8实例用作源实例,则由于2.8无法实现迁移连接缓存,因此操作可能会很慢,因此您可能需要在执行此操作之前使用Redis 3.x版本重新启动源实例。