Redis作为我们最常用的内存数据库之一,在面对高并发环境时,也需要保持高可用性。因此,通常情况下我们需要配置Redis集群。
Redis集群,通常是在多个服务器上部署Redis服务,每个服务器作为一个节点,所有的节点之间互相联系,数据互通,这样就构成了Redis集群。
不过通常我们手上可能没有这么多服务器,不足以搭建集群,因此本文就在一台电脑上运行多个Redis进程为例,模拟集群搭建过程。
Redis集群有三种模式,下面我就来一一介绍一下它们的原理和搭建方式。
1,主从复制模式
(1) 原理与过程
主从复制模式是将多个节点中分为主节点和从属节点,通常主节点只有一台,从节点有很多台,主节点提供写入和读取功能,但是从属节点只提供读取功能。
图中master
就是主节点,而slave
就是从属节点,通常我们向主节点写入数据之后,主节点就会同步到从节点中去。
可见主从复制模式有以下优点:
- 读写分离,职责明确
- 高可用,如果从属节点挂掉一个,则整个集群仍然可用
主从复制的数据同步过程如下:
- 首先主节点启动,然后从属节点启动,从属节点会连接主节点并发送
SYNC
命令以请求同步 - 主节点收到
SYNC
命令之后,就会执行BGSAVE
命令生成RDB
文件并使用缓冲区记录此后执行的写入命令 - 执行了
BGSAVE
之后,就向所有从属节点发送快照文件 - 从属节点收到快照文件之后,会丢弃自己已有的所有旧数据并把收到的快照写入数据库
- 之后,主节点会把缓冲区中的写命令发送给所有从节点实现从节点的增量同步
(2) 部署主从复制集群
这里我们搭建一个“一主二从”的集群,也就是一个主节点两个从属节点的集群,集群地址列表如下:
节点 | 地址 | 端口 |
主节点 | 127.0.0.1 |
7000 |
从属1 | 127.0.0.1 |
7001 |
从属2 | 127.0.0.1 |
7002 |
首先我们要安装好Redis,建议在Linux服务器上面进行搭建,确保redis-server
,redis-cli
和redis-sentinel
命令可以正常使用。这里就不讲述Redis的安装了,对于基本安装和配置不明白的可以参考我之前写的Linux上Redis安装配置的博客。
创建三个文件夹,里面分别存放我们三个节点的Redis配置文件,三个Redis节点的运行路径也对应着我们三个目录。我这里创建如下:
我这里创建了7000
、7001
和7002
文件夹,每个文件夹里面都有一个redis.conf
表示每个节点的配置文件。
7000
里面是主节点配置,文件内容如下:
# 主节点配置 # 端口号 port 7000 # 后台运行 daemonize yes # 密码 requirepass 12345678
可见主节点配置和平常没什么特别的地方。
7001
和7002
里面是从属节点配置,内容如下:
# 从属1配置 # 端口号 port 7001 # 后台运行 daemonize yes # 密码 requirepass 12345678 # 指定该从属的主节点的地址和端口 replicaof 127.0.0.1 7000 # 主机密码 masterauth 12345678
# 从属2配置 # 端口号 port 7002 # 后台运行 daemonize yes # 密码 requirepass 12345678 # 指定该从属的主节点的地址和端口 replicaof 127.0.0.1 7000 # 主机密码 masterauth 12345678
可见从属节点中,需要配置replicaof
指定其对应的主节点的地址和端口号,以及masterauth
指定主节点的密码,若主节点没有配置密码就可以省略此项,否则主节点配置了密码但是不写masterauth
的话,就会导致从属节点同步数据失败!
我们通常最好是设定全部节点密码都一样。
然后依次使用cd
命令进入三个文件夹并执行命令:
redis-server ./redis.conf
这样,三个节点就启动了!
我们可以用redis-cli
命令连接一下我们任意一个节点,比如这里我连接主节点:
redis-cli -h127.0.0.1 -p7000
-h
后面是地址,-p
后面是端口。连上后使用auth 密码
认证,然后就可以执行命令了!
先执行以下命令查看节点信息:
info replication
现在在主节点写一个数据试试:
set a b
然后退出连接一个从属节点:
redis-cli -h127.0.0.1 -p7001
在从属节点获取刚刚写的数据:
get a
这说明主节点写入数据后,成功地同步到了从属节点。
(3) 主从复制模式的问题
事实上,主从复制模式的缺陷也很明显:假设主节点挂了,我们需要手动去设置一个从属节点变为主节点并修改其它节点配置。
因此,主从复制模式目前用的不是说特别广泛。但是为什么这里还要学习主从复制模式呢?因为下面的模式很大程度上都基于主从复制模式。我们接着来看。
2,哨兵模式
(1) 原理和过程
哨兵模式事实上是上述主从复制模式的一种扩展,也就是说在主从复制模式的基础上,增加哨兵节点以监视所有节点的情况,假设主节点挂掉了,哨兵节点会从所有从属节点中选举一个节点作为新的主节点,并修改其余从属节点的配置。
哨兵节点自身不会提供任何数据的读写存储功能,仅仅是负责监视所有的节点。哨兵不仅会监视所有复制数据存储读写的主从节点,还会互相监视。
通常哨兵节点也会有好几个,不过当其中一个哨兵监视到主节点挂掉时,系统并不会马上进行选举,因为这仅仅是一个哨兵看到主节点挂掉,这时称之为主观下线。而当其它哨兵也发现主节点挂掉时,并且发现主节点挂掉的哨兵达到一定值时,哨兵之间才会进行选举过程,这时称之为客观下线。那要多少个哨兵发现主机挂掉才会触发选举呢?这个是可以配置的,我们下面来看。
哨兵模式工作过程:
- 每个哨兵进程以每秒钟一次的频率向其它所有节点发送
PING
命令 - 如果一个节点距离最后一次有效回复时间间隔超过阈值,则这个节点会被这个哨兵标记为主观下线(
SDOWN
),这个阈值可以通过哨兵节点配置文件中的down-after-milliseconds
配置,单位ms - 当主节点被任意一个哨兵标记为
SDOWN
,则正在监视主节点的所有哨兵会以每秒一次的频率确认主服务器的确进入了SDOWN
状态 - 当有足够数量的哨兵在指定的时间范围内确认主节点进入了
SDOWN
,则主节点会被标记为客观下线(ODOWN
),进入故障切换(failover
)过程开始选举,哨兵节点选举出新的主节点后,会以发布订阅模式通知其余从属节点修改配置文件指向新的主节点
可见哨兵模式是一个自动化版的主从复制模式。
(2) 哨兵模式集群搭建和配置
在这里我们搭建一个“一主二从三哨兵”集群,如下:
节点 | 地址 | 端口 |
主节点 | 127.0.0.1 |
7000 |
从属1 | 127.0.0.1 |
7001 |
从属2 | 127.0.0.1 |
7002 |
哨兵1 | 127.0.0.1 |
8000 |
哨兵2 | 127.0.0.1 |
8001 |
哨兵3 | 127.0.0.1 |
8002 |
上述我们已经启动了“一主二从”集群,这里方便起见我们只需再创建三个文件夹存放三个哨兵的配置文件并在其中启动哨兵进程即可(哨兵模式中主从节点搭建配置和上述完全一样)。现在我这里目录如下:
然后我贴出上述第一个哨兵的配置:
# 哨兵1配置 # 端口 port 8000 # 被监视主机 sentinel monitor mymaster 127.0.0.1 7000 1 # 后台运行 daemonize yes # 连接主机密码 sentinel auth-pass mymaster 12345678
这里sentinel monitor
表示指定集群中主节点的地址和端口。这个配置有四个参数意义如下:
mymaster
自定义的主节点名称,同一个集群中所有哨兵的主节点名称应当相同127.0.0.1
主节点地址7000
主节点端口1
表示触发客观下线的哨兵节点数,这里表示只要有一个哨兵标记主节点为主观下线,就触发选举
然后sentinel auth-pass
用于指定主节点密码,后面mymaster
表示主节点名称,12345678
就是主节点密码。
其余哨兵节点配置我不再一一列出,只需复制上述配置文件然后修改里面的端口号配置,其余不变即可。
当然注意集群中所有主从节点的密码要全部一样!否则可能出现同步和选举上的问题。
主从节点都还在运行,这时我们依次使用cd
命令进入三个哨兵配置文件夹执行下面命令启动哨兵进程(平时需要先启动主节点,再启动从属节点,最后启动哨兵节点,注意这个顺序):
redis-sentinel ./sentinel.conf
可见这里哨兵进程需要用redis-sentinel
命令启动,后面参数也是接的配置文件路径。
这时,整个哨兵模式集群搭建完毕!大家可以这个时候手动停掉主节点,然后依次连接从属节点执行info replication
查看谁被选举成了新的主节点,这里我就不再演示了。
(3) Spring Boot配置连接Redis哨兵模式集群
我们很多时候使用spring-data-redis
来连接Redis,不过这里连接哨兵集群和平时配置就有所不同了,我这里配置如下:
# 主节点名称 spring.redis.sentinel.master=mymaster # 哨兵节点列表,用英文逗号隔开 spring.redis.sentinel.nodes=127.0.0.1:8000,127.0.0.1:8001,127.0.0.1:8002 # 主节点的密码 spring.redis.password=12345678
这里主节点名称就是上面哨兵配置中我们定义的mymaster
,然后配置所有哨兵节点,格式为地址:端口
,多个用英文逗号隔开,然后在配置主节点密码(注意是主节点密码)。
这里我们只需配置哨兵节点地址即可,项目启动后会自动通过哨兵节点找到主节点并连接,进而执行读写操作。
3,Cluster模式
这也是官方推荐的模式,是新版本Redis支持的一种集群模式。
(1) 原理和过程
在上述哨兵模式中,已经实现了高可用和读写分离。但是我们也可见每个节点都要储存一份完整的数据,这样很浪费内存。因此Redis官方推出了Cluster模式,这种模式下每个节点不会储存完整的内容,但是节点直接相互连通,所有节点内容加起来才是完整的内容。
上述每个节点可能储存一部分内容,但是不论某个内容存放在哪个节点,我们都可以通过任意一个节点访问到,因为它们之间互相连通。
那么,在整个集群中又是如何存放数据的呢?事实上,搭建集群的时候,Redis会根据节点数量先分配主从节点,然后根据主节点数量平均分配整个空间。
Redis会先把整个集群所使用的储存空间分为一定数量的等分,这个等分就叫做哈希槽(hash slot)。
Redis集群中有16384个哈希槽,那么假设集群中有三个主节点分别是A、B和C,每个主节点对应一个从属节点A1、B1和C1,那么主节点会被分配槽位如下:
- A包含从
0-5460
哈希槽位 - B包含从
5461-10922
哈希槽位 - C包含从
10923-16383
哈希槽位
那我们存放数据时,这个数据到底会放到哪个槽位中去?事实上,Redis也有这样的一个算法:存入数据时,就会对存入的键计算CRC16,然后拿计算出来的值对16384取模得到的结果,就是这个数据的槽位。
同样地,从属节点也会和上面一样同步主节点的内容。假设现在主节点B挂掉了,是不是意味着5461-10922
槽位不能再储存数据了呢?当然不是。这时其对应的从属节点B1会被自动地提升为主节点。
(2) Cluster模式集群搭建和配置
为了保证高可用性,Redis要求Cluster模式至少需要3个主节点,而每个主节点至少要配备一个从属节点,因此我们至少需要6个节点才能搭建起来一个Redis的Cluster集群,所以说实际情况下对我们服务器数量有所要求。
节点如下:
节点 | 地址 | 端口 |
节点1 | 127.0.0.1 |
7000 |
节点2 | 127.0.0.1 |
7001 |
节点3 | 127.0.0.1 |
7002 |
节点4 | 127.0.0.1 |
7003 |
节点5 | 127.0.0.1 |
7004 |
节点6 | 127.0.0.1 |
7005 |
同样地,新建6个文件夹,里面分别存放每个节点的配置文件:
这里放出第一个节点配置:
# 节点1配置 # 端口号 port 7000 # 后台运行 daemonize yes # 开启集群模式 cluster-enabled yes # 配置当集群中有一台机器宕机时集群保持可用 cluster-require-full-coverage no # 设置密码 requirepass 12345678 # 其它集群服务节点密码 masterauth 12345678
对于Cluster模式,我们每个节点都要配置cluster-enabled yes
表示打开集群模式,然后注意配置所有节点密码一样!并配置masterauth
选项表示该节点访问其余节点时的密码,否则也会导致节点之间互相连通时发生错误。
其余的节点配置也可以拷贝这个文件只修改里面的端口配置即可。
然后也是依次使用cd
命令进入每个文件夹并依次执行启动命令:
redis-server ./redis.conf
启动之后,这时所有的节点仍然还是都单独运行的,我们还是需要用redis-cli
命令将它们连结起来,才能完成集群搭建。
命令格式如下:
redis-cli --cluster create 节点1地址:节点1端口 节点2地址:节点2端口 ... --cluster-replicas1-a 密码
--cluster-replicas 1
表示每个主节点分配1个从节点,因此6个节点会被计算为3主3从。如果集群都没有设置密码,可以省略-a
参数。
我这里命令如下:
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas1-a12345678
注意实际情况下指定节点一定要指定节点服务器的外网ip而不是域名或者内网ip!否则可能会连结集群失败。
还有就是我们上述节点端口是7000
、7001
...等等,这个是我们设定的客户端访问端口,而集群中节点互相连通访问的端口不是这个,而对应的是17000
、17001
...等等,也就是在客户端访问端口上加10000
(外网访问端口和节点之间交流的端口总是相差10000
),因此部署在服务器上时也要对应开放节点之间交流端口的防火墙。
然后会出现这些信息,输入yes
确认:
出现这些信息则成功:
现在,我们可以用redis-cli
连接集群:
redis-cli -h127.0.0.1 -p7000-c-a12345678
其中-c
表示以集群模式连接,-a
指定密码,这里要说明的是集群有密码并且以集群模式连接时一定要用-a
指定连接密码,否则如果连接后再用auth
输密码会导致重定向失败!
连接成功,写入数据试一下:
可见连接7000
端口节点并写入数据,结果重定向到端口为7002
的节点写入,这是因为通过上述算法对我们存入的键进行CRC16再取模运算之后得到结果15495
,这个槽位位于第三个节点也就是7002
端口节点,读取也是一样,如果你连接7000
端口节点去取刚刚存入的键,那么它也会重定向到对应位置取得到这个键的值。通过这样重定向实现各个节点连通。
注意如果想重新配置集群,需要停止每个节点并把每个节点生成的这些文件删掉再执行上述步骤!
注意这个nodes.conf
是集群节点自动生成的配置,用于记录节点之间的信息,它默认生成在当前运行目录,和数据文件dump.rdb
在一起,如果你配置了dir
指定了Redis的数据存放目录,则会生成到你指定的数据存放目录中去。平时不需要手动修改,这个文件的文件名默认是nodes.conf
,可以通过cluster-config-file
配置项自定义。
(3) Spring Boot连接Cluster模式集群
同样还是使用spring-data-redis
来连接Redis,配置如下:
# 所有集群节点列表,用英文逗号隔开 spring.redis.cluster.nodes=127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005 # 主机的密码 spring.redis.password=12345678
可见这里只用配置所有节点地址端口和密码即可。
(4) Redisson连接Cluster集群踩坑
今天搭建了个外网服务器集群,并在一个Spring Boot项目中如上进行配置,这个项目使用了Redisson作为分布式锁功能。结果配置之后项目无法启动,并报错:nested exception is org.redisson.client.RedisConnectionException: Not all slots covered! Only 7647 slots are available. Set checkSlotsCoverage = false to avoid this check.
我寻思着我的集群已经正确配置了啊,并且配置过程中没有任何报错。为什么这里提示集群没有覆盖全部哈希槽呢?事实上,这是因为Redisson无法连接所有的节点导致。
上面我们知道了:Redis以集群模式启动时会生成一个nodes.conf
文件记录集群信息,我打开一个节点的这个文件一看,发现问题所在:
搭建集群时生成的nodes.conf
文件内容也很明显:用于记录其余各个节点的id和地址。但是这个里面有个包含myself
的行,显然是记录该节点自己的信息的,但是自己的地址记录的是内网地址,这时导致我们项目从外网获得一个内网地址然后无法连接。
解决方法很显然,就是先停止所有节点,并依次打开每个节点生成的nodes.conf
,找到myself
那一行,将其内网地址改为该节点服务器的外网地址,保存,重新启动节点即可。由于这里已经生成了nodes.conf
文件记录了节点信息,因此再次启动所有节点它们之间就可以再次构成集群,不需要再像第一次创建集群的时候使用redis-cli
命令了!
再次启动项目,就不会出错了!
整个文章参考:
- Redis官方集群搭建文档:传送门
- Spring Data Redis配置文档: