Redis Cluster集群与集群伸缩

使用Redis的时候遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构达到负载均衡的目的。Redis Cluster是Redis的分布式解决方案。

Redis Cluster介绍

Redis Cluster 作为一个分布式的解决方案,首先要解决的就是把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集。

常见哈希分区规则

常见的分区规则有哈希分区和顺序分区,这两种分区各有各的特点,Redis采用的哈希分区的方法。

对应哈希分区常见有如下几种规则

节点取余分区

将key哈希之后对节点数量取余,决定数据最终的映射点。

优点:

  • 简单,常用于数据库的分库分表规则

问题:

  • 当节点数量变化时,这个映射关系需要重新计算

一致性哈希分区

为系统中的每一个节点赋一个token值,这些token构成一个环。读写key的时候,计算key的hash值,然后再环中顺时针寻找离这个key最近的节点。

优点:

  • 加入和删除节点只影响哈希环中 相邻的节点,对其他节点无影响

问题:

  • 加减节点会导致部分数据无法命中,需要单独处理
  • 要保证整体的负载均衡,在加减节点视要增加一倍或者减少一半

虚拟槽分区

Redis Clust采用的就是虚拟槽分区的规则,首先用一个分散度良好的哈希函数把所有的数据映射到一个固定范围的整数集中,整数定义为槽(slot)。槽是集群内数据 管理和迁移的基本单位。每个节点负责处理指定数量的槽的数据。

Redis Clust采用的哈希函数是CRC16方法,总共分了16383个槽。

优点:

  • 解耦数据和节点之间的关系,简化了节点扩容和收缩难度
  • 节点自身维护槽的映射关系

集群功能局限

Redis Clust集群相比单机Redis功能上存在一些限制:

  • key批量操作支持有限。如mset、mget,目前只支持具有相同slot值的 key执行批量操作。
  • key事务操作支持有限。同理只支持多key在同一节点上的事务操 作,当多个key分布在不同的节点上时无法使用事务功能。
  • key作为数据分区的最小粒度,因此不能将一个大的键值对象如 hash、list等映射到不同的节点
  • 不支持多数据库空间,集群模式下只能够使用数据库0

Redis Cluster搭建

节点准备

Redis集群一般由多个节点组成,节点数量至少为6个才能保证组成完整高可用的集群。建议为集群内所有节点统一目录,一般划分三个目录:conf、 data、log,分别存放配置、数据和日志相关文件。

相比单机模式需要改变的配置如下:

1
2
3
4
port 6379                               //端口
cluster-enabled yes //开启集群模式
cluster-config-file nodes-6379.conf //集群内部的配置文件
cluster-node-timeout 15000 //节点超时时间,单位毫秒

注意到上面配置了集群内部的配置文件,集群配置文件的作用:当集群内节点发生信息变化时,如添加节点、节点下线、故障转移等。节点会自动保存集群的状态到配置文件中。该配置文件由Redis自行维护,不要手动修改,防止节点重启时产生集群信息错乱。

启动所有节点以后可以通过 CLUSTER NODES 查看集群状态

1
2
127.0.0.1:6379> CLUSTER NODES
29978c0169ecc0a9054de7f4142155c1ab70258b :6379 myself,master - 0 0 0 connected

节点握手

节点握手是指一批运行在集群模式下的节点通过Gossip协议彼此通信, 达到感知对方的过程。节点握手是集群彼此通信的第一步,由客户端发起命令:CLUSTER MEET [ip] [port]

Redis Clust的节点中是用clusterNode表示一个节点的状态的,握手实际上就是两个节点交换clusterNode的过程,交换的不仅仅是自己的clusterNode还有之间已经通过握手获取的clusterNode信息。当集群中所有的节点都互通以后,每一个节点都会拥有整个集群所有节点的clusterNode信息。

分配槽

Redis集群把所有的数据映射到16384个槽中。每个key会映射为一个固 定的槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。通过 CLUSTER ADDSLOTS命令为节点分配槽。

1
2
3
4
5
6
redis-cli -h 127.0.0.1 -p 6379 cluster addslots {0..5461}
OK
redis-cli -h 127.0.0.1 -p 6380 cluster addslots {5462..10922}
OK
redis-cli -h 127.0.0.1 -p 6381 cluster addslots {10923..16383}
OK

当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail),可以通过CLUSTER INFO获取到的集群状态查看

Redis Cluster集群伸缩

Redis集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务 的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。

伸缩的重点是槽位的重新分配,和数据的转移。

过程

Redis集群的伸缩操作是由Redis的集群管理软件redis-trib负责执行的,Redis提供了进行重新分片所需的所有命令,而redis-trib则通过向源节点和目标节点发送命令来进行重新分片操作。

redis-trib对集群的单个槽s1ot进行重新分片的步骤如下

  1. redis-trib对目标节点发送 CLUSTER SETSLOT<sot> IMPORTING<source_id>命令,让目标节点准备好从源节点导人(Import)属于槽s1ot的键值对。
  2. redis-trib对源节点发送 CLUSTER SETSLOT<s1ot> MIGRATING<target_id>命令,让源节点准备好将属于槽s1ot的键值对迁移(migrate)至目标节点。
  3. redis-trib向源节点发送 CLUSTER GETKEYSINSLOT<s1ot> <count>命令,获得最多count个属于槽s1ot的键值对的键名(key name)
  4. 对于步骤3获得的每个键名,redis-trib都向源节点发送一个 MIGRATE <target_ip> <target_port> <key_name> <timeout>命令,将被选中的键原子地从源节点迁移至目标节点。
  5. 重复执行步骤3和步骤4,直到源节点保存的所有属于槽s1ot的键值对都被迁移至目标节点为止。
  6. redis-trib向集群中的任意一个节点发送 `CLUSTER SETSLOT NODE <target_id> 命令,将槽slot指派绐目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道槽s1ot已经指派给了目标节点。

ASK 错误

在进行集群伸缩期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。

当客户端向源节点发送个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时:

  • 源节点会先在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令。
  • 相反地,如果源节点没能在自己的数据库里面找到指定的键,那么这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令。

操作

扩容:

  • 首先要让扩容的Redis节点上线并完成握手的过程
  • 对槽分布和里面的数据进行迁移

缩容:

  • 对槽分布和里面的数据进行迁移,确保缩容节点没有分配槽
  • 对缩容节点进行忘记处理,命令:CLUST FORGET [NodeId]

参考:

  • 《Redis 开发与运维》
  • 《Redis 设计与实现》