本帖最后由 旧收音机 于 2015-4-25 17:44 编辑
问题导读
1、怎么使用redis-rb-cluster写应用?
2、怎么去重新分片集群?
3、怎么去实现测试故障转移?
使用redis-rb-cluster写一个示例应用
在后面介绍如何操作Redis集群之前,像故障转移或者重新分片这样的事情,我们需要创建一个示例应用,或者至少要了解简单的Redis集群客户端的交互语义。
我们采用运行一个示例,同时尝试使节点失效,或者开始重新分片这样的方式,来看看在真实世界条件下Redis集群如何表现。如果没有人往集群写的话,观察集群发生了什么也没有什么实际用处。
这一小节通过两个例子来解释redis-rb-cluster的基本用法。第一个例子在redis-rb-cluster发行版本的exemple.rb文件中,如下:
- 1 require './cluster'
- 2
- 3 startup_nodes = [
- 4 {:host => "127.0.0.1", :port => 7000},
- 5 {:host => "127.0.0.1", :port => 7001}
- 6 ]
- 7 rc = RedisCluster.new(startup_nodes,32,:timeout => 0.1)
- 8
- 9 last = false
- 10
- 11 while not last
- 12 begin
- 13 last = rc.get("__last__")
- 14 last = 0 if !last
- 15 rescue => e
- 16 puts "error #{e.to_s}"
- 17 sleep 1
- 18 end
- 19 end
- 20
- 21 ((last.to_i+1)..1000000000).each{|x|
- 22 begin
- 23 rc.set("foo#{x}",x)
- 24 puts rc.get("foo#{x}")
- 25 rc.set("__last__",x)
- 26 rescue => e
- 27 puts "error #{e.to_s}"
- 28 end
- 29 sleep 0.1
- 30 }
复制代码
这个程序做了一件很简单的事情,一个一个地设置形式为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) |
复制代码
每一行展示了执行的读操作和写操作的次数,以及错误数(错误导致的未被接受的查询是因为系统不可用)。
如果发现了不一致性,输出将增加一些新行。例如,当我在程序运行期间手工重置计数器,就会发生:
- $ 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的时间
- 节点的配置纪元(请看集群规范).
- 节点的连接状态
- 服务的哈希槽
相关内容:
Redis 3.0官方文档翻译计划(1) ——Redis介绍
Redis 3.0官方文档翻译计划(2) ——从入门到精通(上)
Redis 3.0官方文档翻译计划(3) ——从入门到精通(中)
Redis 3.0官方文档翻译计划(4) ——从入门到精通(下)
Redis 3.0中文官方文档翻译计划(5) ——使用Redis实现Twitter(上)
Redis 3.0中文官方文档翻译计划(6) ——使用Redis实现Twitter(下)
Redis 3.0中文官方文档翻译计划(7) ——使用Redis作为LRU缓存
Redis 3.0中文官方文档翻译计划(8) ——分片
Redis 3.0中文官方文档翻译计划(9) ——复制
Redis 3.0中文官方文档翻译计划(10) ——持久化
Redis 3.0中文官方文档翻译计划(11) ——集中插入
Redis 3.0中文官方文档翻译计划(12) ——高可用(上)
Redis 3.0中文官方文档翻译计划(13) ——高可用(下)
Redis 3.0中文官方文档翻译计划(14) ——高可用客户端指引
Redis 3.0中文官方文档翻译计划(15) ——集群(上)
Redis 3.0中文官方文档翻译计划(16) ——集群(中)
Redis 3.0中文官方文档翻译计划(17) ——集群(下)
|