之前粗浅的学习了Redis三种集群策略的主从复制和哨兵策略,现在最后这篇来学习一下Redis Cluster也就是最后一个集群策略。
1.什么是集群?
集群(Cluster),Redis2.6版本(正式版本是3.0)推出的分布式解决方案,有效解决了单Master节点写操作的压力并且分布式存储数据,大大提高了负载能力。
在Redis发布3.0正式版本前,一般使用代理中间件来实现分布式集群策略。这里不展开学习了,有兴趣的小伙伴自行研究。
特点
- Cluster策略是分布式部署,节点间相互协调工作。
因为对主从复制和哨兵策略都称为集群策略,所以为了防止误解在下文中提及的集群(Cluster)策略,直接用Cluster称呼。
- Cluster至少要3个Master节点,并且是无中心化设计。
- 客户端使用Cluster,不需要连接所有节点,只需要连接Cluster中任意一个可用节点即可。
- 数据的分布式存储不需要指定,Cluster会自动完成。
- Cluster策略是分布式部署,节点间相互协调工作。
优点
- Cluster策略拥有主从复制和哨兵策略的优点。
- 解决了单Master节点写操作的压力。
- 分布式存储数据,提高了负载能力。
- 支持线性扩容。
- 缺点
- 部分操作命令受限,比如mset,目前只能支持同一个插槽(slot)的key进行操作。
- 事务机制不支持多节点操作。
- 不支持多数据库,即只有db0。
2.如何配置(新旧方式)
- 旧方式,./redis-trib.rb create –replicas {SLAVE_NUM} {IP}:{PORT} … {IP}:{PORT}
- 新方式,./redis-cli –cluster create –cluster-replicas {SLAVE_NUM} {IP}:{PORT} … {IP}:{PORT}
redis.conf示例
1 | # 是否以守护进程方式运行 |
3.工作机制
- Redis节点启动,节点根据配置
cluster-enabled
判断是否加入Cluster。 - 新节点通过
cluster meet {IP} {PORT}
命令和其他节点感知并建立连接,节点间会通过Gossip协议
PING/PONG命令来检测状态和交换信息。 - Cluster计算并且分配主节点插槽数量。
这个地方注意,不是插槽数量,是每个节点的插槽数量。插槽数量是固定的:16384。
- 插槽分配成功之后,Cluster开始服务。
4.如何感知新节点?
当给某一节点发送命令cluster meet {IP} {PORT}
(新节点),该节点就会尝试与新节点建立连接,具体流程:
- 该节点向新节点发送MEET命令。
- 新节点接收到MEET命令后,回复PONG命令。
- 该节点接收到新节点返回的PONG命令,知道新节点成功接收了自己的MEET命令。
- 该节点向新节点发送PING命令。
- 新节点接收到该节点发送的PING命令,知道该节点已经成功接收到自己返回的PONG命令。
- 该节点和新节点握手完成,建立连接。
- 最后,该节点会将新节点的信息通过
Gossip协议
同步给Cluster中的其他节点,让其他节点也与新节点进行握手,建立连接。
可以通过
cluster nodes
命令查看集群中哪些节点已经建立连接。
5.数据插槽
说到插槽(slot)不得不提一下,为了能够让数据平均分配到多个节点上而采用的数据分区算法。常见的数据分区算法:范围(Range)、哈希(Hash)、一致性哈希算法和虚拟哈希槽等。
Cluster采用的虚拟哈希槽数据分区算法,所有的key根据哈希函数映射到0 ~ 16383插槽内(公式:slot = crc16(key) & 16383),之前也提到过插槽也是平均分配到每个Master节点的。
- 虚拟哈希槽的特点
- 降低了节点和数据之间的耦合性,方便线性扩容&动态管理节点。
- 节点自己管理和插槽的对应关系。
- 支持查询节点、插槽和key的对应关系。
可以通俗理解为,插槽是Cluster管理数据的基本单位。
6.动态管理节点
假如我们原有4个Master节点(M1 … M4),但是现在因为数据增量问题临时加一个Master节点(M5),我们需要怎么操作呢?
- 启动M5节点,客户端发送MEET命令让M5节点加入到Cluster中,现在M5节点没有任何插槽所以不会接受任何读写操作。
- 在M5节点执行
cluster setslot {SLOT} importing {SOURCE_NODE_ID}
命令,让M5节点准备导入{SLOT}插槽。 - 在拥有这个{SLOT}插槽的源节点上面执行
cluster setslot {SLOT} migrating {M5_NODE_ID}
,让源节点准备好迁出插槽。 - 这时候如果客户端操作的key存在于{SLOT}插槽中,那么这个操作由源节点处理。如果key不存在于{SLOT}插槽中,这个操作将由M5节点操作。
- 现在源节点的{SLOT}插槽不会创建任何新的key,需要把源节点{SLOT}插槽中的key迁移到M5节点。执行
cluster getkeysinslot {SLOT} {COUNT}
命令获取{SLOT}插槽中指定{COUNT}数量的key列表。 - 在源节点对每个key执行
migrate
命令,把key迁移到M5节点。 - 在源节点和M5节点执行
cluster setslot {SLOT} NODE {M5_NODE_ID}
,完成迁移。
这就是动态增加节点的流程了,可能在新的Redis版本中增加了节点迁移工具,但是核心流程应该还是这样。
7.Hash Tag
学习了数据插槽,我们知道Redis在key分配到插槽的这一操作完全是自动化的,不过当我们有需求对不同的key需要放到同一插槽中的时候,这个时候要怎么操作呢?我们只需要在key中加入{}符号即可,比如:
- {UID_1001}:following
- {UID_1001}:followers
这两个key会被分配到同一插槽,原理就是当key中存在{}符号,哈希算法只会针对{}符号内的字符串。
8.插槽为什么是16384?这个值能修改吗?
首先crc16算法算出的值有16bit,2^16即65536。也就是说该算法的值在0 ~ 65535之间,那么为什么作者还是选择了16384即0 ~ 16383。
很开心,对于这个疑问Redis作者给了明确的回答。前面我们知道每个节点之间会以每秒1次的频率互相发送心跳包(PING)&交换信息(信息分为消息头和消息体),之前也提了交换的信息体里面主要包含节点的信息等,那么消息头的内容呢?如下:
1 | typedef struct { |
其中有个myslots
字段要注意,该字段使用位图,即1bit代表1slot,如果该bit为1即说明该插槽属于这个节点。那么该字段的大小为:16384 / 8bit / 1024b = 2kb。也就是消息头不考虑其他信息的情况,单是myslots
就已经有2kb大小。
那么消息体呢?之前已经提到了消息体中会包含节点信息。具体是什么样的呢?消息体每次携带最少3个节点的信息,数量约为总节点数的1/10。如果节点数量越多,消息体越大。
- 如果插槽数量是65535,那么该字段的大小放大为:65535 / 8bit / 1024b = 8kb。这对于每秒1次频率的心跳包来讲,带宽开销是极大的。
- 上面说了节点越多,消息体也就越大,如果节点超过1000个也会导致网络拥堵,因为Redis作者不建议Cluster节点的数量超过1000,那么对于1000个以下的节点来说16384个插槽也就够用了。
- 第三个考虑是关于位图的压缩问题,我还没有搞明白~~所以这里不展开说了,先埋个坑。
这个值能修改吗?16384插槽数量是写死在Redis源代码中的,所以是不可以更改的。
附上关于作者的回答
最后
Cluster在故障恢复主从切换的机制(包括:主观宕机、客观宕机、投票选举、主从切换)和哨兵策略基本一致,所以在这里就不学习Cluster关于故障恢复主从切换的相关知识了。
前面提到过Gossip协议,它主要职责就是各节点间的信息交换,常用的Gossip消息可分为:
- ping
- pong
- meet
- fail
后面会专门来学习Gossip协议的知识(-,-再次埋个坑)。