redis集群模式(redis6.0版本)
随着应用的扩展,虽然主从模式和哨兵模式的加入解决了高可用的问题,但是现代的应用基本都是要求可以动态扩展了,为了支持动态扩展,redis在后续的版本当中加入了哨兵的模式
集群模式主要解决的问题是:
Cluster模式实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题
redis结构设计:
使用的是无中心的结构,每一个节点和节点之间相互连接
- redis 使用彼此互联的(ping-pong)的方式,进行互相关联,内部使用二进制协议优化速度
- 客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- 节点的fail是通过集群中超过半数的节点检测失效时才生效
redis集群的工作机制
- 在Redis的每个节点上,都有一个插槽(slot),取值范围为0-16383,redis会根据接节点的数量分配槽的位置来进行判定发送给哪一个cluster节点
- 当我们存取key的时候,Redis会根据CRC16的算法得出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作
- 为了保证高可用,Cluster模式也引入主从复制模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点
- 当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点都宕机了,那么该集群就无法再提供服务了
配置集群(重点):
为了不产生干扰,先把上一节所有的redis进程干掉,包括哨兵的配置
使用kil -9 进程端口号
直接抹掉整个应用
配置如下:
- 集群至少需要三主三从,同时需要奇数的节点配置。
- 我们可以将之前的主从配置的一主三从增加两个主节点,目前的配置如下:
-rw-r--r-- 1 root root 84993 Nov 28 21:41 redis10000.conf -rw-r--r-- 1 root root 84936 Nov 28 21:35 redis16379.conf -rw-r--r-- 1 root root 84962 Nov 28 21:35 redis16380.conf -rw-r--r-- 1 root root 84962 Nov 28 21:35 redis16381.conf # 增加两个主要节点 -rw-r--r-- 1 root root 84962 Nov 28 21:35 redis16382.conf -rw-r--r-- 1 root root 84962 Nov 28 21:35 redis16383.conf 复制代码
主节点的配置主要如下:
port 7100 # 本示例6个节点端口分别为7100,7200,7300,7400,7500,7600 daemonize yes # r后台运行 pidfile /var/run/redis_7100.pid # pidfile文件对应7100,7200,7300,7400,7500,7600 cluster-enabled yes # 开启集群模式 masterauth passw0rd # 如果设置了密码,需要指定master密码 cluster-config-file nodes_7100.conf # 集群的配置文件,同样对应7100,7200等六个节点 cluster-node-timeout 15000 # 请求超时 默认15秒,可自行设置 复制代码
启动如下:
[root@iZwz99gyct1a1rh6iblyucZ bin]# ./redis-server ./cluster/redis17000_cluster.conf [root@iZwz99gyct1a1rh6iblyucZ bin]# ./redis-server ./cluster/redis17100_cluster.conf [root@iZwz99gyct1a1rh6iblyucZ bin]# ./redis-server ./cluster/redis17200_cluster.conf [root@iZwz99gyct1a1rh6iblyucZ bin]# ./redis-server ./cluster/redis17300_cluster.conf [root@iZwz99gyct1a1rh6iblyucZ bin]# ./redis-server ./cluster/redis17400_cluster.conf [root@iZwz99gyct1a1rh6iblyucZ bin]# ./redis-server ./cluster/redis17500_cluster.conf [root@iZwz99gyct1a1rh6iblyucZ bin]# ps -ef | grep redis root 4761 1 0 15:55 ? 00:00:00 ./redis-server 127.0.0.1:17000 [cluster] root 4767 1 0 15:55 ? 00:00:00 ./redis-server 127.0.0.1:17100 [cluster] root 4773 1 0 15:55 ? 00:00:00 ./redis-server 127.0.0.1:17200 [cluster] root 4779 1 0 15:55 ? 00:00:00 ./redis-server 127.0.0.1:17300 [cluster] root 4785 1 0 15:55 ? 00:00:00 ./redis-server 127.0.0.1:17400 [cluster] root 4791 1 0 15:55 ? 00:00:00 ./redis-server 127.0.0.1:17500 [cluster] root 4797 4669 0 15:55 pts/0 00:00:00 grep --color=auto redis 复制代码
启动了上面六个节点之后,使用下面的命令并且敲入yes
让他们变为集群:
[root@iZwz99gyct1a1rh6iblyucZ bin]# ./redis-cli --cluster create 127.0.0.1:17000 127.0.0.1:17100 127.0.0.1:17200 127.0.0.1:17300 127.0.0.1:17400 127.0.0.1:17500 --cluster-replicas 1 >>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 127.0.0.1:17400 to 127.0.0.1:17000 Adding replica 127.0.0.1:17500 to 127.0.0.1:17100 Adding replica 127.0.0.1:17300 to 127.0.0.1:17200 >>> Trying to optimize slaves allocation for anti-affinity [WARNING] Some slaves are in the same host as their master M: 1179bb5f47e7f8221ba7917b5852f8064778e0db 127.0.0.1:17000 slots:[0-5460] (5461 slots) master M: 153afa1b9b14194de441fffa791f8d9001badc66 127.0.0.1:17100 slots:[5461-10922] (5462 slots) master M: 4029aeeb6b80e843279738d6d35eee7a1adcd2ff 127.0.0.1:17200 slots:[10923-16383] (5461 slots) master S: 3ceb11fe492f98432f124fd1dcb7b2bb1e769a96 127.0.0.1:17300 replicates 1179bb5f47e7f8221ba7917b5852f8064778e0db S: 66eaea82ccf69ef96dbc16aac39fd6f6ed3d0691 127.0.0.1:17400 replicates 153afa1b9b14194de441fffa791f8d9001badc66 S: c34aeb59c8bedc11b4aeb720b70b0019e7389093 127.0.0.1:17500 replicates 4029aeeb6b80e843279738d6d35eee7a1adcd2ff 复制代码
验证集群:
- 输入
redis-cli
进入任意的一个主节点,注意是主节点,从节点不能做写入操作
Redirected to slot [9189] located at 127.0.0.1:17100
根据Hash的算法,算出连接那个节点槽,然后提示slot[9189] 落到了17100上面,所以集群会自动跳转进行Key的加入
[root@iZwz99gyct1a1rh6iblyucZ bin]# ./redis-cli -p 17000 127.0.0.1:17000> set key1 1 [root@iZwz99gyct1a1rh6iblyucZ bin]# ./redis-cli -p 17000 127.0.0.1:17000> set key1 1 (error) MOVED 9189 127.0.0.1:17100 [root@iZwz99gyct1a1rh6iblyucZ bin]# ./redis-cli -p 17000 -c 127.0.0.1:17000> set key1 ke -> Redirected to slot [9189] located at 127.0.0.1:17100 OK 复制代码
小贴士:集群之后不能使用传统的连接方式,因为每一个key都要经过一次hash的操作找到对应的槽 -》节点之后才能做后续的操作
使用如下命令进入后正常
./redis-cli -p 17000 -c
-c 代表以集群的方式连接
- 可以使用如下命令验证集群的信息:
127.0.0.1:17000> cluster nodes 66eaea82ccf69ef96dbc16aac39fd6f6ed3d0691 127.0.0.1:17400@27400 slave 153afa1b9b14194de441fffa791f8d9001badc66 0 1606639411000 2 connected 4029aeeb6b80e843279738d6d35eee7a1adcd2ff 127.0.0.1:17200@27200 master - 0 1606639411000 3 connected 10923-16383 3ceb11fe492f98432f124fd1dcb7b2bb1e769a96 127.0.0.1:17300@27300 slave 1179bb5f47e7f8221ba7917b5852f8064778e0db 0 1606639410000 1 connected 1179bb5f47e7f8221ba7917b5852f8064778e0db 127.0.0.1:17000@27000 myself,master - 0 1606639410000 1 connected 0-5460 153afa1b9b14194de441fffa791f8d9001badc66 127.0.0.1:17100@27100 master - 0 1606639412002 2 connected 5461-10922 c34aeb59c8bedc11b4aeb720b70b0019e7389093 127.0.0.1:17500@27500 slave 4029aeeb6b80e843279738d6d35eee7a1adcd2ff 0 1606639413005 3 connected 复制代码
- 接下来我们验证一下当一个主节点挂掉会发生什么情况:
还是和主从复制的验证一样,直接Kill 进程:
kill掉 17000 之后,我们可以发现 17300 被升级为主节点
127.0.0.1:17300> info replication # Replication role:master connected_slaves:0 复制代码
此时的节点情况如下:
127.0.0.1:17100> cluster nodes 4029aeeb6b80e843279738d6d35eee7a1adcd2ff 127.0.0.1:17200@27200 master - 0 1606640582000 3 connected 10923-16383 153afa1b9b14194de441fffa791f8d9001badc66 127.0.0.1:17100@27100 myself,master - 0 1606640581000 2 connected 5461-10922 66eaea82ccf69ef96dbc16aac39fd6f6ed3d0691 127.0.0.1:17400@27400 slave 153afa1b9b14194de441fffa791f8d9001badc66 0 1606640581000 2 connected c34aeb59c8bedc11b4aeb720b70b0019e7389093 127.0.0.1:17500@27500 slave 4029aeeb6b80e843279738d6d35eee7a1adcd2ff 0 1606640582624 3 connected 3ceb11fe492f98432f124fd1dcb7b2bb1e769a96 127.0.0.1:17300@27300 master - 0 1606640580619 7 connected 0-5460 1179bb5f47e7f8221ba7917b5852f8064778e0db 127.0.0.1:17000@27000 master,fail - 1606640370074 1606640367068 1 disconnected 复制代码
- 如果这时候主节点恢复呢?
和哨兵的模式一样,恢复之后也变为slave
了。
集群模式优缺点:
优点:
- 无中心架构,数据按照slot分布在多个节点。
- 集群中的每个节点都是平等的关系,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。
- 可线性扩展到1000多个节点,节点可动态添加或删除
- 能够实现自动故障转移,节点之间通过gossip协议交换状态信息,用投票机制完成slave到master的角色转换
缺点:
- 客户端实现复杂,驱动要求实现Smart Client,缓存slots mapping信息并及时更新,提高了开发难度。目前仅JedisCluster相对成熟,异常处理还不完善,比如常见的“max redirect exception”
- 节点会因为某些原因发生阻塞(阻塞时间大于 cluster-node-timeout)被判断下线,这种failover是没有必要的
- 数据通过异步复制,不保证数据的强一致性
- slave充当“冷备”,不能缓解读压力
- 批量操作限制,目前只支持具有相同slot值的key执行批量操作,对mset、mget、sunion等操作支持不友好
- key事务操作支持有线,只支持多key在同一节点的事务操作,多key分布不同节点时无法使用事务功能
- 不支持多数据库空间,单机redis可以支持16个db,集群模式下只能使用一个,即db 0
Redis Cluster模式不建议使用pipeline和multi-keys操作,减少max redirect产生的场景。
cluster的相关疑问
为什么redis的槽要用 16384
?
值得高兴的是:这个问题作者出门回答了:
能理解作者意思的可以不用看下面的内容
The reason is: Normal heartbeat packets carry the full configuration of a node, that can be replaced in an idempotent way with the old in order to update an old config. This means they contain the slots configuration for a node, in raw form, that uses 2k of space with16k slots, but would use a prohibitive 8k of space using 65k slots. At the same time it is unlikely that Redis Cluster would scale to more than 1000 mater nodes because of other design tradeoffs. So 16k was in the right range to ensure enough slots per master with a max of 1000 maters, but a small enough number to propagate the slot configuration as a raw bitmap easily. Note that in small clusters the bitmap would be hard to compress because when N is small the bitmap would have slots/N bits set that is a large percentage of bits set. 复制代码
- 首先我们查看一下结构体,关于cluster的源代码:
cluster.h
代码如下:
typedef struct { char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */ uint32_t totlen; /* Total length of this message */ uint16_t ver; /* Protocol version, currently set to 1. */ uint16_t port; /* TCP base port number. */ uint16_t type; /* Message type */ uint16_t count; /* Only used for some kind of messages. */ uint64_t currentEpoch; /* The epoch accordingly to the sending node. */ uint64_t configEpoch; /* The config epoch if it's a master, or the last epoch advertised by its master if it is a slave. */ uint64_t offset; /* Master replication offset if node is a master or processed replication offset if node is a slave. */ char sender[CLUSTER_NAMELEN]; /* Name of the sender node */ unsigned char myslots[CLUSTER_SLOTS/8]; char slaveof[CLUSTER_NAMELEN]; char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */ char notused1[34]; /* 34 bytes reserved for future usage. */ uint16_t cport; /* Sender TCP cluster bus port */ uint16_t flags; /* Sender node flags */ unsigned char state; /* Cluster state from the POV of the sender */ unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */ union clusterMsgData data; } clusterMsg; 复制代码
集群节点之间的通信内容无非就是IP信息,请求头,请求内容,以及一些参数信息,这里着重看一下参数myslots[CLUSTER_SLOTS/8]
#define CLUSTER_SLOTS 16384 这里就是16384的来源
在redis节点发送心跳包时需要把所有的槽放到这个心跳包里,以便让节点知道当前集群信息,16384=16k,在发送心跳包时使用char
进行bitmap压缩后是2k(2 * 8 (8 bit) * 1024(1k) = 2K
),也就是说使用2k的空间创建了16k的槽数。
虽然使用CRC16算法最多可以分配65535(2^16-1)个槽位,65535=65k,压缩后就是8k(8 * 8 (8 bit) * 1024(1k) = 8K
),也就是说需要需要8k的心跳包,作者认为这样做不太值得;并且一般情况下一个redis集群不会有超过1000个master节点,所以16k的槽位是个比较合适的选择。