redis3.0集群官方文档

来源:互联网 发布:抢票软件成网络黄牛 编辑:程序博客网 时间:2024/05/21 12:35


转载自 胡杰的专栏


集群(上)


这篇文档是对Redis集群的介绍,没有使用复杂难懂的东西来理解分布式系统的概念。本文提供了如何建立,测试和操作一个集群的相关指导,但没有涉及在Redis集群规范(参考本系列其他文章,译者注)中的诸多细节,只是从用户的视角来描述系统是如何运作的。

注意,如果你打算来一次认真的Redis集群的部署,更正式的规范文档(关注本系列文章,译者注)强烈建议你好好读一读。

Redis集群(Redis Cluster)


Redis集群提供一种运行Redis的方式,数据被自动的分片到多个Redis节点。

集群不支持处理多个键的命令,因为这需要在Redis节点间移动数据,使得Redis集群不能提供像Redis单点那样的性能,在高负载下会表现得不可预知。

Redis集群也提供在网络分割(partitions)期间的一定程度的可用性,这就是在现实中当一些节点失败或者不能通信时能继续进行运转的能力。

所以,在实践中,你可以从Redis集群中得到什么呢?

在多个节点间自动拆分你的数据集的能力。
当部分节点正在经历失败或者不能与集群其他节点通信时继续运转的能力。
Redis集群的TCP端口(Redis Cluster TCP ports)

每个Redis集群节点需要两个TCP连接打开。正常的TCP端口用来服务客户端,例如6379,加10000的端口用作数据端口,在上面的例子中就是16379。

第二个大一些的端口用于集群总线(bus),也就是使用二进制协议的点到点通信通道。集群总线被节点用于错误检测,配置更新,故障转移授权等等。客户端不应该尝试连接集群总线端口,而应一直与正常的Redis命令端口通信,但是要确保在防火墙中打开了这两个端口,否则Redis集群的节点不能相互通信。

命令端口和集群总线端口的偏移量一直固定为10000。

注意,为了让Redis集群工作正常,对每个节点:

用于与客户端通信的正常的客户端通信端口(通常为6379)需要开放给所有需要连接集群的客户端以及其他集群节点(使用客户端端口来进行键迁移)。
集群总线端口(客户端端口加10000)必须从所有的其他集群节点可达。
如果你不打开这两个TCP端口,你的集群就不会像你期待的那样去工作。

Redis集群的数据分片(Redis Cluster data sharding)


Redis集群没有使用一致性哈希,而是另外一种不同的分片形式,每个键概念上是被我们称为哈希槽(hash slot)的东西的一部分。

Redis集群有16384个哈希槽,我们只是使用键的CRC16编码对16384取模来计算一个指定键所属的哈希槽。

每一个Redis集群中的节点都承担一个哈希槽的子集,例如,你可能有一个3个节点的集群,其中:

节点A包含从0到5500的哈希槽。
节点B包含从5501到11000的哈希槽。
节点C包含从11001到16384的哈希槽。
这可以让在集群中添加和移除节点非常容易。例如,如果我想添加一个新节点D,我需要从节点A,B,C移动一些哈希槽到节点D。同样地,如果我想从集群中移除节点A,我只需要移动A的哈希槽到B和C。当节点A变成空的以后,我就可以从集群中彻底删除它。

因为从一个节点向另一个节点移动哈希槽并不需要停止操作,所以添加和移除节点,或者改变节点持有的哈希槽百分比,都不需要任何停机时间(downtime)。

Redis集群的主从模型(Redis Cluster master-slave model)


为了当部分节点失效时,或者无法与大多数节点通信时仍能保持可用,Redis集群采用每个节点拥有1(主服务自身)到N个副本(N-1个附加的从服务器)的主从模型。

在我们的例子中,集群拥有A,B,C三个节点,如果节点B失效集群将不能继续服务,因为我们不再有办法来服务在5501-11000范围内的哈希槽。

但是,如果当我们创建集群后(或者稍后),我们为每一个主服务器添加一个从服务器,这样最终的集群就由主服务器A,B,C和从服务器A1,B1,C1组成,如果B节点失效系统仍能继续服务。

B1节点复制B节点,于是集群会选举B1节点作为新的主服务器,并继续正确的运转。

Redis集群的一致性保证(Redis Cluster consistency guarantees)

Redis集群不保证强一致性。实践中,这意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令。

Redis集群为什么会丢失写请求的第一个原因,是因为采用了异步复制。这意味着在写期间下面的事情发生了:

你的客户端向主服务器B写入。
主服务器B回复OK给你的客户端。
主服务器B传播写入操作到其从服务器B1,B2和B3。
你可以看到,B在回复客户端之前没有等待从B1,B2,B3的确认,因为这是一个过高的延迟代价,所以如果你的客户端写入什么东西,B确认了这个写操作,但是在发送写操作到其从服务器前崩溃了,其中一个从服务器被提升为主服务器,永久性的丢失了这个写操作。

这非常类似于在大多数被配置为每秒刷新数据到磁盘的数据库发生的事情一样,这是一个可以根据以往不包括分布式系统的传统数据库系统的经验来推理的场景。同样的,你可以通过在回复客户端之前强制数据库刷新数据到磁盘来改进一致性,但这通常会极大的降低性能。

基本上,有一个性能和一致性之间的权衡。

注意:未来,Redis集群在必要时可能或允许用户执行同步写操作。

Redis集群丢失写操作还有另一个场景,发生在网络分割时,客户端与至少包含一个主服务器的少数实例被孤立起来了。

举个例子,我们的集群由A,B,C,A1,B1,C1共6个节点组成,3个主服务器,3个从服务器。还有一个客户端,我们称为Z1。

分割发生以后,有可能分割的一侧是A,C,A1,B1,C1,分割的另一侧是B和Z1。

Z1仍然可以写入到可接受写请求的B。如果分割在很短的时间内恢复,集群会正常的继续。但是,如果分割持续了足够的时间,B1在分割的大多数这一侧被提升为主服务器,Z1发送给B的写请求会丢失。

注意,Z1发送给B的写操作数量有一个最大窗口:如果分割的大多数侧选举一个从服务器为主服务器后过了足够多的时间,少数侧的每一个主服务器节点将停止接受写请求。

这个时间量是Redis集群一个非常重要的配置指令,称为节点超时(node timeout)。

节点超时时间过后,主服务器节点被认为失效,可以用其一个副本来取代。同样地,节点超时时间过后,主服务器节点还不能感知其它主服务器节点的大多数,则进入错误状态,并停止接受写请求。

创建和使用Redis集群(Creating and using a Redis Cluster)


要创建一个集群,我们要做的第一件事情就是要有若干运行在集群模式下的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集群实例生成,每次在需要时被更新。

注意,可以正常运转的最小集群需要包含至少3个主服务器节点。在你的第一次尝试中,强烈建议开始一个6个节点的集群,3个主服务器,3个从服务器。
要这么做,先进入一个新的目录,创建下面这些以端口号来命名的目录,我们后面会在每个目录中运行实例。

像这样:
mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005
在从7000到7005的每个目录内创建一个redis.conf文件。作为你的配置文件的模板,只使用上面的小例子,但是要确保根据目录名来使用正确的端口号来替换端口号7000。

现在,复制你从Github的不稳定分支的最新的源代码编译出来的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地址和端口可能会变化,但是唯一的节点标识符在节点的整个生命周期中都不会改变。我们称这个标识符为节点ID(Node ID)。

创建集群(Creating the cluster)


现在,我们已经有了一些运行中的实例,我们需要创建我们的集群,写一些有意义的配置到节点中。

这很容易完成,因为我们有称为redis-trib 的Redis集群命令行工具来帮忙,这是一个Ruby程序,可以在实例上执行特殊的命令来创建一个新的集群,检查或重分片一个已存在的集群,等等。

redis-trib工具在Redis源代码分发版本的src目录中。要创建你的集群,简单输入:


./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,因为我们想创建一个新的集群。--replicas 1选项意思是我们希望每个创建的主服务器有一个从服务器。其他参数是我想用来创建新集群的实例地址列表。

显然,我们要求的唯一布局就是创建一个拥有3个主服务器和3个从服务器的集群。

Redis-trib会建议你一个配置。输入yes接受。集群会被配置和连接在一起,也就是说,实例会被引导为互相之间对话。最后,如果一切顺利你会看到一个类似这样的消息:

[OK] All 16384 slots covered
这表示,16384个槽中的每一个至少有一个主服务器在处理。

与集群共舞(Playing with the cluste)

在当前阶段,Redis集群的一个问题是缺少客户端库的实现。

据我所知有以下实现:

redis-rb-cluster是我(@antirez)写的Ruby实现,作为其他语言的参考。这个是对原先的redis-rb进行了简单的封装,实现了与集群高效对话的最小语义。
redis-py-cluster看起来就是redis-rb-cluster的Python版本。最新没有更新(最后一次提交是6个月之前)但是这是一个起点。
流行的Predis有对Redis集群的支持,支持最近有更新,并处于活跃开发状态。
最多使用的Java客户端Jedis最近增加了对Redis集群的支持,请查看项目README中的Jedis集群部分。
StackExchange.Redis提供对C#的支持(应该与大多数.NET语言工作正常:VB,F#等)。
Github上Redis仓库的不稳定分支上的redis-cli工具实现了一个基本的集群支持,使用-c启动时切换。
测试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"
redis-cli的集群支持非常基本,所以总是依赖Redis集群节点重定向客户端到正确的节点。一个真正的客户端可以做得更好,缓存哈希槽和节点地址之间的映射,直接使用到正确节点的正确连接。映射只在集群的配置发生某些变化时才重新刷新,例如,故障转移以后,或者系统管理员通过添加或移除节点改变了集群的布局以后。


集群(中)

   使用redis-rb-cluster写一个示例应用 
    在后面介绍如何操作Redis集群之前,像故障转移或者重新分片这样的事情,我们需要创建一个示例应用,或者至少要了解简单的Redis集群客户端的交互语义。 
    我们采用运行一个示例,同时尝试使节点失效,或者开始重新分片这样的方式,来看看在真实世界条件下Redis集群如何表现。如果没有人往集群写的话,观察集群发生了什么也没有什么实际用处。 
    这一小节通过两个例子来解释redis-rb-cluster的基本用法。第一个例子在redis-rb-cluster发行版本的exemple.rb文件中,如下: 
Ruby代码
  require './cluster'  
  
  startup_nodes = [  
      {:host => "127.0.0.1", :port => 7000},  
      {:host => "127.0.0.1", :port => 7001}  
  ]  
  rc = RedisCluster.new(startup_nodes,32,:timeout => 0.1)  
  
  last = false  
  
  while not last  
      begin  
          last = rc.get("__last__")  
          last = 0 if !last  
      rescue => e  
          puts "error #{e.to_s}"  
          sleep 1  
      end  
  end  
  
  ((last.to_i+1)..1000000000).each{|x|  
      begin  
          rc.set("foo#{x}",x)  
          puts rc.get("foo#{x}")  
          rc.set("__last__",x)  
      rescue => e  
          puts "error #{e.to_s}"  
      end  
      sleep 0.1  
  }  


    这个程序做了一件很简单的事情,一个一个地设置形式为foo<number>的键的值为一个数字。所以如果你运行这个程序,结果就是下面的命令流: 

SET foo0 0  
SET foo1 1  
SET foo2 2  
And so forth...  


    这个程序看起来要比通常看起来更复杂,因为这个是设计用来在屏幕上展示错误,而不是由于异常退出,所以每一个对集群执行的操作都被begin rescue代码块包围起来。 
    第7行是程序中第一个有意思的地方。创建了Redis集群对象,使用启动节点(startup nodes)的列表,对象允许的最大连接数,以及指定操作被认为失效的超时时间作为参数。 
启动节点不需要是全部的集群节点。重要的是至少有一个节点可达。也要注意,redis-rb-cluster一旦连接上了第一个节点就会更新启动节点的列表。你可以从任何真实的客户端中看到这样的行为。 
    现在,我们将Redis集群对象实例保存在rc变量中,我们准备像一个正常的Redis对象实例一样来使用这个对象。 
    第11至19行说的是:当我们重启示例的时候,我们不想又从foo0开始,所以我们保存计数到Redis里面。上面的代码被设计为读取这个计数值,或者,如果这个计数器不存在,就赋值为0。 
    但是,注意这里为什么是个while循环,因为我们想即使集群下线并返回错误也要不断地重试。一般的程序不必这么小心谨慎。 
    第21到30行开始了主循环,键被设置赋值或者展示错误。 
    注意循环最后sleep调用。在你的测试中,如果你想尽可能快地往集群写入,你可以移除这个sleep(相对来说,这是一个繁忙的循环而不是真实的并发,所以在最好的条件下通常可以得到每秒10k次操作)。 
    正常情况下,写被放慢了速度,让人可以更容易地跟踪程序的输出。 
    运行程序产生了如下输出: 

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


    这不是一个很有趣的程序,稍后我们会使用一个更有意思的例子,看看在程序运行时进行重新分片会发生什么事情。 


    重新分片集群(Resharding the cluster) 
    现在,我们准备尝试集群重分片。要做这个请保持example.rb程序在运行中,这样你可以看到是否对运行中的程序有一些影响。你也可能想注释掉sleep调用,这样在重分片期间就有一些真实的写负载。 
    重分片基本上就是从部分节点移动哈希槽到另外一部分节点上去,像创建集群一样也是通过使用redis-trib工具来完成。 
    开启重分片只需要输入: 

./redis-trib.rb reshard 127.0.0.1:7000  


    你只需要指定单个节点,redis-trib会自动找到其它节点。 
    当前redis-trib只能在管理员的支持下进行重分片,你不能只是说从这个节点移动5%的哈希槽到另一个节点(但是这也很容易实现)。那么问题就随之而来了。第一个问题就是你想要重分片多少: 
    你想移动多少哈希槽(从1到16384)? 
    我们尝试重新分片1000个哈希槽,如果没有sleep调用的那个例子程序还在运行的话,这些槽里面应该已经包含了不少的键了。 
    然后,redis-trib需要知道重分片的目标了,也就是将接收这些哈希槽的节点。我将使用第一个主服务器节点,也就是127.0.0.1:7000,但是我得指定这个实例的节点ID。这已经被redis-trib打印在一个列表中了,但是我总是可以在需要时使用下面的命令找到节点的ID: 

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

    好了,我的目标节点是97a3a64667477371c4479320d683e4c8db5858b1。 
    现在,你会被询问想从哪些节点获取这些键。我会输入all,这样就会从所有其它的主服务器节点获取一些哈希槽。 
    在最后的确认后,你会看到每一个被redis-trib准备从一个节点移动到另一个节点的槽的消息,并且会为每一个被从一侧移动到另一侧的真实的键打印一个圆点。 
    在重分片进行的过程中,你应该能够看到你的示例程序运行没有受到影响。如果你愿意的话,你可以在重分片期间多次停止和重启它。 
    在重分片的最后,你可以使用下面的命令来测试一下集群的健康情况: 

./redis-trib.rb check 127.0.0.1:7000  

    像平时一样,所有的槽都会被覆盖到,但是这次在127.0.0.1:7000的主服务器会拥有更多的哈希槽,大约6461个左右。 

    一个更有意思的示例程序 
    到目前为止一切挺好,但是我们使用的示例程序却不够好。不顾后果地(acritically)往集群里面写,而不检查写入的东西是否是正确的。 
    从我们的观点看,接收写请求的集群可能一直将每个操作都作为设置键foo值为42,我们却根本没有察觉到。 
    所以在redis-rb-cluster仓库中,有一个叫做consistency-test.rb的更有趣的程序。这个程序有意思得多,因为它使用一组计数器,默认1000个,发送INCR命令来增加这些计数器。 
但是,除了写入,程序还做另外两件事情: 
当计数器使用INCR被更新后,程序记住了写操作。
在每次写之前读取一个随机计数器,检查这个值是否是期待的值,与其在内存中的值比较。

    这个的意思就是,这个程序就是一个一致性检查器,可以告诉你集群是否丢失了一些写操作,或者是否接受了一个我们没有收到确认(acknowledgement)的写操作。在第一种情况下,我们会看到计数器的值小于我们记录的值,而在第二种情况下,这个值会大于。 
    运行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) |  


    每一行展示了执行的读操作和写操作的次数,以及错误数(错误导致的未被接受的查询是因为系统不可用)。 
    如果发现了不一致性,输出将增加一些新行。例如,当我在程序运行期间手工重置计数器,就会发生: 
Java代码  
$ redis 127.0.0.1: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时,真实值是144,所以程序报告了144个写操作丢失(集群没有记住的INCR命令执行的次数)。 
    这个程序作为测试用例很有意思,所以我们会使用它来测试Redis集群的故障转移。 

    测试故障转移(Testing the failover) 
    注意:在测试期间,你应该打开一个标签窗口,一致性检查的程序在其中运行。 
    为了触发故障转移,我们可以做的最简单的事情(这也是能发生在分布式系统中语义上最简单的失败)就是让一个进程崩溃,在我们的例子中就是一个主服务器。 
    我们可以使用下面的命令来识别一个集群并让其崩溃: 

$ 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集群提供的一致性保证(即非强一致性,译者注)。 
    我们现在可以看看故障转移后的集群布局(注意,与此同时,我重启了崩溃的实例,所以它以从服务器的身份重新加入了集群): 

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

    现在,主服务器运行在7000,7001和7005端口。之前运行在7002端口的主服务器现在是7005的从服务器了。 
    CLUSTER NODES命令的输出看起来挺可怕的,但是实际上相当的简单,由以下部分组成: 
节点ID
ip:port
flags: master, slave, myself, fail, ...
如果是从服务器的话,就是其主服务器的节点ID
最近一次发送PING后等待回复的时间
最近一次发送PONG的时间
节点的配置纪元(请看集群规范).
节点的连接状态
服务的哈希槽

集群(下)

手动故障转移(Manual failover) 

    有时候在主服务器事实上没有任何故障的情况下强制一次故障转移是很有用的。例如,为了升级主服务器节点中的一个进程,可以对其进行故障转移使其变为一个从服务器,这样最小化了对可用性的影响。 
    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.  

    基本上,连接到我们正在故障转移的主服务器的客户端停止了。与此同时,主服务器发送复制偏移量给从服务器,等待到达这个偏移量。当复制偏移量到达以后,故障转移就开始了,旧的主服务器被通知切换配置。当客户端在旧主服务器上解除阻塞时,就被重定向到新的主服务器。 

    添加新节点(Adding a new node) 
    添加一个新节点的过程基本上就是,添加一个空节点,然后,如果是作为主节点则移动一些数据进去,如果是从节点则其作为某个节点的副本。 
    两种情况我们都会讨论,先从添加一个新的主服务器实例开始。 
    两种情况下,第一步要完成的都是添加一个空节点。 
    我们使用与其他节点相同的配置(端口号除外)在7006端口(我们已存在的6个节点已经使用了从7000到7005的端口)上开启一个新的节点,那么为了与我们之前的节点布局一致,你得这么做: 
在你的终端程序中开启一个新的标签窗口。
进入cluster-test目录。
创建一个名为7006的目录。
在里面创建一个redis.conf的文件,类似于其它节点使用的文件,但是使用7006作为端口号。
最后使用../redis-server ./redis.conf启动服务器。

    此时服务器已经在运行中了。 
    现在我们可以像通常一样使用redis-trib来添加节点到已存在的集群中。 

./redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000  

    你可以看到,我使用了addnode命令,指定新的节点地址为第一个参数,集群中一个随机存在的节点的地址作为第二个参数。 
    实际上redis-trib在这里对我们只有很少的帮助,只是发送了一个CLUSTER MEET消息到节点,这些也可以手动完成。但是redis-trib也在操作之前检查了集群的状态,所以即便你知道内部是如何工作的,一直通过redis-trib来执行集群操作也是一个不错的主意。 
    现在,我们可以连接到这个新的节点,看看它是否真的加入到了集群中: 

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-trib的重新分片特性来给这个节点赋予哈希槽了。基本上没有必现展示这个了,因为我们已经在之前的小节中展示过了,没有什么不同,只是以空节点为目标的一次重分片。 

    添加副本节点(Adding a new node as a replica) 
    添加一个新副本可以有两种方式。显而易见的一种方式是再次使用redis-trib,但是要使用—slave选项,像这样: 
Java代码  收藏代码
./redis-trib.rb add-node --slave 127.0.0.1:7006 127.0.0.1:7000  

    注意,这里的命令行完全像我们在添加一个新主服务器时使用的一样,所以我们没有指定要给哪个主服务器添加副本。这种情况下,redis-trib会添加一个新节点作为一个具有较少副本的随机的主服务器的副本。 
    但是,你可以使用下面的命令行精确地指定你想要的主服务器作为副本的目标: 

./redis-trib.rb add-node --slave --master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7006 127.0.0.1:7000  

    这样我们就把一个新的副本赋予了一个指定的主服务器。 
    一种更手工的给指定主服务器添加副本的方式,是添加一个新节点作为一个空主服务器,然后使用CLUSTER REPLICATE命令将其变为副本。如果节点被作为从服务器添加,但是你想移动它为另一个不同的主服务器的副本,这也是可行的。 
    例如,为了给节点127.0.0.1:7005添加一个副本,这个节点当前服务11432-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 connect  

    这个3c3a0c…的节点现在有两个从服务器了,分别运行于7002(已存在的)和7006(新的)端口。 

    移除节点(Removing a node) 
    要移除一个从服务器节点,只要使用redis-trib的del-node命令就可以: 
Java代码  收藏代码
./redis-trib del-node 127.0.0.1:7000 <node-id>  

    第一个参数只是集群中的一个随机节点,第二个参数是你想移除的节点的ID。 
你也可以用同样的方式移除一个主服务器节点,但是,为了移除一个主服务器节点,它必须是空的。如果主服务器不是空的,你需要先将其数据重分片到其他的主服务器节点。 
    另一种移除主服务器节点的方式,就是在其从服务器上执行一次手工故障转移,当它变为了新的主服务器的从服务器以后将其移除。显然,当你需要真的减少你的集群中的主服务器的数量时这个没有什么帮助,如果那样的话,就需要重新分片了。 

副本迁移(Replicas migration) 

    在Redis集群中,可以使用下面的命令在任何时候重新配置一个从服务器复制一个不同的主服务器: 

CLUSTER REPLICATE <master-node-id>  

    但是有一种特殊的场景,你想让副本集自动地从一个主服务器移动到另一个主服务器,而不需要系统管理员的帮助。自动重新配置副本集被称为副本集迁移,这可以改善Redis集群的可靠性。 
    注意:你可以在Redis集群规范中阅读到副本集迁移的细节,这里我们只提供一般性的信息,以及为了从中受益你该做什么。 
    为什么你想让你的集群副本在某些特定条件下从一个主服务器移动到另一个的原因,是通常情况下Redis集群对失败的抵御能力和连接到指定从服务器的副本数量成正相关。 
    例如,每个主服务器只有单个副本组成的集群在主服务器及其副本同时失效时就不能够继续运转,因为没有其他的实例拥有这台主服务器服务的哈希槽的副本。但是,网络断裂可能会在同一时间隔绝若干节点,其他类型的故障,例如单个节点的硬件或者软件错误,是值得注意的一类故障,很可能会同时发生,所以在每个主服务器拥有一个从服务器的集群中,有可能从服务器在下午4点被干掉,而主服务器在下午6点被干掉。这仍然会导致集群不再能运转。 
    为了改进系统的可靠性,我们有一些增加额外副本集到每个主服务器的选项,但是这代价昂贵。副本迁移允许添加更多的从服务器到少许的主服务器。所以你可以有10个主服务器,每个有一个从服务器,总共20个实例。但是你为某些主服务器添加例如3个以上的实例,那么有些主服务器会有多余一个的从服务器。 
    有了副本集迁移会发生什么?如果一个主服务器没有从服务器,一个来自于拥有多个从服务器的主服务器上的从服务器会迁移到这个孤独的主服务器上。所以像上面我们举的例子中,在你的从服务器下午4点下线以后,另一个从服务器会接替它的位置,当主服务器在下午5点也失效的时候,仍然有一个从服务器可以被选举,这样集群就可以继续运转了。 
那么,简而言之,你应该了解副本集迁移的什么呢? 
集群会尝试从在某一个指定时刻拥有最多数量副本集的主服务上迁移一个副本。
为了从副本迁移中受益,你需要在集群中添加多一些的副本到单个主服务器,无论是什么主服务器。
有一个称为replica-migration-barrier的控制副本迁移特性的配置参数。你可以在Redis集群提供的示例redis.conf文件中读到更多信息。


    升级节点(Upgrading nodes in a Redis Cluster) 
    升级从服务器节点很简单,因为你只需要停止节点然后用已更新的Redis版本重启。如果有客户端使用从服务器节点分离读请求,它们应该能够在某个节点不可用时重新连接另一个从服务器。 
    升级主服务器要稍微复杂一些,建议的步骤是: 
使用CLUSTER FAILOVER来触发一次手工故障转移主服务器(请看本文档的手工故障转移小节)。
等待主服务器变为从服务器。
像升级从服务器那样升级这个节点。
如果你想让你刚刚升级的节点成为主服务器,触发一次新的手工故障转移,让升级的节点重新变回主服务器。

    你可以按照这些步骤来一个节点一个节点的升级,直到全部节点升级完毕。 

迁移到Redis集群(Migrating to Redis Cluster) 

    想迁移到Redis集群的用户可能只有一个单一的主服务器,或者已经使用了已存在的分片布局,通过使用某种内部算法,或者他们的客户端库实现的分片算法,或者Redis代理,键被分拆到N个节点上。 
    这两种情况下迁移到Redis集群都很简单,但是最重要的细节是,如果程序使用了多键操作,怎么办。有三种不同的情况: 
没有使用多键操作,或者事务,或者涉及多个键的Lua脚本。键被独立地访问(即使通过事务或者Lua脚本组合针对同样的键的多个命令一起来访问)。

使用了多键操作,或者事务,或者涉及多个键的Lua脚本,但是键都有相同的哈希标签(hash tag),也就是说这些一起使用的键都碰巧有相同的{…}子串。例如,下面的多键操作是在相同的哈希标签上下文中定义的:SUNION {user:1000}.foo {user:1000}.bar。

即多个键相同的部分作为前缀,并用"{ }"包起来,如这样的一组键可以一起作为多键操作

{user:1000}.friends.1,{user:1000}.friends.2,{user:1000}.friends.2,因为{user:1000}这部分有相同的哈希标签

使用了多键操作,或者事务,或者涉及多个键的Lua脚本,但是键的名字没有一个显式的或者相同的哈希标签。

    Redis不处理第三种情况:应用程序需要被修改为不能使用多键操作,或者只能在相同的哈希标签上下文中使用。 
    前两种情况覆盖到了,所以我们会聚焦在这两种情况,它们会用相同的方式来处理,所以本文不会去区别对待。 
    假设你的已存在数据集已经被拆分到了N个主服务器上,如果你没有已存在的分片的话N=1,你需要下面的步骤来迁移你的数据集到Redis集群: 
停止你的客户端。当前没有自动在线迁移(live-migration)到Redis集群的可能。你也许可以通过精心策划一次在你的程序或环境上下文中的在线迁移来办到。
使用BGREWRITEOF命令为所有你的N个主服务器生成一个追加文件,然后等待AOF文件完全生成。
按照aof-1到af-N保存你的AOF文件到某处。此时愿意的话你可以停掉你的旧实例(这很有用,因为在非虚拟化的部署中,你常常需要重用这些计算机)。
创建一个由N个主服务器和0个从服务器组成的Redis集群。你可以稍后添加从服务器。确保所有你的节点都是使用追加文件来持久化。
停止所有的集群节点,用你已存在的追加文件替换他们的朱家文件,aof-1替换第一节点,aof-2替换第二个节点,一直到aof-N。
使用新的AOF文件来重启你的Redis集群。它们会抱怨按照配置有些键不应该出现。
使用redis-trib fix命令来修正集群,这样键就会根据每个节点的哈希槽被迁移了。
最后使用redis-trib check来确保集群是正常的。
重启被修改为支持Redis集群的客户端。

    还有一个方式从外部实例导入数据到Redis集群,就是使用redis-trib import命令。 

    这个命令移动一个运行实例(同时删除源实例上的键)上的所有键到一个指定已存在的Redis集群。但是,注意如果你使用redis 2.8实例作为来源实例,操作可能很慢,因为2.8没有实现迁移连接缓存(migrate connection caching),所以在执行这个操作之前,你可能得重启你的Redis 3.x版本的源实例。



原创粉丝点击