Redis Cluster集群模式的原理与搭建


        Redis 3.0之后,redis节点之间通过去中心化的方式,提供了完整的sharding分片replication主从复制(复制机制仍使用原有机制,并且具备感知主备的能力)、failover故障容错的解决方案,该方案称为Redis Cluster。即:将proxy代理/sentinel哨兵的工作融合到了普通Redis节点里,下面将介绍Redis Cluster这种模式下,水平拆分、故障转移等需求的实现方式。

 

一、Redis Cluster的拓扑结构


        一个Redis Cluster由多个Redis节点组成。不同的节点组服务的数据无交集,每个节点对应数据sharding的一个分片。节点组内部分为主和备这两类,对应前面叙述的master和slave。两者数据准实时一致,通过异步化的主备复制机制保证。一个节点组有且仅有一个master,同时有0到多个slave。只有master对外提供写服务,读服务可由master/slave提供。如下所示:

Redis Cluster集群模式的原理与搭建

上图中,key-value全部被分成了5份,5个slot槽(实际上Redis Cluster有 16384 [0-16383] 个slot,每个节点服务一段区间的slot,这里面仅仅举例)。A和B为master节点,对外提供写服务。分别负责1/2/3和4/5的slot。A/A1和B/B1/B2之间通过主备复制的方式同步数据。

上述的5个节点,每两个节点之间通过Redis Cluster Bus交互,相互交换如下的信息:

  • 数据分片(slot)和节点的对应关系;
  • 集群中每个节点可用状态;
  • 集群结构发生变更时,通过一定的协议对配置信息达成一致。数据分片的迁移、主备切换、单点master的发现和其发生主备关系变更等,都会导致集群结构变化。
  • publish/subscribe(发布订阅)功能,在Cluster版内部实现所需要交互的信息。

Redis Cluster Bus通过单独的端口进行连接,由于Bus是节点间的内部通信机制,交互的是字节序列化信息。相对Client的字符序列化来说,效率较高。

Redis Cluster是一个去中心化的分布式实现方案,客户端和集群中任一节点连接,然后通过后面的交互流程,逐渐的得到全局的数据分片映射关系。

 

二、Redis Cluster配置的一致性


       对于去中心化的实现,集群的拓扑结构并不保存在单独的配置节点上,后者的引入同样会带来新的一致性问题。那么孤立的节点间,如何对集群的拓扑达成一致,是Redis Cluster配置机制要解决的问题。Redis Cluster通过引入2个自增的Epoch变量,来使得集群配置在各个节点间最终达成一致。

1、配置信息数据结构


Redis Cluster中的每个节点都保存了集群的配置信息,并且存储在clusterState中,结构如下:

Redis Cluster集群模式的原理与搭建

上图的各个变量语义如下:

  • clusterState 记录了从集群中某个节点视角,来看集群配置状态;
  • currentEpoch 表示整个集群中最大的版本号,集群信息每变更一次,改版本号都会自增。
  • nodes 是一个列表,包含了本节点所感知的,集群所有节点的信息(clusterNode),也包含自身的信息。
  • clusterNode 记录了每个节点的信息,其中包含了节点本身的版本 Epoch;自身的信息描述:节点对应的数据分片范围(slot)、为master时的slave列表、为slave时的master等。

每个节点包含一个全局唯一的NodeId

当集群的数据分片信息发生变更(数据在节点间迁移时),Redis Cluster 仍然保持对外服务。

当集群中某个master出现宕机时,Redis Cluster 会自动发现,并触发故障转移的操作。会将master的某个slave晋升为新的 master。

由此可见,每个节点都保存着Node视角的集群结构。它描述了数据的分片方式,节点主备关系,并通过Epoch 作为版本号实现集群结构信息的一致性,同时也控制着数据迁移和故障转移的过程。

2、信息交互


去中心化的架构不存在统一的配置中心。在Redis Cluster中,这个配置信息交互通过Redis Cluster Bus来完成(独立端口)。Redis Cluster Bus 上交互的信息结构如下:

Redis Cluster集群模式的原理与搭建

clusterMsg 中的type指明了消息的类型,配置信息的一致性主要依靠PING/PONG。每个节点向其他节点频繁的周期性的发送PING/PONG消息。对于消息体中的Gossip部分,包含了sender/receiver 所感知的其他节点信息,接受者根据这些Gossip 跟新对集群的认识。

对于大规模的集群,如果每次PING/PONG 都携带着所有节点的信息,则网络开销会很大。此时Redis Cluster 在每次PING/PONG,只包含了随机的一部分节点信息。由于交互比较频繁,短时间的几次交互之后,集群的状态也会达成一致。

3、一致性的达成


当Cluster 结构不发生变化时,各个节点通过gossip 协议在几轮交互之后,便可以得知Cluster的结构信息,达到一致性的状态。但是当集群结构发生变化时(故障转移/分片迁移等),优先得知变更的节点通过Epoch变量,将自己的最新信息扩散到Cluster,并最终达到一致。

  • clusterNode 的Epoch描述的单个节点的信息版本;
  • clusterState 的currentEpoch 描述的是集群信息的版本,它可以辅助Epoch 的自增生成。因为currentEpoch 是维护在每个节点上的,在集群结构发生变更时,Cluster 在一定的时间窗口控制更新规则,来保证每个节点的currentEpoch都是最新的。

更新规则如下:

  1. 当某个节点率先知道了变更时,将自身的currentEpoch 自增,并使之成为集群中的最大值。再用自增后的currentEpoch 作为新的Epoch 版本;
  2. 当某个节点收到了比自己大的currentEpoch时,更新自己的currentEpoch;
  3. 当收到的Redis Cluster Bus 消息中的某个节点的Epoch > 自身的时,将更新自身的内容;
  4. 当Redis Cluster Bus 消息中,包含了自己没有的节点时,将其加入到自身的配置中。

上述的规则保证了信息的更新都是单向的,最终朝着Epoch更大的信息收敛。同时Epoch也随着currentEpoch的增加而增加,最终将各节点信息趋于稳定。

三、Redis Cluster的sharding分片原理


不同节点分组服务于相互无交集的分片(sharding),Redis Cluster 不存在单独的proxy或配置服务器,所以需要将客户端路由到目标的分片。

1、数据分片(slot)


Redis Cluster 将所有的数据划分为16384 [0-16383] 个分片,每个分片负责其中一部分。每一条数据(key-value)根据key值通过数据分布算法(一致性哈希)映射到16384 个slot中的一个。数据分布算法为:

slotId = crc16(key) % 16384


       客户端根据slotId 决定将请求路由到哪个Redis 节点。Cluster 不支持跨节点的单命令,如:sinterstore,如果涉及的2个key对应的slot 在不同的Node,则执行失败。通常Redis的key都是带有业务意义的, 

如:Product:Trade:20180890310921230001Product:Detail:20180890310921230001。当在集群中存储时,上述同一商品的交易和详情可能会存储在不同的节点上,进而对于这2个key 不能以原子的方式操作。为此,Redis引入了HashTag的概念,使得数据分布算法可以根据key 的某一部分进行计算,让相关的2 条记录落到同一个数据分片。如:

  • 商品交易记录key:Product:Trade:{20180890310921230001}
  • 商品详情记录key:Product:Detail:{20180890310921230001}

Redis 会根据 { } 符号 之间的字符串作为数据分布式算法的输入。

2、客户端的路由


        Redis Cluster的客户端相比单机Redis 需要具备路由语义的识别能力,且具备一定的路由缓存能力。当Client 访问的key 不在当前Redis 节点的slots中,Redis 会返回给Client 一个moved命令。并告知其正确的路由信息,如下所示:

Redis Cluster集群模式的原理与搭建

当Client 接收到moved 后,再次请求新的Redis时,此时Cluster 的结构又可能发生了变化。此时有可能再次返回moved 。Client 会根据moved响应,更新其内部的路由缓存信息,以便后续的操作直接找到正确的节点,减少交互次数。

当Cluster 在数据重新分布过程中时,可以通过ask 命令控制客户端的路由,如下所示:

Redis Cluster集群模式的原理与搭建

上图中,slot 1 需要迁移到新节点上,此时如果客户端已经完成迁移的key,节点将相应ask 告知客户端想目标节点重试。

ask命令和moved 命令的不同在于,moved 会更新Client数据路由,ask 只是重定向新节点,但是后续的相同slot 仍会路由到旧节点。

迁移的过程可能会持续一段时间,这段时间某个slot 的数据,同时可能存在于新旧 2 个节点。由于move 操作会使Client 的路由缓存变更,如果新旧节点对于迁移中的slot 所有key 都回应moved,客户端的路由缓存会频繁变更。因此引入ask 类型消息,将重定向和路由缓存分离。

 

3、分片的迁移


在一个稳定的Redis Cluster 中,每个slot 对应的节点都是确定的。在某些情况下,节点和分片需要变更:

  • 新的节点作为master加入;
  • 某个节点分组需要下线;
  • 负载不均衡需要调整slot 分布。

此时需要进行分片的迁移,迁移的触发和过程控制由外部系统完成。Redis Cluster 只提供迁移过程中需要的原语,包含下面 2 种:

  • 节点迁移状态设置:迁移前标记源/目标节点。
  • key迁移的原子化命令:迁移的具体步骤。

下面的Demo会介绍slot 1 从节点A 迁移到B的过程。

Redis Cluster集群模式的原理与搭建

  1. 向节点B发送状态变更命令,将B的对应slot 状态置为importing
  2. 向节点A发送状态变更命令,将A对应的slot 状态置为migrating
  3. 针对A上的slot 的所有key,分别向A 发送migrate 命令,告知A 将对应的key 迁移到B。

当A节点的状态置为migrating 后,表示对应的slot 正在从A迁出,为保证该slot 数据的一致性。A此时提供的写服务和通常状态下有所区别,对于某个迁移中的slot:

  • 如果Client 访问的key 尚未迁出,则正常的处理该key;
  • 如果key已经迁出或者key不存在,则回复Client ASK 信息让其跳转到B处理;

当节点B 状态变成importing 后,表示对应的slot 正在向B迁入。即使B 能对外提供该slot 的读写服务,但是和通常情况下有所区别:

  • 当Client的访问不是从ask 跳转的,说明Client 还不知道迁移。有可能操作了尚未迁移完成的,处在A上面的key,如果这个key 在A上被修改了,则后续会产生冲突。
  • 所以对于该slot 上所有非ask 跳转的操作,B不会进行操作,而是通过moved 让Client 跳转至A执行。

       这样的状态控制,保证了同一个key 在迁移之前总是在源节点执行。迁移后总是在目标节点执行,从而杜绝了双写的冲突。迁移过程中,新增加的key 会在目标节点执行,源节点不会新增key。使得迁移有界限,可以在某个确定的时刻结束。

单个key 的迁移过程可以通过原子化的migrate 命令完成。对于A/B的slave 节点,是通过主备复制,从而达到增删数据。

当所有key 迁移完成后,Client 通过 cluster setslot 命令设置B的分片信息,从而包含了迁入的slot。设置过程中会让Epoch自增,并且是Cluster 中的最新值。然后通过相互感知,传播到Cluster 中的其他节点。

 

四、Redis Cluster的failover故障容错原理


同Sentinel 一样,Redis Cluster 也具备一套完整的故障发现、故障状态一致性保证、主备切换机制。

1、failover的状态变迁

 

  1. 故障发现:当某个master 宕机时,宕机时间如何被集群其他节点感知。
  2. 故障确认:多个节点就某个master 是否宕机如何达成一致。
  3. slave选举:集群确认了某个master 宕机后,如何将它的slave 升级成新的master;如果有多个slave,如何选择升级。
  4. 集群结构变更:成功选举成为master后,如何让整个集群知道,以更新Cluster 结构信息。

2、故障发现


        Redis Cluster 节点间通过Redis Cluster Bus 两两周期性的PING/PONG 交互。当某个节点宕机时,其他Node 发出的PING消息没有收到响应,并且超过一定时间(NODE_TIMEOUT)未收到,则认为该节点故障,将其置为PFAIL状态(Possible Fail)。后续通过Gossip 发出的PING/PONG消息中,这个节点的PFAIL 状态会传播到集群的其他节点。

        Redis Cluster 的节点两两通过TCP 保持Redis Cluster Bus连接,当对PING 无反馈时,可能是节点故障,也可能是TCP 链接断开。如果是TCP 断开导致的误报,虽然误报消息会因为其他节点的正常连接被忽略,但是也可以通过一定的方式减少误报。Redis Cluster 通过 预重试机制 排除此类误报:当 NODE_TIMEOUT/2 过去了,但是还未收到响应,则重新连接重发PING 消息,如果对端正常,则在很短的时间内就会有响应。

3、故障确认


        对于网络分隔的情况,某个节点(B)并没有故障,但是和A 无法连接,但是和C/D 等其他节点可以正常联通。此时只会有A 将 B 标记为PFAIL 状态,其他节点认为B 正常。此时A 和C/D 等其他节点信息不一致,Redis Cluster 通过故障 确认协议 达成一致。

       集群中每个节点都是Gossip的接收者,A 也会接收到来自其他节点的Gossip 消息,被告知B 是否处于PFAIL 状态。当A收到来气其他master 节点对于 B 的PFAIL 达到一定数量后,会将B的PFAIL 状态升级为 FAIL 状态。表示B 已经确认为故障态,后面会发起slave 选举流程。

A节点内部的集群信息中,对于B的状态从PFAIL 到 FAIL 的变迁,如下图所示:

Redis Cluster集群模式的原理与搭建

 

4、slave选举


上图中,B是A的master,并且B 已经被集群公认是FAIL 状态了,那么A 发起竞选,期望成为新的master。

如果B 有多个slave (A/E/F)都认知到B 处于FAIL 状态了,A/E/F 可能会同时发起竞选。当B的slave 个数 >= 3 时,很有可能产生多轮竞选失败。为了减少冲突的出现,优先级高的slave 更有可能发起竞选,从而提升成功的可能性。这里的优先级是slave的数据最新的程度,数据越新的(最完整的)优先级越高。

slave 通过向其他master发送FAILVOER_AUTH_REQUEST 消息发起竞选,master 收到后回复FAILOVER_AUTH_ACK 消息告知是否同意。slave 发送FAILOVER_AUTH_REQUEST 前会将currentEpoch 自增,并将最新的Epoch 带入到FAILOVER_AUTH_REQUEST 消息中,如果自己未投过票,则回复同意,否则回复拒绝。

5、结构变更通知


当slave 收到过半的master 同意时,会替代B 成为新的master。此时会以最新的Epoch 通过PONG 消息广播自己成为master,让Cluster 的其他节点尽快的更新拓扑结构。

当B 恢复可用之后,它手续爱你仍然认为自己是master,但逐渐的通过Gossip 协议得知A 已经替代了自己,然后降级为A的slave。

五、Redis Cluster高可用性、性能和节点通信机制


Redis Cluster 还提供了一些方法可以提升性能和高可用性。

1、Redis Cluster的读写分离


      对于读写分离的场景,应用对于某些读请求允许舍弃一定的数据一致性,以换取更高的吞吐量。此时希望将读请求交给slave处理,以分担master的压力。

通过分片映射关系,某个slot 一定对应着一个master节点。Client 通过moved 命令,也只会路由到各个master中。即使Client 将请求直接发送到slave上,也会回复moved 到master去处理。

为此,Redis Cluster 引入了readonly 命令。Client 向slave发送该命令后,不再moved 到master处理,而是自己处理,这成为slave的readonly 模式。通过readwrite命令,可以将slave的readonly模式重置。

2、master单点保护


假如Cluster 的初始状态如下所示:

Redis Cluster集群模式的原理与搭建

上图中A、B两个master 分别有自己的slave,假设A1 发生宕机,结构变为如下所示:

Redis Cluster集群模式的原理与搭建

此时A 成为了单点,一旦A 再次宕机,将造成不可用。此时Redis Cluster 会把B 的某个slave (如 B1 )进行副本迁移,变成A的slave。如下所示:

Redis Cluster集群模式的原理与搭建

这样集群中每个master 至少有一个slave,使得Cluster 具有高可用。集群中只需要保持 2*master+1 个节点,就可以保持任一节点宕机时,故障转移后继续高可用。
 

3.判断节点宕机

  如果一个节点认为另外一个节点宕机,那么就是pfail,主观宕机。

  如果多个节点都认为另外一个节点宕机了,那么就是fail,客观宕机,跟哨兵的原理几乎一样,sdown,odown。

  在cluster-node-timeout内,某个节点一直没有返回pong,那么就被认为pfail。

  如果一个节点认为某个节点pfail了,那么会在gossip ping消息中,ping给其他节点,如果超过半数的节点都认为pfail了,那么就会变成fail。

 

4.从节点过滤

  对宕机的master node,从其所有的slave node中,选择一个切换成master node。

  检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master。

  这个也是跟哨兵是一样的,从节点超时过滤的步骤。

5.从节点选举

  哨兵:对所有从节点进行排序,slave priority,offset,run id。

  每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。

  所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master。

  从节点执行主备切换,从节点切换为主节点。

6.与哨兵比较

  整个流程跟哨兵相比,非常类似,所以说,redis cluster功能强大,直接集成了replication和sentinal的功能。

 

7.节点间的内部通信机制

7.1、基础通信原理

(1)redis cluster节点间采取gossip协议进行通信:

  跟集中式不同,不是将集群元数据(节点信息,故障,等等)集中存储在某个节点上,而是互相之间不断通信,保持整个集群所有节点的数据是完整的。

  •   集中式:好处在于,元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候立即就可以感知到; 不好在于,所有的元数据的跟新压力全部集中在一个地方,可能会导致元数据的存储有压力。
  •   gossip:好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力; 缺点,元数据更新有延时,可能导致集群的一些操作会有一些滞后。

 

(2)10000端口

  每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口。

  每隔节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong。

 

(3)交换的信息

  故障信息,节点的增加和移除,hash slot信息,等等。

 

7.2、gossip协议

  •   gossip协议包含多种消息,包括ping,pong,meet,fail,等等。
  •   meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信。
  •   redis-trib.rb add-node:其实内部就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群。
  •   ping: 每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据每个节点每秒都会频繁发送ping给其他的集群,ping,频繁的互相之间交换数据,互相进行元数据的更新。
  •   pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新。
  •   fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。

 

7.3、ping消息深入

  1.   ping很频繁,而且要携带一些元数据,所以可能会加重网络负担。
  2.   每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点。
  3.   当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了。
  4.   比如说,两个节点之间都10分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。
  5.   所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送的频率。
  6.   每次ping,一个是带上自己节点的信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换。
  7.   至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息。

 

六、Redis集群方案

Redis Cluster 集群模式通常具有 高可用可扩展性分布式容错 等特性。Redis 分布式方案一般有两种:

1.1 客户端分区方案

客户端 就已经决定数据会被 存储 到哪个 redis 节点或者从哪个 redis 节点 读取数据。其主要思想是采用 哈希算法 将 Redis 数据的 key 进行散列,通过 hash 函数,特定的 key会 映射 到特定的 Redis 节点上。

Redis Cluster集群模式的原理与搭建

客户端分区方案 的代表为 Redis Sharding,Redis Sharding 是 Redis Cluster 出来之前,业界普遍使用的 Redis 多实例集群 方法。Java 的 Redis 客户端驱动库 Jedis,支持 Redis Sharding 功能,即 ShardedJedis 以及 结合缓存池 的 ShardedJedisPool。

优点

不使用 第三方中间件分区逻辑 可控,配置 简单,节点之间无关联,容易 线性扩展,灵活性强。

缺点

客户端 无法 动态增删 服务节点,客户端需要自行维护 分发逻辑,客户端之间 无连接共享,会造成 连接浪费

1.2. 代理分区方案

客户端 发送请求到一个 代理组件代理 解析 客户端 的数据,并将请求转发至正确的节点,最后将结果回复给客户端。

优点:简化 客户端 的分布式逻辑,客户端 透明接入,切换成本低,代理的 转发 和 存储 分离。

缺点:多了一层 代理层,加重了 架构部署复杂度 和 性能损耗

Redis Cluster集群模式的原理与搭建

代理分区 主流实现的有方案有 Twemproxy 和 Codis。

1.2.1. Twemproxy

Twemproxy 也叫 nutcraker,是 twitter 开源的一个 redis 和 memcache 的 中间代理服务器 程序。Twemproxy 作为 代理,可接受来自多个程序的访问,按照 路由规则,转发给后台的各个 Redis 服务器,再原路返回。Twemproxy 存在 单点故障 问题,需要结合 Lvs 和 Keepalived 做 高可用方案

Redis Cluster集群模式的原理与搭建

优点:应用范围广,稳定性较高,中间代理层 高可用

缺点:无法平滑地 水平扩容/缩容,无 可视化管理界面,运维不友好,出现故障,不能 自动转移

1.2.2. Codis

Codis 是一个 分布式 Redis 解决方案,对于上层应用来说,连接 Codis-Proxy 和直接连接 原生的 Redis-Server 没有的区别。Codis 底层会 处理请求的转发,不停机的进行 数据迁移 等工作。Codis 采用了无状态的 代理层,对于 客户端 来说,一切都是透明的。

Redis Cluster集群模式的原理与搭建

优点

实现了上层 Proxy 和底层 Redis 的 高可用数据分片 和 自动平衡,提供 命令行接口 和 RESTful API,提供 监控 和 管理 界面,可以动态 添加 和 删除 Redis 节点。

缺点

部署架构 和 配置 复杂,不支持 跨机房 和 多租户,不支持 鉴权管理

1.3. 查询路由方案

客户端随机地 请求任意一个 Redis 实例,然后由 Redis 将请求 转发 给 正确 的 Redis 节点。Redis Cluster 实现了一种 混合形式 的 查询路由,但并不是 直接 将请求从一个 Redis 节点 转发 到另一个 Redis 节点,而是在 客户端 的帮助下直接 重定向( redirected)到正确的 Redis 节点。

Redis Cluster集群模式的原理与搭建

优点

无中心节点,数据按照  存储分布在多个 Redis 实例上,可以平滑的进行节点 扩容/缩容,支持 高可用 和 自动故障转移,运维成本低。

缺点

严重依赖 Redis-trib 工具,缺乏 监控管理,需要依赖 Smart Client (维护连接缓存路由表,MultiOp 和 Pipeline 支持)。Failover 节点的 检测过慢,不如 中心节点 ZooKeeper 及时。Gossip 消息具有一定开销。无法根据统计区分 冷热数据

七、数据分布

2.1. 数据分布理论

        分布式数据库 首先要解决把 整个数据集 按照 分区规则 映射到 多个节点 的问题,即把 数据集 划分到 多个节点 上,每个节点负责 整体数据 的一个 子集

Redis Cluster集群模式的原理与搭建

      数据分布通常有 哈希分区 和 顺序分区 两种方式,对比如下:

       分区方式 特点 相关产品 哈希分区 离散程度好,数据分布与业务无关,无法顺序访问 Redis Cluster,Cassandra,Dynamo 顺序分区 离散程度易倾斜,数据分布与业务相关,可以顺序访问 BigTable,HBase,Hypertable 由于 Redis Cluster 采用 哈希分区规则,这里重点讨论 哈希分区。常见的 哈希分区 规则有几种,下面分别介绍:

2.1.1. 节点取余分区

使用特定的数据,如 Redis 的  或 用户 ID,再根据 节点数量 N 使用公式:hash(key)% N 计算出 哈希值,用来决定数据 映射 到哪一个节点上。

Redis Cluster集群模式的原理与搭建

优点

这种方式的突出优点是 简单性,常用于 数据库 的 分库分表规则。一般采用 预分区 的方式,提前根据 数据量 规划好 分区数,比如划分为 512 或 1024 张表,保证可支撑未来一段时间的 数据容量,再根据 负载情况 将  迁移到其他 数据库 中。扩容时通常采用 翻倍扩容,避免 数据映射 全部被 打乱,导致 全量迁移 的情况。

缺点

当 节点数量 变化时,如 扩容 或 收缩 节点,数据节点 映射关系 需要重新计算,会导致数据的 重新迁移

2.1.2. 一致性哈希分区

一致性哈希 可以很好的解决 稳定性问题,可以将所有的 存储节点 排列在 收尾相接 的 Hash 环上,每个 key 在计算 Hash 后会 顺时针 找到 临接 的 存储节点 存放。而当有节点 加入 或 退出 时,仅影响该节点在 Hash 环上 顺时针相邻 的 后续节点

Redis Cluster集群模式的原理与搭建

优点

加入 和 删除 节点只影响 哈希环 中 顺时针方向 的 相邻的节点,对其他节点无影响。

缺点

加减节点 会造成 哈希环 中部分数据 无法命中。当使用 少量节点 时,节点变化 将大范围影响 哈希环 中 数据映射,不适合 少量数据节点 的分布式方案。普通 的 一致性哈希分区 在增减节点时需要 增加一倍 或 减去一半 节点才能保证 数据 和 负载的均衡

注意:因为 一致性哈希分区 的这些缺点,一些分布式系统采用 虚拟槽 对 一致性哈希 进行改进,比如 Dynamo 系统。

2.1.3. 虚拟槽分区

虚拟槽分区 巧妙地使用了 哈希空间,使用 分散度良好 的 哈希函数 把所有数据 映射 到一个 固定范围 的 整数集合 中,整数定义为 (slot)。这个范围一般 远远大于 节点数,比如 Redis Cluster 槽范围是 0 ~ 16383。 是集群内 数据管理 和 迁移 的 基本单位。采用 大范围槽 的主要目的是为了方便 数据拆分 和 集群扩展。每个节点会负责 一定数量的槽,如图所示:

Redis Cluster集群模式的原理与搭建

当前集群有 5 个节点,每个节点平均大约负责 3276 个 。由于采用 高质量 的 哈希算法,每个槽所映射的数据通常比较 均匀,将数据平均划分到 5 个节点进行 数据分区。Redis Cluster 就是采用 虚拟槽分区

节点1: 包含 0 到 3276 号哈希槽。

节点2:包含 3277 到 6553 号哈希槽。

节点3:包含 6554 到 9830 号哈希槽。

节点4:包含 9831 到 13107 号哈希槽。

节点5:包含 13108 到 16383 号哈希槽。

这种结构很容易 添加 或者 删除 节点。如果 增加 一个节点 6,就需要从节点 1 ~ 5 获得部分  分配到节点 6 上。如果想 移除 节点 1,需要将节点 1 中的  移到节点 2 ~ 5 上,然后将 没有任何槽 的节点 1 从集群中 移除 即可。

由于从一个节点将 哈希槽 移动到另一个节点并不会 停止服务,所以无论 添加删除或者 改变 某个节点的 哈希槽的数量 都不会造成 集群不可用 的状态.

2.2. Redis的数据分区

Redis Cluster 采用 虚拟槽分区,所有的  根据 哈希函数 映射到 0~16383 整数槽内,计算公式:slot = CRC16(key)& 16383。每个节点负责维护一部分槽以及槽所映射的 键值数据,如图所示:

Redis Cluster集群模式的原理与搭建

2.2.1. Redis虚拟槽分区的特点

     解耦 数据 和 节点 之间的关系,简化了节点 扩容 和 收缩 难度。

     节点自身 维护槽的 映射关系,不需要 客户端 或者 代理服务 维护 槽分区元数据

     支持 节点 之间的 映射查询,用于 数据路由在线伸缩 等场景。

2.3. Redis集群的功能限制

Redis 集群相对 单机 在功能上存在一些限制,需要 开发人员 提前了解,在使用时做好规避。

key 批量操作 支持有限。

类似 mset、mget 操作,目前只支持对具有相同 slot 值的 key 执行 批量操作。对于 映射为不同 slot 值的 key 由于执行 mget、mget 等操作可能存在于多个节点上,因此不被支持。

key 事务操作 支持有限。

只支持  key 在 同一节点上 的 事务操作,当多个 key 分布在 不同 的节点上时 无法 使用事务功能。

key 作为 数据分区 的最小粒度

不能将一个 大的键值 对象如 hash、list 等映射到 不同的节点

不支持 多数据库空间

单机 下的 Redis 可以支持 16 个数据库(db0 ~ db15),集群模式 下只能使用 一个 数据库空间,即 db0。

复制结构 只支持一层

从节点 只能复制 主节点,不支持 嵌套树状复制 结构。

 

八、 Redis集群搭建

Redis-Cluster 是 Redis 官方的一个 高可用 解决方案,Cluster 中的 Redis 共有 2^14(16384) 个 slot 。创建 Cluster 后, 会 平均分配 到每个 Redis 节点上。

下面介绍一下本机启动 6 个 Redis 的 集群服务,并使用 redis-trib.rb 创建 3主3从 的 集群。搭建集群工作需要以下三个步骤:

1. 准备节点

Redis 集群一般由 多个节点 组成,节点数量至少为 6 个,才能保证组成 完整高可用 的集群。每个节点需要 开启配置          cluster-enabled yes,让 Redis 运行在 集群模式 下。

Redis 集群的节点配置规划如下:

   1.节点名称    2.端口号       3.是主是从      4.所属主节点

举例子:

   节点1: redis-6379      6379         主节点

   节点2: redis-6389     6389          从节点       redis-6379

   节点3:redis-6380     6380          主节点

   节点4: redis-6390     6390          从节点       redis-6380

   节点5: redis-6381      6381          主节点

   节点6: redis-6391      6391          从节点       redis-6381 

注意:建议为集群内 所有节点 统一目录,一般划分三个目录:conf、data、log,分别存放 配置数据和 日志 相关文件。把 6 个节点配置统一放在 conf 目录下。

 

1.1. 创建redis各实例目录

    Linux命令:$sudo mkdir -p /usr/local/redis-cluster   

                       $cd/usr/local/redis-cluster

                       $sudo mkdir conf datalog

                       $ sudo mkdir -p data/redis-6379 data/redis-6389 data/redis-6380 data/redis-6390 data/redis-6381 data/redis- 6391

 

1.2. redis配置文件管理

      根据以下 模板 配置各个实例的 redis.conf,以下只是搭建集群需要的 基本配置,可能需要根据实际情况做修改。

  •  redis后台运行  daemonize  yes
  •  绑定的主机端口  bind 127.0.0.1
  •  数据存放目录  dir /usr/local/redis-cluster/data/redis-6379
  •  进程文件 pidfile /var/run/redis-cluster/${自定义}.pid
  •  日志文件 logfile /usr/local/redis-cluster/log/${自定义}.log
  •   端口号 port 6379
  •  开启集群模式,把注释#去掉cluster-enabled yes
  •  集群的配置,配置文件首次启动自动生成 cluster-config-file  /usr/local/redis-cluster/conf/${自定义}.conf
  •  请求超时,设置10秒cluster-node-timeout 10000
  •  aof日志开启,有需要就开启,它会每次写操作都记录一条日志appendonly  yes

 

1.3. 修改redis集群中每个节点的配置

   redis-6379.conf

daemonize yes 、 bind 127.0.0.1 、 dir /usr/local/redis-cluster/data/redis-6379 、 pidfile /var/run/redis-cluster/redis-6379.pid、 logfile /usr/local/redis-cluster/log/redis-6379.log、port 6379、cluster-enabled yes、cluster-config-file /usr/local/redis-cluster/conf/node-6379.conf、cluster-node-timeout 10000、appendonly yes

redis-6389.conf

daemonize yes 、bind 127.0.0.1、dir /usr/local/redis-cluster/data/redis-6389、 pidfile /var/run/redis-cluster/redis-6389.pid、logfile /usr/local/redis-cluster/log/redis-6389.log、port 6389、cluster-enabled yes、cluster-config-file/usr/local/redis-cluster/conf/node-6389.conf、cluster-node-timeout 10000、appendonly yes

redis-6380.conf

daemonize yes、bind 127.0.0.1、dir /usr/local/redis-cluster/data/redis-6380、pidfile /var/run/redis-cluster/redis-6380.pid、logfile /usr/local/redis-cluster/log/redis-6380.log、port 6380、cluster-enabled yes、cluster-config-file/usr/local/redis-cluster/conf/node-6380.conf、cluster-node-timeout 10000、appendonly yes

redis-6390.conf

daemonize yes、bind 127.0.0.1、dir /usr/local/redis-cluster/data/redis-6390、pidfile /var/run/redis-cluster/redis-6390.pid、logfile /usr/local/redis-cluster/log/redis-6390.log、port 6390、cluster-enabled yes、cluster-config-file/usr/local/redis-cluster/conf/node-6390.conf、cluster-node-timeout 10000、appendonly yes

redis-6381.conf

daemonize yes、bind 127.0.0.1、dir /usr/local/redis-cluster/data/redis-6381、pidfile /var/run/redis-cluster/redis-6381.pid、logfile /usr/local/redis-cluster/log/redis-6381.log、port 6381、cluster-enabled yes、cluster-config-file/usr/local/redis-cluster/conf/node-6381.conf、cluster-node-timeout 10000、appendonly yes

redis-6391.conf

daemonize yes、bind 127.0.0.1、dir/usr/local/redis-cluster/data/redis-6391、pidfile /var/run/redis-cluster/redis-6391.pid、logfile /usr/local/redis-cluster/log/redis-6391.log、port 6391、cluster-enabled yes、cluster-config-file/usr/local/redis-cluster/conf/node-6391.conf、cluster-node-timeout 10000、appendonly yes

2. 环境准备

2.1. 安装Ruby环境

$ sudobrew install ruby

2.2. 准备rubygem redis依赖

linux命令:$ sudo geminstall redis Password:Fetching: redis-4.0.2.gem (100%)Successfully installed redis-4.0.2 Parsing documentation for redis-4.0.2 Installing ri   documentation for redis-4.0.2 Done installing    documentation for redis after 1 seconds 1 gem installed

2.3. 拷贝redis-trib.rb到集群根目录

redis-trib.rb 是 redis 官方推出的管理 redis 集群 的工具,集成在 redis 的源码 src 目录下,将基于 redis 提供的 集群命令 封装成 简单便捷实用 的 操作工具

$ sudo cp/usr/local/redis-4.0.11/src/redis-trib.rb /usr/local/redis-cluster

查看 redis-trib.rb 命令环境是否正确,输出如下:

$ ./redis-trib.rb Usage:redis-trib create host1:port1 ...hostN:portN--replicascheck host:portinfo host:portfix host:port--timeoutreshard host:port--from--to--slots--yes--timeout--pipelinerebalance host:port--weight--auto-weights--use-empty-masters--timeout--simulate--pipeline--thresholdadd-node new_host:new_port existing_host:existing_port--slave--master-iddel-node host:port node_idset-timeouthost:port millisecondscall host:port command arg arg ..argimport host:port--from--copy--replacehelp (show this help)For check,fix,reshard,del-node,set-timeoutyou can specify the host and port of any working node in the cluster.

redis-trib.rb 是 redis 作者用 ruby 完成的。redis-trib.rb 命令行工具 的具体功能如下:

命令作用: create 创建集群 、check 检查集群 、info 查看集群信息、 fix 修复集群、 reshard 在线迁移、slot rebalance 平衡集群节点、slot数量 、add-node 将新节点加入集群 、del-node 从集群中删除节点 、set-timeout 设置集群节点间心跳连接的超时时间、 call 在集群全部节点上执行命令 、import 将外部redis数据导入集群 。

 

3. 安装集群

3.1. 启动redis服务节点

运行如下命令启动 6 台 redis 节点:

sudo redis-serverconf/redis-6379.conf   sudo redis-serverconf/redis-6389.conf    sudo redis-serverconf/redis-6380.conf        sudo redis-serverconf/redis-6390.conf   sudo redis-serverconf/redis-6381.conf    sudo redis-serverconf/redis-6391.conf

启动完成后,redis 以集群模式启动,查看各个 redis 节点的进程状态:

$ ps -ef | grep redis-server

0 1908 1 0 4:59下午 ?? 0:00.01 redis-server *:6379 [cluster]

0 1911 1 0 4:59下午 ?? 0:00.01 redis-server *:6389 [cluster]

0 1914 1 0 4:59下午 ?? 0:00.01 redis-server *:6380 [cluster]

0 1917 1 0 4:59下午 ?? 0:00.01 redis-server *:6390 [cluster]

0 1920 1 0 4:59下午 ?? 0:00.01 redis-server *:6381 [cluster]

0 1923 1 0 4:59下午 ?? 0:00.01 redis-server *:6391 [cluster] 

在每个 redis 节点的 redis.conf 文件中,我们都配置了 cluster-config-file 的文件路径,集群启动时,conf 目录会新生成 集群 节点配置文件。查看文件列表如下:

$ tree -L3..├── appendonly.aof├── conf│ ├── node-6379.conf│ ├── node-6380.conf│ ├── node-6381.conf│ ├── node-6389.conf│ ├── node-6390.conf│ ├── node-6391.conf│ ├── redis-6379.conf│ ├── redis-6380.conf│ ├── redis-6381.conf│ ├── redis-6389.conf│ ├── redis-6390.conf│ └── redis-6391.conf├── data│ ├── redis-6379│ ├── redis-6380│ ├── redis-6381│ ├── redis-6389│ ├── redis-6390│ └── redis-6391├── log│ ├── redis-6379.log│ ├── redis-6380.log│ ├── redis-6381.log│ ├── redis-6389.log│ ├── redis-6390.log│ └── redis-6391.log└── redis-trib.rb9directories,20files。

 

3.2. redis-trib关联集群节点

按照 从主到从 的方式 从左到右 依次排列 6 个 redis 节点。

$ sudo ./redis-trib.rb create --replicas1127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6389 127.0.0.1:6390 127.0.0.1:6391 

集群创建后,redis-trib 会先将 16384 个 哈希槽 分配到 3 个 主节点,即 redis-6379,redis-6380 和 redis-6381。然后将各个 从节点 指向 主节点,进行 数据同步

>>>Creatingcluster>>>Performing hash slotsal location on  6 nodes...Using  3  masters:

127.0.0.1:6379    127.0.0.1:6380     127.0.0.1:6381

Addingreplica127.0.0.1:6390 to 127.0.0.1:6379

Addingreplica127.0.0.1:6391 to 127.0.0.1:6380

Addingreplica127.0.0.1:6389 to 127.0.0.1:6381

>>>Trying tooptimize slaves allocation for anti-affinity[WARNING]Some slaves are in the same host as their masterM:ad4b9ffceba062492ed67ab336657426f55874b7 127.0.0.1:6379    slots:0-5460(5461slots)

masterM:df23c6cad0654ba83f0422e352a81ecee822702e 127.0.0.1:6380    slots:546110922(5462slots)

masterM:ab9da92d37125f24fe60f1f33688b4f8644612ee 127.0.0.1:6381      slots:10923-16383(5461slots)

masterS:25cfa11a2b4666021da5380ff332b80dbda97208127.0.0.1:6389   replicates        ad4b9ffceba062492ed67ab336657426f55874b7

S:48e0a4b539867e01c66172415d94d748933be173  127.0.0.1:6390 replicates   df23c6cad0654ba83f0422e352a81ecee822702e

S:d881142a8307f89ba51835734b27cb309a0fe855 127.0.0.1:6391     replicates   ab9da92d37125f24fe60f1f33688b4f8644612ee

然后输入 yes,redis-trib.rb 开始执行 节点握手 和 槽分配 操作,输出如下:

CanIset the above configuration? (type'yes'to accept):yes>>>Nodesconfigurationupdated>>>Assignadifferentconfigepochtoeachnode>>>SendingCLUSTERMEETmessagestojointheclusterWaitingfortheclustertojoin....>>>PerformingClusterCheck(using node127.0.0.1:6379)M:ad4b9ffceba062492ed67ab336657426f55874b7127.0.0.1:6379slots:0-5460(5461slots)master1additionalreplica(s)M:ab9da92d37125f24fe60f1f33688b4f8644612ee127.0.0.1:6381slots:10923-16383(5461slots)master1additionalreplica(s)S:48e0a4b539867e01c66172415d94d748933be173127.0.0.1:6390slots: (0slots)slavereplicatesdf23c6cad0654ba83f0422e352a81ecee822702eS:d881142a8307f89ba51835734b27cb309a0fe855127.0.0.1:6391slots: (0slots)slavereplicatesab9da92d37125f24fe60f1f33688b4f8644612eeM:df23c6cad0654ba83f0422e352a81ecee822702e127.0.0.1:6380slots:5461-10922(5462slots)master1additionalreplica(s)S:25cfa11a2b4666021da5380ff332b80dbda97208127.0.0.1:6389slots: (0slots)slavereplicatesad4b9ffceba062492ed67ab336657426f55874b7[OK]Allnodesagreeaboutslotsconfiguration.>>>Checkforopenslots...>>>Checkslotscoverage...[OK]All16384slotscovered.

执行 集群检查,检查各个 redis 节点占用的 哈希槽(slot)的个数以及 slot 覆盖率。16384 个槽位中,主节点 redis-6379、redis-6380 和 redis-6381 分别占用了 5461、5461 和 5462 个槽位。

3.3. redis主节点的日志

可以发现,通过 BGSAVE 命令,从节点 redis-6389 在 后台 异步地从 主节点 redis-6379 同步数据。

$ catlog/redis-6379.log1907:C05Sep16:59:52.960

# oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo1907:C05Sep16:59:52.961

# Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=1907, just started1907:C05Sep16:59:52.961

# Configuration loaded1908:M05Sep16:59:52.964

* Increased maximumnumberofopen filesto10032(itwas originallysetto256).1908:M05Sep16:59:52.965

* No cluster configuration found, I'm ad4b9ffceba062492ed67ab336657426f55874b71908:M05Sep16:59:52.967

* Running mode=cluster, port=6379.1908:M05Sep16:59:52.967

# Server initialized1908:M05Sep16:59:52.967

* Readytoaccept connections1908:M05Sep17:01:17.782

# configEpoch set to 1 via CLUSTER SET-CONFIG-EPOCH1908:M05Sep17:01:17.812

# IP address for this node updated to 127.0.0.11908:M05Sep17:01:22.740

# Cluster state changed: ok1908:M05Sep17:01:23.681

* Slave127.0.0.1:6389asksforsynchronization1908:M05Sep17:01:23.681

* Partial resynchronizationnotaccepted: Replication ID mismatch (Slave askedfor'4c5afe96cac51cde56039f96383ea7217ef2af41',myreplication IDs are '037b661bf48c80c577d1fa937ba55367a3692921'and'0000000000000000000000000000000000000000')1908:M05Sep17:01:23.681

* Starting BGSAVEforSYNCwithtarget: disk1908:M05Sep17:01:23.682

* Background saving startedbypid19521952:C05Sep17:01:23.683

* DB savedondisk1908:M05Sep17:01:23.749

* Background saving terminatedwithsuccess1908:M05Sep17:01:23.752

* Synchronizationwithslave127.0.0.1:6389 succeeded

 

吐血整理~