Redis Cluster 通讯流程
在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指:节点负责哪些数据,是否出现故障灯状态信息,redis 集群采用 Gossip(流言)协议,Gossip 协议工作原理就是节点彼此不断交换信息,一段时间后所有的节点都会知道集群完整信息,这种方式类似流言传播。
1. 通信过程
1)集群中的每一个节点都会单独开辟一个 Tcp 通道,用于节点之间彼此通信,防火墙放行(端口号+10000).
2)每个节点在固定周期内通过特定规则选择结构节点发送 ping 消息
3)接收到 ping 消息的节点用 pong 消息作为响应。集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,
只要这些节点彼此可以正常通信,最终他们会打成一致的状态,当节点出现故障,新节点加入,主从角色变化等,它能够给不断的ping/pong消息,从而达到同步目的。
2. 通讯消息类型
Gossip
Gossip 协议职责就是信息交换,信息交换的载体就是节点间彼此发送Gossip 消息。
常见 Gossip 消息分为:ping、 pong、 meet、 fail 等
meet
meet 消息:用于通知新节点加入,消息发送者通知接受者加入到当前集群,meet 消息通信正常完成后,接收节点会加入到集群中并进行ping、 pong 消息交换
ping
ping 消息:集群内交换最频繁的消息,集群内每个节点每秒想多个其他节点发送 ping 消息,用于检测节点是否在线和交换彼此信息。
pong
Pong 消息:当接收到 ping,meet 消息时,作为相应消息回复给发送方确认消息正常通信,节点也可以向集群内广播自身的 pong 消息来通知整个集群对自身状态进行更新。
fail
fail 消息:当节点判定集群内另一个节点下线时,回向集群内广播一个fail 消息,其他节点收到 fail 消息之后把对应节点更新为下线状态。
Redis Cluster手动分配槽位
虽然节点之间已经互相发现了,但是此时集群还是不可用的状态,因为并没有给节点分配槽位,而且必须是所有的槽位都分配完毕后整个集群才是可用的状态。反之,也就是说只要有一个槽位没有分配,那么整个集群就是不可用的。
测试命令如下:
1. [root@redis1 ~]# redis-cli -h 192.168.1.4 -p 6380 2. 192.168.1.4:6380> set k1 v1 3. (error) CLUSTERDOWN Hash slot not served //此刻报错为正常 4. 192.168.1.4:6380> cluster info 5. cluster_state:fail 6. cluster_slots_assigned:0 7. cluster_slots_ok:0 8. cluster_slots_pfail:0 9. cluster_slots_fail:0 10. cluster_known_nodes:6 11. cluster_size:0 12. cluster_current_epoch:5 13. cluster_my_epoch:1 14. cluster_stats_messages_ping_sent:316 15. cluster_stats_messages_pong_sent:321 16. cluster_stats_messages_meet_sent:6 17. cluster_stats_messages_sent:643 18. cluster_stats_messages_ping_received:320 19. cluster_stats_messages_pong_received:322 20. cluster_stats_messages_meet_received:1 21. cluster_stats_messages_received:643
前面说了,我们虽然有6个节点,但是真正负责数据写入的只有3个节点,其他3个节点只是作为主节点的从节点,也就是说,只需要分配其中三个主节点的槽位就可以了。
1. 分配槽位的方法:
分配槽位需要在每个主节点上来配置,此时有2种方法执行:
分别登录到每个主节点的客户端来执行命令。
在其中一台机器上用redis客户端远程登录到其他机器的主节点上执行命令。
每个节点执行命令:
1. [root@redis1 ~]# redis-cli -h 192.168.1.4 -p 6380 cluster addslots {0..5460} 2. OK 3. [root@redis1 ~]# redis-cli -h 192.168.1.5 -p 6380 cluster addslots {5461..10922} 4. OK 5. [root@redis1 ~]# redis-cli -h 192.168.1.6 -p 6380 cluster addslots {10923..16383} 6. OK
分配完所有槽位之后我们再查看一下集群的节点状态和集群状态,可以看到三个节点都分配了槽位,而且集群的状态是OK的。
1. [root@redis1 ~]# redis-cli -h 192.168.1.4 -p 6380 2. 3. 192.168.1.4:6380> cluster info 4. 5. cluster_state:ok 6. 7. //省略部分内容
2. 手动配置集群高可用
虽然这时候集群是可用的了,但是整个集群只要有一台机器坏掉了,那么整个集群都是不可用的。
所以这时候需要用到其他三个节点分别作为现在三个主节点的从节点,以应对集群主节点故障时可以进行自动切换以保证集群持续可用。
注意:
(1)不要让复制节点复制本机器的主节点,因为如果那样的话机器挂了集群还是不可用状态, 所以复制节点要复制其他服务器的主节点。
(2)使用redis-trid工具自动分配的时候会出现复制节点和主节点在同一台机器上的情况,需要注意。
3. 高可用配置命令
这一次我们采用在一台机器上使用redis客户端远程操作集群其他节点。
注意:
(1)需要执行命令的是每个服务器的从节点。
(2)注意主从的ID不要搞混了。
拓扑图如下:
1. [root@redis1 ~]# redis-cli -h 192.168.1.4 -p 6381 cluster replicate d1830b324dd295d885cba841c6ab5039bc956c44 //第三台6380的id 2. OK 3. [root@redis1 ~]# redis-cli -h 192.168.1.5 -p 6381 cluster replicate 5bbd0a1e95af121ad09d8b1c90d6c2caa2851ae4 //第一台6380的id 4. OK 5. [root@redis1 ~]# redis-cli -h 192.168.1.6 -p 6381 cluster replicate 10a4845657f28c482673e335ac9606a21139e472 //第二台6380的id 6. OK
下图中可以看到,6380全都是master,6381全都是slave,此刻主从关系就指定成功了。关于手动指定主从关系需要理解之间的关系,后面还会将学习自动指定关系,但之间的原理还是要了解的。
4. Redis Cluster测试集群
我们使用常规插入redis数据的方式往集群里写入数据,看看会发生什么?
1. [root@redis1 ~]# redis-cli -h 192.168.1.4 -p 6380 set k1 v1 2. 3. (error) MOVED 12706 192.168.1.6:6380
结果提示error, 但是给出了集群另一个节点的地址,那么这条数据到底有没有写入呢? 我们登录这两个节点分别查看。
1. [root@redis1 ~]# redis-cli -h 192.168.1.6 -p 6380 get k1 2. 3. (nil)
结果没有,这是因为使用集群后由于数据被分片了,所以并不是说在那台机器上写入数据就会在哪台机器的节点上写入,集群的数据写入和读取就涉及到了另外一个概念,ASK路由。
Redis Cluster ASK路由介绍
在集群模式下,Redis接受任何键相关命令时首先会计算键对应的槽,再根据槽找出所对应的节点。
如果节点是自身,则处理键命令;否则回复MOVED重定向错误,通知客户端请求正确的节点,这个过程称为Mover重定向。
知道了ask路由后,我们使用-c选项批量插入一些数据。
正确访问命令
创建一万个键值对测试。
1. [root@redis1 ~]# mkdir /sh 2. [root@redis1 ~]# vim /sh/input.key.sh 3. #!/bin/bash 4. for i in $(seq 1 10000) 5. do 6. redis-cli -c -h 192.168.1.4 -p 6380 set k_${i} v_${i} && echo "set k_${i} is ok" 7. done 8. [root@redis1 ~]# sh /sh/input.key.sh
写入后执行脚本等待创建键值对,我们同样使用-c选项来读取刚才插入的键值,然后查看下redis会不会帮我们路由到正确的节点上。
1. [root@redis1 ~]# redis-cli -c -h 192.168.1.4 -p 6380 2. 3. 192.168.1.4:6380> get k_1 4. 5. "v_1" 6. 7. 192.168.1.4:6380> get k_100 8. 9. -> Redirected to slot [5541] located at 192.168.1.5:6380 10. 11. "v_100" 12. 13. 192.168.1.5:6380> get k_1000 14. 15. -> Redirected to slot [79] located at 192.168.1.4:6380 16. 17. "v_1000"
模拟故障转移
至此,我们已经手动的把一个redis高可用的集群部署完毕了,但是还没有模拟过故障
这里我们就模拟故障,停掉期中一台主机的redis节点,然后查看一下集群的变化。
我们使用暴力的kill -9(真实环境禁止使用,建议使用kill或pkill)杀掉 redis3上的redis集群节点,然后观察节点状态。
理想情况应该是redis1上的6381从节点升级为主节点,在redis1上查看集群节点状,虽然我们已经测试了故障切换的功能,但是节点修复后还是需要重新上线,所以这里测试节点重新上线后的操作。
重新启动redis3的6380,然后观察日志。
观察redis1上的日志。
这时假如我们想让修复后的节点重新上线,可以在想变成主库的从库执行CLUSTER FAILOVER命令。
这里我们在redis3的6380上执行
操作命令:
模拟redis3故障:(关掉redis3的服务)
1. [root@redis1 ~]# redis-cli -h 192.168.1.6 -p 6380 shutdown 2. 3. [root@redis1 ~]# redis-cli -h 192.168.1.6 -p 6381 shutdown
查看群集节点状态:
查看redis3的服务器状态全都是fail(失败)。
再启动redis3的服务:
1. [root@redis3 ~]# redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf 2. 3. [root@redis3 ~]# redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf
虽然启动了redis3的服务,但是状态始终是slave,需要指定6380重新回到master。
重回master:
1. [root@redis1 ~]# redis-cli -h 192.168.1.6 -p 6380 CLUSTER FAILOVER 2. 3. OK
执行命令后可以看到redis3的6380重新回到了master状态。
自动搭建部署Redis Cluster
手动搭建集群便于理解集群创建的流程和细节,不过手动搭建集群需要很多步骤,当集群节点众多时,必然会加大搭建集群的复杂度和运维成本,因此官方提供了 redis-trib.rb的工具方便我们快速搭建集群。redis-trib.rb是采用 Ruby 实现的 redis 集群管理工具,内部通过 Cluster相关命令帮我们简化集群创建、检查、槽迁移和均衡等常见运维操作,使用前要安装 ruby 依赖环境。
前提准备:
停掉所有的节点,然后清空数据,恢复成一个全新的集群,所有机器执行命令
前提条件需要根据上面的配置全部清空数据,恢复全新的群集状态,三台服务器都需要执行下面命令。这里以redis1为例,清空全部数据并启动。
1. [root@redis1 ~]# rm -rf /data/redis_cluster/redis_6380/* 2. 3. [root@redis1 ~]# rm -rf /data/redis_cluster/redis_6381/* 4. 5. [root@redis1 ~]# redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf 6. 7. [root@redis1 ~]# redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf
执行后查看群集,每个服务器端口都应成为下面的状态。下面将进行自动分配槽位及主从关系。
1. 安装命令:老版本需要安装,注意:新版本redis不需安装,直接采用步骤2。
yum makecache fast
yum install rubygems
gem sources --remove https://rubygems.org/
gem sources -a http://mirrors.aliyun.com/rubygems/
gem update –system
gem install redis -v 3.3.5
redis1执行创建集群命令
cd /opt/redis_cluster/redis/src/
./redis-trib.rb create --replicas 1 192.168.1.4:6380 192.168.1.5:6380 192.168.1.6:6380 192.168.1.4:6381 192.168.1.5:6381 192.168.1.6:6381
检查集群完整性
./redis-trib.rb check 192.168.1.4:6380
2. 直接使用redis-cli命令
创建群集:前面写主后面写从
[root@redis1 ~]# redis-cli --cluster create --cluster-replicas 1 192.168.1.4:6380 192.168.1.5:6380 192.168.1.6:6380 192.168.1.4:6381 192.168.1.5:6381 192.168.1.6:6381
确定自动分配的槽位及master和slave输入"yes"。
检查完整性:
[root@redis1 ~]# redis-cli --cluster check 192.168.1.4:6380
工具扩容节点
Redis集群的扩容操作可分为以下几个步骤:
①准备新节点
②加入集群
③迁移槽和数据
1. 我们在redis1上创建2个新节点,分别为6390、6391。
1. [root@redis1 ~]# mkdir -p /opt/redis_cluster/redis_{6390,6391}/{conf,logs,pid} 2. 6391.conf 3. [root@redis1 ~]# mkdir -p /data/redis_cluster/redis_{6390,6391} 4. [root@redis1 ~]# cd /opt/redis_cluster/ 5. [root@redis1 redis_cluster]# cp redis_6380/conf/redis_6380.conf redis_6390/conf/redis_6390.conf 6. [root@redis1 redis_cluster]# cp redis_6380/conf/redis_6380.conf redis_6391/conf/redis_6391.conf 7. [root@redis1 redis_cluster]# sed -i 's#6380#6390#g' redis_6390/conf/redis_6390.conf 8. [root@redis1 redis_cluster]# sed -i 's#6380#6391#g' redis_6391/conf/redis_6391.conf
2. 启动节点
1. [root@redis1 redis_cluster]# redis-server /opt/redis_cluster/redis_6390/conf/redis_6390.conf 2. 3. [root@redis1 redis_cluster]# redis-server /opt/redis_cluster/redis_6391/conf/redis_6391.conf
3. 发现节点
1. [root@redis1 redis_cluster]# redis-cli -c -h 192.168.1.4 -p 6380 cluster meet 192.168.1.4 6390 2. 3. OK 4. 5. [root@redis1 redis_cluster]# redis-cli -c -h 192.168.1.4 -p 6380 cluster meet 192.168.1.4 6391 6. 7. OK
4. 分配slots
[root@redis1 redis_cluster]# redis-cli --cluster reshard 192.168.1.4:6390
①输入分配的slots数:4096
②再输入6390的id号
③再输入all
④最后输入yes
配置6391 slave 6390:
1. [root@redis1 redis_cluster]# redis-cli -h 192.168.1.4 -p 6391 cluster replicate d5abff2bf2e0e2eac77a3d7a90f217d5895b135b (输入6390的id) 2. 3. OK
通过命令查看,6390成为了master、6391成为了slave,而槽位分别从三个区域拿了一部分。
工具收缩节点
首先需要确定下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性。
当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭。
这里我们准备将刚才新添加的节点下线,也就是6390和6391。
收缩和扩容迁移的方向相反,6390变为源节点,其他节点变为目标节点,源节点把自己负责的4096个槽均匀的迁移到其他节点上。
下面请根据版本来选择收缩方法,我这里使用的新版本将直接使用第二个方法。
1. 旧版本
由于redis-trib..rb reshard命令只能有一个目标节点,因此需要执行3次reshard命令,分别迁移1365、1365、1366个槽。
操作命令:
cd /opt/redis_cluster/redis/src/
./redis-trib.rb reshard 192.168.1.4:6380
How many slots do you want to move (from 1 to 16384)? 1365
输入6380的id
输入6390的id
done
忘记节点
由于我们的集群是做了高可用的,所以当主节点下线的时候从节点也会顶上,所以最好我们先下线从节点,然后在下线主节点
cd /opt/redis_cluster/redis/src/
./redis-trib.rb del-node 192.168.1.4:6391 ID
./redis-trib.rb del-node 192.168.1.4:6390 ID
2. 新版本
移除下线节点的槽位:
[root@redis1 ~]# redis-cli --cluster reshard 192.168.1.4:6390
案例中分三次移除:分别
1365(槽位) 给redis1的6380(输入id)
1366(槽位) 给redis2的6380(输入id)
1365(槽位) 给redis3的6380(输入id)
移除后遗忘下线节点:
1. [root@redis1 ~]# redis-cli -c -h 192.168.1.4 -p 6380 cluster forget d5abff2bf2e0e2eac77a3d7a90f217d5895b135b //(6390的ID) 2. 3. OK 4. 5. [root@redis1 ~]# redis-cli -c -h 192.168.1.4 -p 6380 cluster forget 45439cc021b73d743083f83f633124ab81eed67e //(6391的ID) 6. 7. OK
总结:经过节点扩容与收缩功能后,了解了如何给新增节点增加槽位,以及把指定的主机槽位给其他主机。
Redis常用命令(扩展内容)
集群(cluster)
CLUSTER INFO 打印集群的信息
CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。
节点(node)
CLUSTER MEET <ip> <port> 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
CLUSTER FORGET <node_id> 从集群中移除 node_id 指定的节点。
CLUSTER REPLICATE <node_id> 将当前节点设置为 node_id 指定的节点的从节点。
CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。
槽(slot)
CLUSTER ADDSLOTS <slot> [slot ...] 将一个或多个槽(slot)指派(assign)给当前节点。
CLUSTER DELSLOTS <slot> [slot ...] 移除一个或多个槽对当前节点的指派。
CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
CLUSTER SETSLOT <slot> NODE <node_id> 将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
CLUSTER SETSLOT <slot> MIGRATING <node_id> 将本节点的槽 slot 迁移到 node_id 指定的节点中。
CLUSTER SETSLOT <slot> IMPORTING <node_id> 从 node_id 指定的节点中导入槽 slot 到本节点。
CLUSTER SETSLOT <slot> STABLE 取消对槽 slot 的导入(import)或者迁移(migrate)。
键 (key)
CLUSTER KEYSLOT <key> 计算键 key 应该被放置在哪个槽上。
CLUSTER COUNTKEYSINSLOT <slot> 返回槽 slot 目前包含的键值对数量。CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 个 slot 槽中的键。