官方原文地址:https://redis.io/topics/cluster-tutorial
水平有限,如果您在阅读过程中发现有翻译的不合理的地方,请留言,我会尽快修改,谢谢。
这是一篇对Redis集群的入门介绍,这里不会使用复杂难懂的分步式系统概念。这里提供的指导有集群 的安装、测试,和操作,不函盖Redis集群规范中的细节,而只是站在用户的角度来描述系统的行为方式。
这个教程试图从最终用户角度,以简单易懂的方式来讲解Redis集群高可用性和一至性的特点。
注意,这个教程需要redis的版本为3.0及以上版本。
如果你打算运行更严格的Redis集群部属,即使不是特别的需要 ,也非常建议阅读正式的规范。当然从这篇文档开始也是不错的主意,花一些时间在Redis集群上,然后再阅读规范。
Redis Cluster 101
Redis集群提供 数据自动分片到多个Redis节点的方式运行Redis实例,Redis 集群在分区期间提供了一定程度的可用性,实际上是当节点挂掉或不能通信时继续运行的能力。然而会在重大故障情况下Redis会停止运行(eg.绝大多数的主节点不可用)
在实际情况中,你想从Redis集群中得到什么呢?
》自动分割数据集到多个节点的能力
》子节点故障或者不能和集群内的其它节点通信时,继续运行的能力
Rdis Cluster TCP 端口
每个Redis集群节点需要打开两个TCP连接。一个用于服务端和客户端连接的一般端口,例好6379,另一个端口是通过数据端口加10000得到,这个例子中是16379。第二个较高的端口用于集群总线,这是一个使用二进制协议端到端的通信通道。集群通道用于节点的故障侦查,配置更新,故障转移授权等。客户端总是和正常的Redis命令端口通信 ,永远不要尝试和集群总线通信.请确保你的防火墙开放这两个端口,不然集群节点不能相互通信。
命令端口和集群总线端口偏移是固定的,偏移量总是10000.
注意为了Redis集群正常的工作,对每一个节点:
1.用于和客户端通信的标准通信端口(通常是6379)对所有需要和联系集群和集群其它节点(使用用于键迁移的客户端端口) 的客户端开放。
2.集群总线端口(客户端端口+10000)必须可以被集群的其它节点访问到。
Redis集群和Docker
现在Redis集群不支持 NATed环境和ip地址或TCP端口被重新映射的环境。
Docker使用一种称为端口映射的技术:程序在Docker容器中运行的端口,可能与Docker暴露出的端口不一至。这对于同时在一个服务器内多个容器使用同一个端口是非常有用的。
为了使Docker和Redis集群兼容,你需要使用Docker的主机网络模式。请在Docker document查看更多的--net=host相关信息。
Redis集群数据分片
Redis集群不使用一致性的hash算法,而使用数据分片(sharding),每一个键概念上都是哈希槽(hash slot)的一部分。在Redis集群里有16384个哈希槽,我们使用键进行CRC16算法运算再和16384进行模运算来计算给定的key属于哪个槽。
Redis集群里的每个节点都是哈希槽(hash slot)的子集。例,假如你的集群有3个节点:
》节点A包含0到5500哈希槽(hash slot)。
》节点B包含5501到11000哈希槽(hash slot)。
》节点C包含11001到16384哈希槽(hash slot)。
这样允许你轻松的增减集群节点。例,假如你想添加 一个新的节点D,需要从节点A,B,C移动哈希槽(hash slot)到D节点。同样的,如果想去掉节点A,只需要将A的哈希槽(hash slot)移动到B和C,当A的哈希槽(hash slot)为空的时候就可以从集群中删除它了。
因为从一个节点移动哈希槽(hash slot)到其它节点不需要中断Redis运行,添加和移除节点,或者修改节点哈希槽(hash slot)所占用的百分比,不需要任何停机时间。
Redis集群支持运行多键操作,在一个命令执行中所有涉及到的键(整个事务或Lua执行角本)都属于同一个哈希槽。用户可以通过使用哈希标签(hash tags)概念来强制多个键归属于相同哈希槽的一部分。
哈希标签(hash tags)在Redis集群规范中有记载,但是关键在于键的{}内有子字符,只有{}中的字符串才会被哈希,例 this{foo}key和 another{foo}key被保证在同一个哈希槽中(hash slot),并且可以在具有多键作为参数的命令中一起使用。
Redis集群主从模式
为了当子节点故障或者不能和其它大部分节点通信时能正常运行,Redis集群修改主从模式(master-slave),每一个哈希槽(hash slot)有1(master自己)到N个复制(N-1个附加的从节点).
在我们的例子中集群有3个节点A,B,C.如果B节点故障则集群不能继续运行,因为我们没有办法再为5501-11000范围内的哈希槽(hash slot)提供服务。
然而当集群被创建时(或在后面的时间)我们每一个主节点添加一个从节点,这样最终的主节点由A,B,C组成,从节点为A1,B1,C1,这样的系统当节点B故障时依然可以运行。
节点B1复制B节点,当B故障时,集群将把B1节点晋升为新的主节点,这样系统会继续运行。
Redis一致性的保证
Redis集群不保证数据的强一致性。实际上这意味着在某些情况下Redis集群可能会丢失已经被系统确认的客户端写操作。
使用异步复制是集群丢失写命令的一个原因,这意味在写操作期间,会发生下列情况:
》你的客户端对主节点B 写入操作.
》主节点B向客户端返回OK。
》主节点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发送的写入请求将会丢失。
Z1可以向B节点发送的请求写入量有一个最大的上限:如果占用的时间足够长,有大量节点的分区将会选择一个从节点晋升为主节点,在有大量节点的分区内的所有主节点将停止接受写入命令。
这一段时间是非常重要的Redis集群配置,称为节点超时时间(node timeout)
节点超节点时时间(node timeout)过后,一个主节点将被认为已经下线,并且可以被他的从节点替换。
同样的超时节点时间过后,没有一个主节点能够感知到大多数的其它主节点,它将进入一个错误状态,并停止接收写入命令。
Redis 集群配置参数
我们将创建一个集群部属实例。在开始之前,先介绍Redis集群的redis.conf文件内引入的配置参数,有一些很易于理解,其它的只要你持续阅读就能很明白。
> cluster-enabled <yes/no>
:如果设置为yes,那么将在一个特殊的Redis实例内启用支持Redis集群.否则实例做为普通的独立实例启动。
> cluster-config-file <filename>
:注意,不用理这个选项的名字,这不是用户可编辑的配置文件,而是Redis集群节有变动的时自动维护的集群配置(状态,和基本信息)的文件,为了能在启动时重新读取它。这个文件列出了集群中的节点,和节点的状态,持久变量等等。经常这个文件做为消息接收的结果被重写并刷入到硬盘
> cluster-node-timeout <milliseconds>
: Redis集群节点最长的不可用时间,如果没有设置他将做为故障处理。如果主节在指定的时间内无法访问,将通过他的从节点故障转移。在Redis集群内这个参数还控制其它重要的东西.尤其是,在指定的时间内每一个不能访问绝大多数的主节点的节点,将会停止接受请求。
> cluster-slave-validity-factor <factor>
: 如果设置为0,从节点将总是尝试故障切换主节点,不理会主节点和从节点之间的链路保持断开的时间。如果设置为正数,最大的断开时间被计算为节点超时(node timeout)的值乘以提供此选项的因子(factor),并且如果这个节点是个从节点,主节点链路断开的时间大于指定的时间也不尝试启动故障切换。例如,如果节点超时时间设置为5秒,这个有效因子设置为10,从节点和主节点的断开时间超过50秒也不会尝试故障切换它的主节点。注意,当主节点故障时,如果没有从节点故障切换主节点,任何不同于0的值会导致Redis集群不可用。在这种情况下,只有原来的主节点重新加入到集群,集群才会返回可用。
> cluster-migration-barrier <count>
:主节点保持连接的最小从节点的数量,用于别的从节点迁移到不再被其它从节点覆盖的主节点。相关更详细的信息,请阅读本教程中关于复制副本迁移的相应部分。
> cluster-require-full-coverage <yes/no>
:如果设置为yes,也是它的默认值,如果某些键空间的百分比没有被任何一个节点所覆盖集群将停止接收写入命令。如果设置为no,即使只能处理有关键的子集求,集群依然提供查询。
创建和使用Redis集群
注意:要手动的创建Redis集群学习,学习某些业务方面很重要。然而如果你想使集群启动并快速运行,跳过这一章节和下一节直接去创建Redis集群使用创建集群角本(Creating a Redis Cluster using the create-cluster script).
要创建集群,我们需要做的第一件事是有几个空的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集群实例启动时创建的,并在需要的时候对它进行修改。
注意,这个最小的集群(minimal cluster)按预期的工作需要包含至少3个主节点。做为你的第一次尝试,强烈建议你启用3个主节点和3个从节点,6个节点集群。
这样做,进入一个新的目录,并创建下面的目录并以实例的端口号命名,我们将在这些给定的目录内运行实例。
像这样:
mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005
在每个目录内创建一个redis.conf文件,从7000到7005。使用上边的小例子做为你的配置文件的模板,但是请确保依据目录名使用正确的端口号来替换端口号7000.
现在复制你的redis-server 可执行文件,从Github上不稳定分支内的最新源码编译的,复制到cluster-test目录,最后使用你最喜欢的终端程序打开6个终端标签。
在每个终端标签内像这样启动实例:
cd 7000
../redis-server ./redis.conf
正如你在每个实例的log中看到的,由于没有nodes.conf存在,每一个节点赋给自己一个新的ID.
[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1
这个ID将被把定的实例永久使用,以便这个实例在集群的上下文中有唯一的名称。每一个节点使用这个ID来记录其它节点,而不是使用IP或端口号.IP地址或端口号可能会变动,而在节点的整个生命周其唯一的节点标识符是不会变动的。我们简单的称呼这个标识符为Node ID。
创建集群
现在我们有很多实例在运行,我们需要通过向节点写入一些有意义的配置来创建我们的集群。
这是很容易完成的,因为我们可以使用Redis集群命令行工具redis-trib的帮助,Ruby程序在实例上执行特殊的命令来创建新集群,检查或reshard现有的集群,等等。
redis-trib实用工具在Redis源码发行版中src目录内。你需要安装redis gem才能运行redis-trib。
gem install redis
要创建集群只需输入:
./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个可用槽提供服务。