集群重新分片
现在我们可以来尝试集群重新分片了。做分片的时候请保持集群运行,这样如果分片对程序有什么影响你就可以观察的到了。你也可以考虑将 example.rb 中的 sleep 调用删掉, 从而让重新分片操作在近乎真实的写负载下执行。
重分片意思就是把一些哈希槽从一些节点移动到另一些节点中取。正如我们集群创建的时候那样做的,重新分片也可以使用redis-trib 工具来做。
除了移动的哈希槽数量之外, redis-trib 还需要知道重新分片的目标, 也即是, 负责接收这 1000 个哈希槽的节点。我会使用第一个master节点: 127.0.0.1:70000 。 但是我需要在实例中指定Node ID。用redis-trib可以输出节点列表。但是我也可以通过以下命令找到节点ID:
现在需要指定从哪写节点来移动keys到目标。我输入的是all ,这样就会从其他每个master上取一些哈希槽。最后确认后你将会看到每个redis-trib移动的槽的信息,每个key的移动的信息也会打印出来。在重新分片的过程中,你的例子程序是不会受到影响的,你可以停止或者重新启动多次。
在重新分片结束后你可以通过如下命令检查集群状态
将重新分片操作做成一个脚本
重新分片操作可以做成自动的,这样我们就不用在交互模式下手动的输入一个个参数了。可以通过以下的命令行去实现:
一个更有趣的示例应用
我们在前面使用的示例程序 example.rb 并不是十分有趣, 因为它只是不断地对集群进行写入, 但并不检查写入结果是否正确。 比如说, 集群可能会错误地将 example.rb 发送的所有 SET 命令都改成了 SET foo 42 , 但因为 example.rb 并不检查写入后的值, 所以它不会意识到集群实际上写入的值是错误的。
因为这个原因, redis-rb-cluster 项目包含了一个名为 consistency-test.rb 的示例应用, 这个应用比起 example.rb 有趣得多: 它创建了多个计数器(默认为 1000 个), 并通过发送
INCR 命令来增加这些计数器的值。
- 每次使用 INCR 命令更新一个计数器时, 应用会记录下计数器执行 INCR 命令之后应该有的值。 举个例子, 如果计数器的起始值为 0 , 而这次是程序第 50 次向它发送 INCR 命令, 那么计数器的值应该是 50 。
- 在每次发送 INCR 命令之前, 程序会随机从集群中读取一个计数器的值, 并将它与自己记录的值进行对比, 看两个值是否相同。
换句话说, 这个程序是一个一致性检查器(consistency checker): 如果集群在执行 INCR 命令的过程中, 丢失了某条 INCR 命令, 又或者多执行了某条客户端没有确认到的 INCR 命令, 那么检查器将察觉到这一点 —— 在前一种情况中, consistency-test.rb 记录的计数器值将比集群记录的计数器值要大; 而在后一种情况中, consistency-test.rb 记录的计数器值将比集群记录的计数器值要小。
运行 consistency-test 程序将产生类似以下的输出:
如果程序察觉了不一致的情况出现, 它将在输出行的末尾显式不一致的详细情况。
比如说, 如果我们在 consistency-test.rb 运行的过程中, 手动修改某个计数器的值,那么 consistency-test.rb 将向我们报告不一致情况:
注意:在执行本节操作的过程中, 请一直运行 consistency-test 程序。为了触发失效备援,我们要做的最简单的事情(这也是在一个分布式系统中最简单的一种故障)就是把一个redis进程搞挂,在我们的例子中就是一个master节点进程。
我们可以定义一个集群,并通过以下命令让其崩溃:
现在我们可以来观察下 consistency test 的输出。
你可以看到在失效备援的时候系统拒绝了578个读请求和577个写请求,但是数据库中没有引发任何一个的不一致问题。这可能跟教程刚开始部分所说的不同。在教程刚开始的时候我们说到redis 集群之所以在失效备援的时候会丢失写请求是因为它使用的是异步复制机制。我在教程开始的时候没有提到这点:其实丢失写请求的情况是很少发生的。因为把请求的响应返回给客户端和发送复制命令给slave这两件事情几乎是同时发生的。所以丢失数据的时间窗口非常小。然而非常难发生并不意味这不可能发生。所以这并没有改变redis集群无法实现强一致性的事实。
我们现在可以查看失效备援之后集群的布局(注意我同时重启了崩溃的实例,这样可以把这个节点作为slave重新加入到系统中):
现在 7000, 7001 和 7005 是master节点。7002之前是master节点,现在是7005的一个slave节点。
CLUSTER NODES 命令的结果可能看起来很复杂,但是它实际上是很简单的,并且是一下token的组合:
现在 7000, 7001 和 7005 是master节点。7002之前是master节点,现在是7005的一个slave节点。
CLUSTER NODES 命令的结果可能看起来很复杂,但是它实际上是很简单的,并且是一下token的组合:
- 节点 ID
- ip:port
- flags :例如 master 、 slave 、 myself 、fail
- 如果节点是一个从节点的话, 那么跟在 flags 之后的将是主节点的节点 ID
- 集群最近一次向节点发送 PING 命令之后, 过去了多长时间还没接到回复。
- 节点最近一次返回 PONG 回复的时间。
- 节点的配置纪元(configuration epoch):详细信息请参考 Redis 集群规范 。
- 本节点的网络连接情况:例如 connected 。
- 节点目前包含的槽:例如 127.0.0.1:7001 目前包含号码为 5960 至 10921 的哈希槽。
手动失效备援
有时候就算master不是真的出问题了,也需要强制引发一次失效备援。比如为了升级Redis中的一个master节点,又想尽量减小对系统可用性的影响,我们就可以用失效备援来把这个master节点转换为slave节点。
redis集群可以用
CLUSTER FAILOVER命令来进行手动失效备援。这个命令必须要在你想切换的目标
slave上执行。
手动失效备援比master实际出错引发的失效备援更安全。因为手动失效备援是在系统正常运行并且新的master节点持续不断的接收来自master的复制请求的情况下将客户端的连接从原来的master移动到新的master之上的,这样可以保证在切换的过程中不丢失数据。
以下是你做失效备援的时候slave产生的日志:
添加新节点
添加一个新节点其实就是以下过程:添加一个空白节点,移动一些数据到这个节点里面或者告诉它作为一个现有节点的备份节点,即一个slave节点。
本节将对以上两种情况进行介绍,首先介绍主节点的添加方法:
2种情况第一个步骤都是
添加一个空白节点。
照启动其他节点的配置(我们已经照这个配置启动了7000到7005这6个节点了)来启动一个监听7006的节点其实很简单,唯一不一样的就是端口号.以下是启动端口号为 7006 的新节点的详细步骤:
- 在终端里创建一个新的标签页。
- 进入 cluster-test 文件夹。
- 创建并进入 7006 文件夹。
- 将 redis.conf 文件复制到 7006 文件夹里面,然后将配置中的端口号选项改为 7006 。
- 使用命令 ../../redis-server redis.conf 启动节点。
如果一切正常, 那么节点应该会正确地启动.
现在我们像平时一样使用
redis-trib 来添加一个节点到集群中
在实际情况下 redis-trib 其实没有帮我们做很多事情,它只是发送了一个 CLUSTER MEET 信息给节点,这件事情手动也可以完成。 然而redis-trib 还在操作之前检查了集群的状态,所以就算你知道redis-trib 是如何工作的你也最好使用redis-trib来执行这些操作。
通过 cluster nodes 命令, 我们可以确认新节点 127.0.0.1:7006 已经被添加到集群里面了
通过 cluster nodes 命令, 我们可以确认新节点 127.0.0.1:7006 已经被添加到集群里面了
- 新节点没有包含任何数据, 因为它没有包含任何哈希桶。
- 尽管新节点没有包含任何哈希桶, 但它仍然是一个主节点, 所以在集群需要将某个从节点升级为新的主节点时, 这个新节点不会被选中。
现在可以用redis-trib 的重新分片功能移动一些哈希槽到这个节点了。因为使用 redis-trib 移动哈希桶的方法在前面已经介绍过, 所以这里就不再重复介绍了。
添加一个slave节点
通过2步操作可以添加一个新的slave。 第一步是再次使用 redis-trib ,不过这回要带上 --slave 选项,如下:
不过你也可以精确的指定你要挂载这个slave及诶单到哪个master上:
这就是我们添加一个slave到指定master的方法。
一个更手动添加slave到指定master的方式是:添加一个空节点然后通过 CLUSTER REPLICATE 命令来将其转化为一个slave节点。这个方法也同样适用于当一个节点已经是slave节点的时候你想将它转换为另一个master的slave。
这就是我们添加一个slave到指定master的方法。
一个更手动添加slave到指定master的方式是:添加一个空节点然后通过 CLUSTER REPLICATE 命令来将其转化为一个slave节点。这个方法也同样适用于当一个节点已经是slave节点的时候你想将它转换为另一个master的slave。
比如我现在想给127.0.0.1:7005节点添加一个复制节点(就是slave),该节点现在的哈希槽范围是 11423-16383,Node ID 是 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e。我所要做的只不过是连上新节点(在此之前该节点已经被作为空master节点添加到集群里面了)并执行以下命令:
移除一个节点
通过 redis-trib 提供的 del-node 命令可以移除一个slave节点:
你也可以用这条命令来移除master节点,但是 在移除master节点之前必须确保它是空的。如果你要移除的master节点不是空的,你需要先用重新分片命令来把数据移到其他的节点。另外一个移除master节点的方法是先进行一次手动的失效备援,等它的slave被选举为新的master,并且它被作为一个新的slave被重新加到集群中来之后再移除它。很明显,如果你是想要减少集群中的master数量,这种做法没什么用。在这种情况下你还是需要用重新分片来移除数据后再移除它。
复制迁移
虽然在redis集群中通过以下命令是可以将一个slave节点重新配置为另外一个master的slave:
注意: 你可以从
Redis集群手册 中读到复制迁移的细节。但是在这篇教程里面我们只介绍大概的思路和究竟你可以从中得到什么好处。
在某种情况下,你想让集群的复制节点从一个master迁移到另一个master的原因可能是:集群的抗崩溃能力总是跟集群中master 拥有的平均slave数量成正比。
比如,如果一个集群中每个master只有一个slave,当master和slave都挂掉的时候这个集群就崩溃了。因为此时有一些哈希槽无法找到了。虽然网络分裂会把一堆节点从集群中孤立出来(这样你一下就会知道集群出问题了),但是其他的更常见的硬件或者软件的问题并不会在多台机器上同时发生,所以很可能在你的这个集群(平均每个master只有一个slave)有一个slave在早上4点挂掉,然后他的master在随后的早上6点挂掉。这样依然会导致集群崩溃。
比如,如果一个集群中每个master只有一个slave,当master和slave都挂掉的时候这个集群就崩溃了。因为此时有一些哈希槽无法找到了。虽然网络分裂会把一堆节点从集群中孤立出来(这样你一下就会知道集群出问题了),但是其他的更常见的硬件或者软件的问题并不会在多台机器上同时发生,所以很可能在你的这个集群(平均每个master只有一个slave)有一个slave在早上4点挂掉,然后他的master在随后的早上6点挂掉。这样依然会导致集群崩溃。
我们可以通过给每个master都再多加一个slave节点来改进系统的可靠性,但是这样很昂贵。复制迁移允许只给某些master增加slave。比方说你的集群有20个节点,10个master,每个master都有1个slave。然后你增加3个slave到集群中并把他们分配给某几个master节点,这样某些master就会拥有多于1个slave。
当某个master失去了slave的时候,复制迁移可以将slave节点从拥有富余slave的master旗下迁移给没有slave的master。所以当你的slave在早上4点挂掉的时候,另一个slave会被迁移过来取代它的位置,这样当master节点在早上5点挂掉的时候,依然有一个slave可以被选举为master,集群依然可以正常运行。
所以简而言之你应该了解关于复制迁移的哪些方面?
- 集群在迁移的时候会尝试去迁移拥有最多slave数量的master旗下的slave。
- 想利用复制迁移特性来增加系统的可用性,你只需要增加一些slave节点给单个master(哪个master节点并不重要)。
- 复制迁移是由配置项cluster-migration-barrier控制的: 你可以从Redis集群提供的默认配置文件 redis.conf 样例中了解到更多关于复制迁移的知识。
在Redis集群中升级节点
升级一个slave节点非常简单,因为你只需要停止节点,升级它,然后启动节点就好了。如果此时这个slave有客户端在连接也没关系,在这个slave停止的时候客户端会被重定向到别的slave去。
升级一个master就有点复杂了,推荐使用以下步骤升级master:
- 使用 CLUSTER FAILOVER 命令来使用手动失效备援,这样来把master切换为slave
- 等待master切换为slave完成
- 就像你升级普通slave一样升级它
- 如果你希望刚刚升级好的节点再次作为master在集群中运行,那就再触发一次手动失效备援让这个及节点重新成为master
照这些步骤你就可以一个一个的升级集群中的节点了。
迁移到redis集群
希望迁移到redis集群的用户可能只有一个master节点。他也可能正在使用一个现有的分片设置,该分片设置中的key已经被切分到N个节点去,这些节点是使用客户端自己实现的或者redis代理实现的一些分片算法。
在这些情况下迁移到redis集群都是很容易的,然而最终要的是如果应用使用了多key操作。以下是三种不同的情况:
- 不使用多key操作或者事务操作或者Lua脚本(涉及到多key)。对key的访问都是独立的。
- 使用多key操作,事务或者lua脚本(涉及到多key),但是只作用于相同的哈希槽,即这些key都有一个{...}包裹起来的部分相同。比如以下的多key操作都是在同一个哈希标签下的:SUNION {user:1000}.foo {user:1000}.bar.
- 使用了多key操作,事务或者lua脚本(涉及到多key),操作的key并没有相同的哈希标签。
Redis集群无法处理第3种情况:如果不想用多key操作就要修改一下应用,或者只在相同哈希槽的情况下使用
第一种和第二种情况是适用的,所以我们重点关注前两种情况。这两种情况是采用同一个方法解决的:
假设你已经有一些数据了,这些数据本分割到N个节点上,并且如果没有数据分片的话这个N=1。你可以采用以下步骤将你的数据迁移到redis集群上:
- 停止你的客户端。目前redis集群还没有动态迁移功能。
- 通过BGREWRITEAOF 命令生成一个AOF(append only file)文件。并等待该AOF文件生成完毕
- 把AOF文件命名为 aof-1 到 aof-N 。此时你可以停止你的旧实例 (在实际情况下一般会用相同的机器来跑新集群)
- 建一个有N个master节点但没有slave节点的集群。你可以吃些添加slave。确保你所有的节点都使用AOF。
- 停止所有节点把它们的aof文件替换成之前保存的aof文件,aof-1对应第1节点,aof-n对应第n个节点。
- 重启你的redis集群。
- 用 redis-trib 命令修复集群,让key可以被迁移过来
- 最后,用 redis-trib 来检查你的集群是否迁移成功。
- 重启客户端。
还有另外一种方法来把外部数据导入到redis集群里面,就是用 redis-trib import 命令。该命令可以把所有key从一个运行中的实例迁移到redis集群中(这些key会被从源实例中删除)。不过请注意如果源实例用的是2.8版本该操作可能会很慢,因为2.8版本还没有实现迁移连接的缓存,所以你可能需要升级源实例到3.x之后重启你的实例,之后再迁移。