`
powersoft
  • 浏览: 194914 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
博客专栏
53a63413-d61c-321e-8dc6-5008e88923c6
Redis官方文档翻译和源...
浏览量:187827
社区版块
存档分类
最新评论

Redis 3.0中文官方文档翻译计划(20) ——集群(中)

阅读更多
Redis 3.0中文官方文档翻译计划(20)
——集群(中)


    使用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的时间
  • 节点的配置纪元(请看集群规范).
  • 节点的连接状态
  • 服务的哈希槽

===============================================================================
    大家好,我是阮威。华中科技大学,计算机软件专业硕士。毕业后加入腾讯,先后在腾讯电子商务部和无线游戏产品部工作,现供职于欢聚时代基础产品部。IT男,至今。欢迎大家收听我的公众账号。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics