redis 集群(文档整理)

Redis集群

·Redis集群提供了一种运行Redis安装的方法,在该安装中,数据会在多个Redis节点之间自动分片。css

Redis集群在分区期间还提供了必定程度的可用性,这其实是在某些节点出现故障或没法通讯时有继续工做的能力。可是,若是发生较严重故障(例如,大多数主节点不可用时),集群将中止运行。node

实际上,Redis集群能给你带来什么?git

  • 自动在多个节点之间拆分数据集的能力。
  • 当一部分节点出现故障或没法与集群中其余节点通讯时,仍然能够继续操做。

Redis集群TCP端口

每一个Redis集群节点都须要打开两个TCP链接。用于服务客户端的常规RedisTCP端口,例如6379,再加上将数据端口加10000的端口,好比在示例中为16379github

第二个值更大一点的端口用于集群总线,也就是使用二进制协议的节点到节点之间的通讯通道。节点将集群总线用于故障检测,配置更新,故障转移受权等。客户端永远不要尝试与集群总线端口进行通讯,而应始终与普通的Redis命令端口进行通讯,可是请确保您在防火墙中同时打开了这两个端口,不然Redis集群节点将没法进行通讯。redis

命令端口和集群总线端口的偏移量是固定的,而且始终为10000数据库

请注意,对于每一个节点,要使Redis集群正常工做,您须要:swift

  1. 用于与客户端通讯的常规通讯端口(一般为6379),向那些须要访问集群的全部客户端以及全部其余集群节点(使用客户端端口进行key迁移)开放。
  2. 集群总线端口(客户端端口+ 10000)必须能够从全部其余集群节点访问。

若是您没有同时打开两个TCP端口,则集群将没法正常工做。promise

集群总线使用不一样的二进制协议进行节点到节点的数据交换,它更适合于在节点之间使用较少的带宽和较少的处理时间来交换信息。缓存

Redis集群数据分片

Redis集群不使用一致性哈希,而是使用一种不一样形式的分片,从概念上讲每一个key都是咱们称为hash槽的一部分。安全

Redis集群中有16384个hash槽,要计算给定key的hash槽,需将key的CRC16值用16384取模。

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

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

这样能够轻松添加和删除集群中的节点。例如,若是我想添加一个新节点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主节点写入数据。
  • B主节点向您的客户端答复“肯定”。
  • B主节点将写操做传播到其备份节点B1,B2和B3。

如您所见,B在回复客户端以前不会等待B1,B2,B3的确认,由于这会对Redis形成延迟,所以,若是您的客户端进行了写操做,而后B会确认,可是在它把写操做发送给备份节点以前崩溃了,此时其中一个备份节点(未接收到写操做)能够升级为主节点,这样就永远丢失该写操做。这与配置为每秒将数据刷新到磁盘的大多数数据库所发生的状况很是类似,所以因为过去使用不涉及分布式系统的传统数据库系统的经验,您已经能够对此进行合理推断。一样,您能够经过强制数据库在答复客户端以前刷新磁盘上的数据来提升一致性,但这一般会致使性能太低。在Redis集群下,这至关于同步复制。

基本上,在性能和一致性之间进行权衡是必须的。

Redis集群在须要时能够经过WAIT命令实现同步写,这使得丢失写的可能性大大下降,可是请注意,即便使用同步复制,Redis集群也不实现强一致性:在更复杂的状况下,老是有可能存在一种场景,就是一个没法接收数据的备份节点被选为主节点。

还有一种值得注意的状况,Redis集群也会丢失写操做,这种状况发生在网络分区期间,在该分区中,客户端与少数实例(至少包括主节点)隔离。

以咱们的6个节点集群为例,该集群由A,B,C,A1,B1,C1组成,具备3个主节点
和3个备份节点。还有一个客户,咱们将其称为Z1。

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

Z1仍然可以对B进行写操做,B将接受其写入。若是分区在很短的时间内恢复正常,则集群将继续正常运行。可是,若是分区持续的时间足以使B1升级为该分区的多数端的主节点,则Z1向B发送的写操做将丢失。

请注意,Z1将可以发送到B的写入量有一个最大的窗口:若是已经有足够的时间使大分区选举出一个主节点,则小分区中的每一个主节点都将中止接受写入。该时间是Redis集群的一个很是重要的配置指令,称为节点超时。

在节点超时以后,主节点被视为发生故障,而且能够用其副本之一替换。相似地,在超过指定的时间后,主节点仍是没法感知大多数其余主节点,此主节点进入错误状态并中止接受写入。

Redis集群配置参数

咱们将建立一个集群部署做为例子。在继续以前,让咱们介绍一下Redis集群里的redis.conf文件中引入的配置参数。
继续阅读下去您就会得到更多清晰的要点。

  • cluster-enabled <yes/no> : 若是设置为yes,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>:若是设置为yes,默认状况下,若是某个节点未覆盖必定比例的key空间,集群将中止接受写入。若是该选项设
    置为no,即便此节点仅能处理有关key的部分子集的请求,集群仍将提供查询。

建立和使用一个Redis集群

注意:手动部署Redis集群,了解其某些操做很是重要。 可是,若是要尽快创建集群并运行,请跳过本节和下一节,直接转到使用 create-cluster 脚本建立Redis集群。

要建立集群,咱们须要作的第一件事就是让一些空Redis实例运行在集群模式下。基本上,这意味着不能使用常规Redis实例来建立集群,由于须要配置特殊模式,以便Redis实例启用集群特定的功能和命令。

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

port 7000  # 端口号
cluster-enabled yes  # 是成为cluster节点
cluster-config-file nodes.conf  # 节点配置文件
cluster-node-timeout 5000   # 节点超时时间
appendonly yes  # 是否使用aof

启用集群模式的只须要直接打开cluster-enabled命令。每一个实例还包含该节点配置存储位置的文件路径,默认状况下为nodes.conf。 该文件不会被人接触。 它只是由Redis集群实例在启动时生成,并在须要时进行更新。

请注意,按预期工做的最小集群要求至少包含三个主节点。 对于您的第一个测试,强烈建议启动一个包含三个主节点和三个备份节点的六个节点集群。为此,输入一个新目录并建立如下目录,该目录以咱们将在给定目录中运行的实例的端口号命名。就像是:

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

在从7000到7005的每一个目录中建立一个redis.conf文件。做为配置文件的模板,只需使用上面的小示例,但请确保根据目录名称用正确的端口号替换端口号7000。如今,将您的redis-server可执行文件(从GitHub不稳定分支中的最新资源编译而来)复制到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地址和端口可能会更改,可是惟一的节点标识符在节点的整个生命周期中都不会改变。咱们将此标识符简称为节点ID。

建立集群

如今,咱们有许多实例正在运行,而后须要经过向节点写入一些有意义的配置来建立集群。

若是您使用的是Redis 5,这很容易完成,这是由于redis-cli中嵌入了Redis集群命令行实用程序,咱们可使用它来建立新集群,检查或从新分片现有集群等。

对于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个槽位中某一个.

使用create-cluster脚本建立一个Redis集群

若是您不想如上所述经过手动配置和执行单个实例来建立Redis集群,则可使用更简单的系统(可是您将不会学到一样多的操做细节)。

只需检查Redis发行版中的utils / create-cluster目录。内部有一个名为create-cluster的脚本(名称与包含在其中的目录相同),它是一个简单的bash脚本。 为了启动具备3个主节点和3个备份节点的6节点集群,只需键入如下命令:

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

在步骤2中,当redis-cli但愿您接受集群布局时,回复yes。

如今,您能够与集群进行交互,默认状况下,第一个节点将从端口30001开始。 完成后,使用如下命令中止集群:

create-cluster stop.

关于如何运行这个脚本的更多信息,请阅读目录里的README。

集群操做

到目前为止,Redis集群的问题之一是缺乏客户端库的实现。

据我所知有如下实现:

  • redis-rb-cluster 是我(@antirez)编写的Ruby实现,可做为其余语言的参考。它是原始redis-rb的简单包装,实现了最小语义以有效地与集群通讯。
  • redis-py-cluster redis-rb-cluster的Python实现。支持大多数redis-py功能。正在积极发展中。
  • 流行的Predis支持Redis集群,该支持最近已更新而且正在积极开发中。
  • 使用最普遍的Java客户端,Jedis最近添加了对Redis集群的支持,请参阅项目README中的Jedis集群部分。
  • StackExchange.Redis提供对C#的支持(而且应与大多数.NET语言,VB,F#等兼容)
  • thunk-redis提供对Node.js和io.js的支持,它是基于thunk/promise的redis客户端,具备管道和集群功能。
    redis-go-cluster是Go语言的
    Redis集群的实现,它使用了Redigo library client做为基本客户端,经过结果聚合实现了MGET/MSET。
  • ioredis是流行的Node.js客户端,为Redis集群提供了强大的支持。
  • 当使用-c开关启动时,redis-cli程序实现了基本的集群支持。

测试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:7000> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"

注意:若是使用脚本建立集群,则节点可能会侦听不一样的端口,默认状况下从30001开始。

redis-cli的支持很是基础,所以它始终基于如下事实:Redis集群节点可以将客户端重定向到正确的节点。一个严格的客户端能够作得更好,而且能够在hash槽和节点地址之间缓存映射,以便直接使用与节点的正确链接。仅在集群配置中发生某些更改时(例如,在故障转移以后或系统管理员经过添加或删除节点来更改集群布局以后),才会刷新映射。

使用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 形式的key的值设置为数字,而且是一个接一个的递增。
所以,若是您运行该程序,其结果将和如下命令是同样的效果:

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

该程序看起来比较复杂,由于它须要在屏幕上显示错误而不是异常退出,所以,对集群执行的每一个操做都应该由错误处理包装。

第14行是程序中的第一个有趣的行。它建立Redis集群对象,使用启动节点列表做为参数,并容许该对象与不一样节点创建的最大链接数,最后是超时时间,对于给定的操做多少时间后被视为失败。

启动节点不须要是集群的全部节点。但至少有一个节点是可达的。还要注意,只要可以与第一个节点链接,redis-rb-cluster就会更新此启动节点列表。您应该指望任何其余严格的客户端都应该采起这种行为。

如今咱们已经将Redis集群对象实例存储在rc变量中,咱们能够像使用普通的Redis对象实例同样使用该对象了。

这刚好发生在第18至26行中:从新启动示例时,咱们不想以foo0从新开始,所以咱们将计数器存储在Redis自己内。上面的代码旨在读取此计数器,或者若是不存在该计数器,则为其分配零值。

可是请注意这是一个while循环,由于即便集群关闭并返回错误,咱们也要一次又一次尝试。普通的应用程序不须要那么当心。

28和37之间开始主循环,在该循环中设置key或显示错误。

注意循环结束时的sleep调用。在测试中,若是您想尽量快地写入集群,则能够删除sleep(相对来讲,这只是一个很繁忙的循环操做,它并无真正的并行,所以,在最好的条件下,您一般将得到每秒10k个操做))。

一般,为了使示例程序更容易被人看懂,写入速度会减慢。启动应用程序将产生如下输出:

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

这不是一个很是有趣的程序,咱们稍后将使用更好的程序,可是咱们已经能够看到程序运行时,在从新分片期间都发生了什么。

集群从新分片

如今,咱们准备尝试集群从新分片。 为此,请保持example.rb程序运行,以便您查看对程序的运行是否有影响。另外,您可能想注释一下sleep调用,以便在从新分片期间发生一些更严重的写入负载。从新分片基本上意味着将hash槽从一组节点移动到另外一组节点,而且像集群建立同样,它使用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个hash槽,若是该示例仍在运行且没有sleep调用,则该hash槽应已包含少许的key。

而后redis-cli须要知道从新分片的目标是什么,也就是将接收hash槽的节点。 我将使用第一个主节点,即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。

如今,它会问你要从哪些节点获取这些key。我只输入all,以便从全部其余主节点获取一些hash槽。

最终确认后,您会看到一条消息,代表redis-cli将要从一个节点移动到另外一个节点,而且将从一侧移动到另外一侧的每一个实际的key都会打印出来。

在从新分片过程当中,您应该可以看到示例程序运行不受影响。若是须要,您还能够在从新分片期间中止并从新启动它屡次。从新分片结束时,可使用如下命令测试集群的运行情况:

redis-cli --cluster check 127.0.0.1:7000

全部插槽都会被覆盖到,可是此次127.0.0.1:7000的主节点将具备更多的hash插槽
,大约为6461。

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

咱们以前编写的示例程序不怎么好。它以一种简单的方式写入集群,甚至无需检查写入的内容是否正确。从咱们的角度来看,接收写操做的集群能够始终在每一个操做里将名为foo的key写到42这个hash槽里,而咱们根本不会注意到。所以,在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) |

该行显示执行的读取和写入的次数,以及错误的数目(因为系统不可用,所以因为错误而没法接受查询)。若是发现不一致,则将新行添加到输出中。例如,若是我在程序运行时手动重置了计数器,就会发生这种状况:

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

测试故障转移

注意:在此测试过程当中,你应打开一个tab标签页并在上面运行一致性测试应用程序。

为了触发故障转移,咱们能够作的最简单的事情(也就是在分布式系统中可能发生的语义上最简单的失败)是使单个进程崩溃,在咱们的例子中是单个主机崩溃。

咱们可使用如下命令来识别主节点并使其崩溃:

$ 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都是主节点,咱们把7002这台机器用DEBUG SEGFAULT命令使其崩溃。

$ 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集群提供的一致性保证。

如今,咱们能够检查故障转移以后的集群设置是什么(请注意,我从新启动了崩溃的实例,以便它做为备份节点从新加入集群):

$ 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:端口
  • 标志位: 主节点,备份节点, myself, 失败状态, ...
  • 若是本身是备份节点,则是其主节点的ID
  • 上一次发出PING后还未收到回复的持续时间.
  • 上一次接收到的PONG的时间.
  • 节点的配置epoch (请看 集群规范).
  • 此节点的连接状态.
  • 服务的插槽...

手动故障转移

有时,强制进行故障转移而实际上不会对主节点引发任何问题是颇有用的。例如,为了升级主节点之一的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.

基本上,链接到咱们将要进行故障转移的主节点的客户端都已中止。同时,主节点将其复制偏移发送到备份节点,备份节点会在它这边等待偏移接收完毕。 当复制偏移量完成时,故障转移开始,而且将向旧的主节点通知配置切换。 当客户端在旧的主节点上解锁时,它们将被重定向到新的主节点。

添加新节点

添加新节点的基本过程是先添加一个空节点,而后将一些数据移入该节点(若是它是新的主节点),或者告诉它设置为已知节点的副本(若是它是备份节点)。从添加新的主节点开始,咱们二者都会展现。在这两种状况下,要执行的第一步都是添加一个空节点。这就像在端口7006中启动一个新节点(现有的6个节点已经从7000到7005使用新节点)同样简单,除了端口号以外,其余节点都使用相同的配置,所以您应该按顺序进行操做以符合咱们以前节点使用的设置:

  • 在你的终端应用上开启一个新的tab。
  • 输入 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在这里对咱们没什么用,它只是向节点发送了CLUSTERMEET消息,这也能够手动完成。不过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

请注意,因为此节点已经链接到集群,所以它已经可以正确重定向客户端查询,一般来讲它已是集群的一部分了。 可是,与其余主节点相比,它有两个特色:

  • 因为没有分配的hash槽,所以不保存任何数据。
  • 由于它是没有分配插槽的主机,因此当备份节点要成为主节点时,它不会参与选
    举过程。

如今可使用redis-cli的从新分片功能将hash槽分配给该节点。像上一节中已经展现的那样,这里我就不展现了,他们的操做没有区别,只是将空节点做为目标进行从新分片。

添加一个节点做为副本(备份节点)

添加一个备份节点能够经过2种方式完成。最经常使用的是用 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命令将其转换为副本。 若是将该节点添加为备份节点,但您想将其做为其余主节点的副本进行移动,也同样适用。

例如,为了给节点127.0.0.1:7005添加副本,此节点当前服务的hash槽正在11423-16383范围内,节点ID为3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e,我要作的就是链接到新节点(已经做为空的主节点添加到集群)并在新节点上发送命令:

redis 127.0.0.1:7006> cluster replicate 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e

就是这样。 如今,这组hash槽有了一个新副本,而且集群中的全部其余节点都已经知道(几秒钟后须要更新其配置)。 咱们可使用如下命令进行验证:

$ 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...如今有了2个备份节点,运行在7002端口(已存在)和7006端口(新加入的)

移除节点

为了移除一个备份节点,只须要在redis-cli上使用del-node 命令:

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

第一个参数只是集群中的一个随机节点,第二个参数是您要删除的节点的ID。您也能够用相同的方法删除主节点,可是要删除主节点,它必须为空。若是主节点不为空,则须要先将数据从新分片到全部其余主节点。

删除主节点的另外一种方法是在其一个备份节点上对其执行手动故障转移,并在该节点成为新主节点的备份节点以后删除该节点。显然,这在您想要减小集群中的主节点的实际数量时没什么用,在这种状况下,须要从新分片。

副本迁移

在Redis集群里里任什么时候间你均可以从新配置一个备份节点使其做为另外一个主节点的从属节点,使用下列命令:

CLUSTER REPLICATE <master-node-id>

可是,有一种特殊状况,您但愿副本在没有系统管理员帮助的状况下自动从一个主节点移动到另外一个主节点。副本的自动从新配置称为副本迁移,它可以提升Redis集群的可靠性。

注意:您能够在Redis集群规范中阅读副本迁移的详细信息,这里咱们仅提供一些通常的想法以及您应该从中受益的信息。

在某些状况下,您可能想让您的集群副本从一个主节点移动到另外一个主节点的缘由是,Redis集群一般具备与给定主节点的副本数量相同的故障容忍性。

例如,若是一个主节点及其副本同时失败,则每一个主节点都有一个副本的集群将没法继续工做,这仅仅是由于没有其余实例拥有与该主节点服务的相同的hash槽的副本。可是,尽管网络断裂可能会同时隔离多个节点,可是许多其余类型的故障(例如单个节点本地的硬件或软件故障)是很是值得注意的一类故障,这类故障不太可能同时发生,所以在每一个主节点都有一个备份节点的集群中,若是该备份节点在凌晨4点被关闭,而主节点在凌晨6点被关闭。这仍然会致使集群没法运行。

为了提升系统的可靠性,咱们能够选择向每一个主节点添加副本,但这成本很高。副本迁移容许将更多备份节点添加到少数几个主节点中。所以,您有10个节点,每一个节点有1个备份节点,总共20个实例。可是,好比您增长了3个实例做为某些主节点的备份节点,所以某些主节点将具备多个副本了。

使用副本迁移时,若是一个主节点不包含备份节点,则具备多个备份节点的主节点的副本将迁移到孤立的主节点。所以,当您的备份节点在上述示例中的凌晨4点关闭以后,另外一个备份节点将接替它;当主节点在凌晨5点也发生故障时,另外一个备份节点将被选举成为主节点,以便集群能够继续操做。

因此您应该了解哪些有关副本迁移的知识?

  • 在某个时刻,集群会尝试从具备最多副本数的主节点中选择一个副本进行迁移。
  • 为了从副本迁移中受益,您只需为集群中的单个主节点添加一些副本,无论是哪一个主节点
  • 有一个配置参数可控制副本迁移功能,称为cluster-migration-barrier:您可
    以在Redis集群随附的示例redis.conf文件中了解有关此功能的更多信息。
    参考
相关文章
相关标签/搜索