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

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


    这篇文档是对Redis集群的介绍,没有使用复杂难懂的东西来理解分布式系统的概念。本文提供了如何建立,测试和操作一个集群的相关指导,但没有涉及在Redis集群规范(参考本系列其他文章,译者注)中的诸多细节,只是从用户的视角来描述系统是如何运作的。
    注意,如果你打算来一次认真的Redis集群的部署,更正式的规范文档(关注本系列文章,译者注)强烈建议你好好读一读。
    Redis集群当前处于alpha阶段,如果你发现任何问题,请联系Redis邮件列表,或者在Redis的Github仓库中开启一个问题(issue)。

    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集群工作正常,对每个节点:
  1. 用于与客户端通信的正常的客户端通信端口(通常为6379)需要开放给所有需要连接集群的客户端以及其他集群节点(使用客户端端口来进行键迁移)。
  2. 集群总线端口(客户端端口加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集群节点重定向客户端到正确的节点。一个真正的客户端可以做得更好,缓存哈希槽和节点地址之间的映射,直接使用到正确节点的正确连接。映射只在集群的配置发生某些变化时才重新刷新,例如,故障转移以后,或者系统管理员通过添加或移除节点改变了集群的布局以后。
===============================================================================
    大家好,我是阮威。华中科技大学,计算机软件专业硕士。毕业后加入腾讯,先后在腾讯电子商务部和无线游戏产品部工作,现供职于欢聚时代基础产品部。IT男,至今。欢迎大家收听我的公众账号。

猜你喜欢

转载自powersoft.iteye.com/blog/2153858