集群模式
概念
主从复制和哨兵模式一定程度上能解决高可用问题,但是也存在问题,比如容量不够,redis如何进行扩容?并发写操作,redis如何分摊?redis3.0中提供了解决方案,就是无中心化集群配置。
集群,即Redis Cluster,是Redis 3.0开始引入的分布式存储方案。
集群由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点:只有主节点负责读写请求和集群信息的维护;从节点只进行主节点数据和状态信息的复制。
集群的作用,可以归纳以下几点:
1、数据分区:数据分区(或称数据分片)是集群最核心的功能;
集群将数据分散到多个节点,一方面突破了Redis单机内存大小的限制,存储容量大大增加;另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。
Redis单机内存大小受限问题,在介绍持久化和主从复制时 都有提及;例如,如果单机内存太大,bgsave和bgrewriteof的fork操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出...。
2、高可用:集群支持主从复制和主节点的自动故障转移(与哨兵类似);当任意节点发生故障时,集群任然可以对外提供服务;
3、水平扩容:Redis集群实现了对Redis的水平扩容,即启动N个Redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N;
注意事项
- redis集群采用P2P模式,是完全去中心化的,不存在中心节点或者代理节点;
- redis集群是没有统一的入口的,客户端(client)连接集群的时候连接集群中的任意节点(node)即可,集群内部的节点是相互通信的(PING-PONG机制),每个节点都是一个redis实例;
- 为了实现集群的高可用,即判断节点是否健康(能否正常使用),redis-Cluster有这么一个投票容错机制:如果集群中超过半数的节点投票认为某个节点挂了,那么这个节点就挂了(fail)。这是判断节点是否挂了的方法;
- 那么如何判断集群是否挂了呢?->如果集群中任意一个节点挂了,而且该节点没有从节点(备份节点),那么这个集群就挂了。这是判断集群是否挂了的方法;
- 那么为什么任意一个节点挂了(没有从节点)这个集群就挂了呢?->因为集群内置了16384个slot(哈希槽),并且把所有物理节点映射到了这16384[0-16383]个slot上,或者说把这些slot均等的分配给了各个节点。当需要在Redis集群存放一个数据(key-value)时,redis会先对这个key进行crc16算法,然后得到一个结果。再把这个结果对16384进行求余,这个余数会对应[0-16384]其中一个槽,进而决定key-value存储到哪个节点中。所以一旦某个节点挂了,该节点对应的slot就无法使用,那么就会导致集群无法正常工作。
- 每个Redis集群理论上最多可以有16384个节点。
集群搭建
Redis集群至少需要3个节点,因为投票容错机制要求超过半数节点认为某个节点挂了该节点才是挂了,所以2个节点无法构成集群。要保证集群的高可用,需要每个节点都有从节点,也就是备份节点,所以Redis集群至少需要6台服务器。
集群的搭建有两种方式:
- 手动执行Redis命令,一步步完成搭建;
- 使用Ruby脚本搭建。
二者搭建的原理是一样的,只是Ruby脚本将Redis命令进行了打包封装;
这一部分我们将搭建一个简单的集群:共6个节点,3主3从。方便起见:所有节点在同一台服务器上,以端口号进行区分;配置从简。3个主端口号:7000/7001/7002,对应的从节点端口号:8000/8001/8002。
手动执行Redis命令
1、关闭之前的Redis;
[root@localhost redis-6.2.2]# ps -ef | grep redis
root 7734 1 0 09:39 ? 00:00:24 /usr/local/redis/bin/redis-server 127.0.0.1:6389
root 7741 1 0 09:39 ? 00:00:24 /usr/local/redis/bin/redis-server 127.0.0.1:6399
root 7851 1 0 11:40 ? 00:00:30 /usr/local/redis/bin/redis-sentinel *:16379 [sentinel]
root 8098 7693 0 11:47 pts/0 00:00:00 /usr/local/redis/bin/redis-cli -p 16379
root 8182 7828 0 14:40 pts/1 00:00:00 grep --color=auto redis
[root@localhost redis-6.2.2]# kill 7734 7741 7851
[root@localhost redis-6.2.2]# ps -ef | grep redis
root 8098 7693 0 11:47 pts/0 00:00:00 /usr/local/redis/bin/redis-cli -p 16379
root 8184 7828 0 14:40 pts/1 00:00:00 grep --color=auto redis
添加节点配置文件
[root@localhost cluster-redis]# vim redis_7000.conf
include /usr/local/redis/redis.conf
port 7000
daemonize yes
pidfile "/var/run/redis_7000.pid"
dbfilename "dump7000.rdb"
dir "/usr/local/cluster-redis"
logfile "/usr/local/cluster-redis/redis_7000.log"
# 打开集群模式
cluster-enabled yes
# 设定节点信息配置文件,会自动生成
cluster-config-file node_7000.conf
# 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
cluster-node-timeout 15000
# 复制
[root@localhost cluster-redis]# cp redis_7000.conf redis_7001.conf
[root@localhost cluster-redis]# cp redis_7000.conf redis_7002.conf
[root@localhost cluster-redis]# cp redis_7000.conf redis_8000.conf
[root@localhost cluster-redis]# cp redis_7000.conf redis_8001.conf
[root@localhost cluster-redis]# cp redis_7000.conf redis_8002.conf
# 替换7000为当前节点配置
[root@localhost cluster-redis]# sed -i 's/7000/7001/g' redis_7001.conf
[root@localhost cluster-redis]# sed -i 's/7000/7002/g' redis_7002.conf
[root@localhost cluster-redis]# sed -i 's/7000/8000/g' redis_8000.conf
[root@localhost cluster-redis]# sed -i 's/7000/8001/g' redis_8001.conf
[root@localhost cluster-redis]# sed -i 's/7000/8002/g' redis_8002.conf
启动节点
[root@localhost cluster-redis]# /usr/local/redis/bin/redis-server /usr/local/cluster-redis/redis_7000.conf
[root@localhost cluster-redis]# /usr/local/redis/bin/redis-server /usr/local/cluster-redis/redis_7001.conf
[root@localhost cluster-redis]# /usr/local/redis/bin/redis-server /usr/local/cluster-redis/redis_7002.conf
[root@localhost cluster-redis]# /usr/local/redis/bin/redis-server /usr/local/cluster-redis/redis_8000.conf
[root@localhost cluster-redis]# /usr/local/redis/bin/redis-server /usr/local/cluster-redis/redis_8001.conf
[root@localhost cluster-redis]# /usr/local/redis/bin/redis-server /usr/local/cluster-redis/redis_8002.conf
[root@localhost cluster-redis]# ps -ef | grep redis
root 8409 1 0 15:55 ? 00:00:00 /usr/local/redis/bin/redis-server 127.0.0.1:7000 [cluster]
root 8415 1 0 15:55 ? 00:00:00 /usr/local/redis/bin/redis-server 127.0.0.1:7001 [cluster]
root 8421 1 0 15:55 ? 00:00:00 /usr/local/redis/bin/redis-server 127.0.0.1:7002 [cluster]
root 8427 1 0 15:55 ? 00:00:00 /usr/local/redis/bin/redis-server 127.0.0.1:8000 [cluster]
root 8433 1 0 15:55 ? 00:00:00 /usr/local/redis/bin/redis-server 127.0.0.1:8001 [cluster]
root 8439 1 0 15:55 ? 00:00:00 /usr/local/redis/bin/redis-server 127.0.0.1:8002 [cluster]
创建集群,节点启动以后是相互独立的,并不知道其他节点存在,需要创建集群;
- -cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点
redis-cli-cluster命令参数:
redis-cli --cluster help
Cluster Manager Commands:
create host1:port1 ... hostN:portN #创建集群
--cluster-replicas <arg> #从节点个数
check host:port #检查集群
--cluster-search-multiple-owners #检查是否有槽同时被分配给了多个节点
info host:port #查看集群状态
fix host:port #修复集群
--cluster-search-multiple-owners #修复槽的重复分配问题
reshard host:port #指定集群的任意一节点进行迁移slot,重新分slots
--cluster-from <arg> #需要从哪些源节点上迁移slot,可从多个源节点完成迁移,以逗号隔开,传递的是节点的node id,还可以直接传递--from all,这样源节点就是集群的所有节点,不传递该参数的话,则会在迁移过程中提示用户输入
--cluster-to <arg> #slot需要迁移的目的节点的node id,目的节点只能填写一个,不传递该参数的话,则会在迁移过程中提示用户输入
--cluster-slots <arg> #需要迁移的slot数量,不传递该参数的话,则会在迁移过程中提示用户输入。
--cluster-yes #指定迁移时的确认输入
--cluster-timeout <arg> #设置migrate命令的超时时间
--cluster-pipeline <arg> #定义cluster getkeysinslot命令一次取出的key数量,不传的话使用默认值为10
--cluster-replace #是否直接replace到目标节点
rebalance host:port #指定集群的任意一节点进行平衡集群节点slot数量
--cluster-weight <node1=w1...nodeN=wN> #指定集群节点的权重
--cluster-use-empty-masters #设置可以让没有分配slot的主节点参与,默认不允许
--cluster-timeout <arg> #设置migrate命令的超时时间
--cluster-simulate #模拟rebalance操作,不会真正执行迁移操作
--cluster-pipeline <arg> #定义cluster getkeysinslot命令一次取出的key数量,默认值为10
--cluster-threshold <arg> #迁移的slot阈值超过threshold,执行rebalance操作
--cluster-replace #是否直接replace到目标节点
add-node new_host:new_port existing_host:existing_port #添加节点,把新节点加入到指定的集群,默认添加主节点
--cluster-slave #新节点作为从节点,默认随机一个主节点
--cluster-master-id <arg> #给新节点指定主节点
del-node host:port node_id #删除给定的一个节点,成功后关闭该节点服务
call host:port command arg arg .. arg #在集群的所有节点执行相关命令
set-timeout host:port milliseconds #设置cluster-node-timeout
import host:port #将外部redis数据导入集群
--cluster-from <arg> #将指定实例的数据导入到集群
--cluster-copy #migrate时指定copy
--cluster-replace #migrate时指定replace
help
For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.
[root@localhost src]# /usr/local/redis/bin/redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:8000 127.0.0.1:8001 127.0.0.1:8002
登录并查看集群信息
#
[root@localhost bin]# ./redis-cli -c -p 7000
127.0.0.1:7000> cluster nodes
ff521f3df6bd78ea349def87a14f85325255ff60 127.0.0.1:8001@18001 slave aca95991dbf86cbf434b111ea62bc17542f61e90 0 1620895426134 2 connected
093d9a44534c2575f94b7ab615f73bccbc90ab0d 127.0.0.1:8000@18000 slave 9aae25fe9fa22aa0884f606a49e837782f0f62d1 0 1620895425000 1 connected
9aae25fe9fa22aa0884f606a49e837782f0f62d1 127.0.0.1:7000@17000 myself,master - 0 1620895424000 1 connected 0-5460
d85c81e3929b7a3dd73f1047ac046907e3b28103 127.0.0.1:8002@18002 slave 2a4ae345c9ac938596e8103a8ee0c33c6395573b 0 1620895425000 3 connected
aca95991dbf86cbf434b111ea62bc17542f61e90 127.0.0.1:7001@17001 master - 0 1620895424078 2 connected 5461-10922
2a4ae345c9ac938596e8103a8ee0c33c6395573b 127.0.0.1:7002@17002 master - 0 1620895425108 3 connected 10923-16383
使用Ruby脚本搭建
因为Redis5.0开始使用redis-cli作为创建集群的命令,使用c语音实现,不再使用ruby语言,Redis6发现已经没有这个脚本,之前的版本如果有可以使用此方法创建。
集群方案设计原则
设计集群方案时,至少要考虑以下因素:
1、高可用要求:根据故障转移的原理,至少需要3个主节点才能完成故障转移,且3个主节点不应在同一台物理机上;每个主节点至少需要1个从节点,且主从节点不应在一台物理机上;因此高可用集群至少包含6个节点;
2、数据量和访问量:估算应用需要的数据量和总访问量(考虑业务发展,留有冗余),结合每个主节点的容量和能承受的访问量(可以通过benchmark得到较准确估计),计算需要的主节点数量;
3、节点数量限制:Redis官方给出的节点数量限制为1000,主要是考虑节点间通信带来的消耗在实际应用中应避免大集群;如果节点数量不足以满足应用对Redis数据量和访问量的要求,可以考虑:(1)业务分割,大集群分为多个小集群;(2)减少不必要的数据;(3)调整数据过期策略等;
4、适度冗余:Redis可以在不影响集群服务的情况下增加节点,因此节点数量适当冗余即可,不用太大;
slots(哈希槽)
Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384个槽(slot),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽。
集群使用公式CRC16(key)% 16384来计算键key属于哪个槽,其中CRC6(key)语句用于计算键key的CRC16校验和。