Redis系列-4 Redis集群介绍

Redis集群

Redis提供了持久化能力,保证了重启不会丢失数据;但Redis重启至完全恢复期间,缓存不可用。另外,对于高并发场景下,单点Redis服务器的性能不能满足吞吐量要求,需要进行横向扩展。此时,可通过搭建Redis集群解决如上两个问题。按照并发量和具体业务的要求,可以部署三种架构的集群:主从模式、哨兵模式、Cluster模式,以下分章节分别进行介绍。

1.Redis主从复制

1.1 流程介绍

主从复制的整体流程可用下图表示:

具体流程

【1】从节点向主节点发送PSYNC命令(旧版本Redis发送SYNC)建立连接;

【2】主节点收到PSYNC消息后,判断是否需要全量同步:第一次连接时触发全量同步,通过BGSAVE机制将内存快照保存为RDB文件中,并将RDB文件发送给从节点;否则,触发增量同步(如主从节点因网络等原因断开重连);

【3】从节点将数据清空后,将RDB文件的数据写入内存;

【4】此过程中主节点的累积的写操作以AOF文件的形式增量同步至从节点;

【5】之后,主节点的所有写操作,通过AOF文件以增量形式通知从节点。

总之,从节点第一次同步数据以RDB形式进行全量同步,后续以AOF形式进行增量同步。

1.2 环境搭建

在持久化文章中为快速搭建演示环境,使用了docker方式安装Redis;

本文中,为了方便搭建集群,使用二进制的方式运行Redis.

本地安装Redis:

shell 复制代码
#下载和安装Redis
wget http://download.redis.io/releases/redis-5.0.4.tar.gz
tar xzf redis-5.0.4.tar.gz
cd redis-5.0.4
make

#启动redis(不指定配置文件时使用根目录下的redis.conf)
src/redis-server

#使用客户端连接
src/redis-cli
redis> set foo bar
OK
redis> get foo
"bar"

#使用客户端关闭redis
redis> shutdown

说明:每运行redis-server一次,会创建一个独立的Redis进程,需要注意使用不同的配置文件,以防止端口冲突。

集群搭建:

搭建如下图所示的主从集群, 包含1个主节点以及两个从节点:主节点监听6001端口,从节点分别监听6002和6003端口。

step1: 准备redis.conf文件

为每个redis实例准备一份redis.conf文件

shell 复制代码
#创建测试用例目录
mkdir -p /temp/slaveof/6001
mkdir -p /temp/slaveof/6002
mkdir -p /temp/slaveof/6003

#将redis根目录下的redis.conf复制到测试用例目录下
cp redis.conf /temp/slaveof/6001/
cp redis.conf /temp/slaveof/6002/
cp redis.conf /temp/slaveof/6003/

#修改监听的端口号
sed -i 's/6379/6001/g' /temp/slaveof/6001/redis.conf
sed -i 's/6379/6002/g' /temp/slaveof/6001/redis.conf
sed -i 's/6379/6003/g' /temp/slaveof/6001/redis.conf

step2: 配置主从关系

在从节点6001和6002中执行

shell 复制代码
slaveof 127.0.0.1 6001

也可以在redis.conf文件中加入slaveof配置.

step3: 启动redis

shell 复制代码
./redis-server /temp/slaveof/6001/redis.conf
./redis-server /temp/slaveof/6002/redis.conf
./redis-server /temp/slaveof/6003/redis.conf

启动日志如下所示:

主节点6001, 接收6002和6003的连接请求:

从节点6002, 连接到6001:

从节点6003, 连接到6001:

step4: 读写数据

shell 复制代码
#主节点写入数据
主节点6001:0>set key1 value1
"OK"
主节点6001:0>get key1
"value1"


#从节点读取数据,可以读取写入主节点的数据
从节点6002:0>get key1
"value1"


#从节点写数据,抛出异常
从节点6002:0>set key2 value2
"READONLY You can't write against a read only replica."

说明:主节点可读写,从节点只读;主节点写入的数据,通过redis主从复制机制同步至从节点。

另外,从节点后也可以添加从节点,如下所示:

只需要将6003节点中redis.conf的slaveof 设置为6002节点,即将"slaveof 127.0.0.1 6001"修改为"slaveof 127.0.0.1 6002".

2.哨兵模式

主从模式的主节点和从节点固定,程序不能自动切换。当主节点宕机时,整个缓存将不可写,直到手动恢复主节点(或者将幸存的从节点设置为主节点)为止。

章节1.2中存在 主节点(6001)和从节点(6002和6003), 如果主节点down机了,可以依次执行如下命令恢复环境:

shell 复制代码
#不妨将6002设置为主节点
主节点6002:>slaveof no one
主节点6003:>slaveof 127.0.0.1 6002

对于实际场景,显然不能通过手动方式去切换主从节点。哨兵模式的引入为其提供了一个解决方案,主从切换实现自动化。可以推断:哨兵需要具备识别主节点down机,从从节点选取主节点,调整节点的主次状态等三种能力。

2.1 流程介绍

在流程介绍之前,有必须关注一下主观离线和客观离线以及sentinel.conf(哨兵配置文件)中的配置项。
主观离线和客观离线:

哨兵与Redis主/从节点之间通过心跳保活,当哨兵发现某个节点超时未发心跳消息,认为这个节点离线,称为主观离线(主观怀疑,可能是网络问题,也可能离线了);此哨兵会询问其他哨兵该节点的状态信息,当足够数量的哨兵都主观认为Redis节点离线时,Redis客观离线(确实离线)。

配置项:
[1] 配置监听的主节点

sentinel monitor <master-name> <ip> <port> <quorum>

master-name表示主节点名称-自定义; ip和port表示主节点的ip和端口;quorum为主观离线转转客观离线的标准。如sentinel monitor mymaster 127.0.0.1 6379 2表示:哨兵监听主节点(127.0.0.1:6379),当有2个哨兵主观认为主节点离线时,此节点被标记为客观离线。

[2] 配置主观离线时间

 sentinel down-after-milliseconds <master-name> <milliseconds>

milliseconds表示Redis节点的心跳超时时间。当心跳超时后,哨兵认为该节点主观离线。

sentinel down-after-milliseconds mymaster 30000设置超时时间为30s.

[3] 配置通知脚本

当哨兵监听事件发生时,会调用配置的脚本,启动时需要保证该脚本存在。

sentinel notification-script <master-name> <script-path>
sentinel client-reconfig-script <master-name> <script-path>

script-path为脚本的存放路径。配置notification-script时,有节点主观或者客观离线会触发;配置client-reconfig-script时,只有故障转移(主节点替换)时才会触发。

哨兵模式的工作流程如下:

[1] 哨兵启动后,与Redis的主从节点建立心跳连接机制;

[2] 哨兵每秒向Redis节点发送PING心跳消息,等待PONG心跳回复消息;

[3] 当哨兵超时(大于down-after-milliseconds)未收到节点信息时,将该节点标记为主观离线;

[4] 哨兵向其他哨兵发送请求,获取该节点的状态,如果超过quorum数量的哨兵认为该节点离线,将该节点标记为客观离线;

[5] 如果该节点为主节点,触发故障转义流程;

[6] 哨兵间会选举一个leader,用于主导故障转义流程;

[7] 哨兵leader根据条件从从节点中选出主节点A(127.0.0.1 6001);

[8] 将选出的节点设置为主节点(slaveof no one),将其他节点设置为该节点的从节点(slaveof 127.0.0.1 6001);

其中,哨兵leader选择主节点的条件如下:

(1)排除主观离线的节点;

(2)排除优先级(slave-priority)为0的节点;

(3)选择优先级最高的节点;

(4)优先级相同,选择复制偏移量最大的节点(与原master数据重合度更高);

2.2 环境搭建

章节1.2 的基础上添加两个哨兵,如下图所示:

step1: 准备redis.conf文件

为每个哨兵准备一份redis.conf文件

shell 复制代码
#创建测试用例目录
mkdir -p /temp/sentinel/20001
mkdir -p /temp/sentinel/20002

#将redis根目录下的sentinel.conf复制到测试用例目录下
cp sentinel.conf /temp/sentinel/20001/
cp sentinel.conf /temp/sentinel/20002/

#修改哨兵监听的端口号
sed -i 's/26379/20001/g' /temp/sentinel/20001/sentinel.conf
sed -i 's/26379/20002/g' /temp/sentinel/20002/sentinel.conf

step2:添加哨兵的检测规则

shell 复制代码
#设置监控的redis主节点的ip、port和quorum(当认为master主节点主观失联的哨兵数超过quorum,主节点客观失联)
sentinel monitor mymaster 127.0.0.1 6001 2

#主节点响应答哨兵的超时时间, 默认30秒
sentinel down-after-milliseconds mymaster 5000

# 故障转移的超时时间(r两次failover的间隔时间)
sentinel failover-timeout mymaster 60000

同时修改工作目录和日志配置:

shell 复制代码
pidfile "/temp/sentinel/20001/redis-sentinel.pid"
logfile ""
dir "/temp/sentinel/20001/tmp"

step3:启动哨兵程序

shell 复制代码
./redis-sentinel /temp/sentinel/20001/sentinel.conf
./redis-sentinel /temp/sentinel/20002/sentinel.conf

启动日志如下所示:


step4:重启主节点(以检测哨兵的反应)

(1) 通过info replication查看各个节点的状态:

shell 复制代码
67-6001:0>info replication
"# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6002,state=online,offset=424559,lag=1
slave1:ip=127.0.0.1,port=6003,state=online,offset=424559,lag=1
...
"

67-6002:0>info replication
"# Replication
role:slave
master_host:127.0.0.1
master_port:6001
...
"

67-6003:0>info replication
"# Replication
role:slave
master_host:127.0.0.1
master_port:6001
...
"

此时6001为主节点,6002和6003为从节点。

(2) 手动停止6001节点:

shell 复制代码
67-6001:0>shutdown

此时,6002和6003报错,连接主节点失败:

当哨兵检测到6001主节点宕机后,进行节点选举:

选举完成后,将结果通知给各节点。此时选择了6003作为主节点, 再次查看6003节点的信息:

shell 复制代码
67-6003:0>info replication
"# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6002,state=online,offset=459708,lag=0
...
"

3.cluster集群

3.1 流程介绍

支持多个master节点,每个master节点可以有多个slave节点,master可写而slave只读;cluster集群自带故障转移能力,不需要配置哨兵。

没有中心节点,对数据进行分片存储,每个master节点存储不同的数据;当有master节点宕机时,不影响其他节点的读写。

Redis节点通过gossip协议建立集群。

gossip工作原理是节点间的信息交换:不断地信息交换,很快所有节点都会拥有集群的完整信息,如同流言散播。

gossip协议包括meet,ping,pong,fail等消息类型:meet用于加入集群,ping用于心跳和数据交换,pong用于ping和meet的返回消息;fail用于广播节点宕机。

集群内部通过分片机制,每个redis节点负责整体数据的一个子集,数据和节点之间存在映射关系。

因此,在数据和redis节点之间引入了哈希槽的概念:一个集群固定有16384(2^14-1)个槽位,每个Redis节点分配一段槽位范围(slot range),数据(key)经过哈希算法得到一个哈希槽(slot),算法为CRC16(key) % 16384,

从而确定所属节点。

由于每个节点只负责部分slot(数据库子集), 且slot可能发生前移,使得客户端请求变得复杂。

Cluster集群通过重定向机制解决该问题,重定向机制包括两个消息类型:MOVED和ASK。

当客户端将一个数据操作发送给Redis实例时,如果这个数据所在的slot不是由该Redis实例负责,

该实例会返回一个MOVED消息,如下所示:

(error) MOVED 12345 127.0.0.1:6001

表示客户端正在操作的数据的slot是12345,这个槽点在127.0.0.1:6001实例上。

当客户端请求的数据所在的slot正在前移至另一个Redis实例,

此时给客户端响应一个ASk消息,如下所示:

(error) ASK 12345 127.0.0.1:6002

表示客户端正在操作的数据的slot是12345,这个槽点正在前移到127.0.0.1:6002实例上。

另外,客户端自身也会维护一份槽与节点的映射关系,当客户端操作数据时,先计算键的哈希值并根据映射关系找到对应的节点,然后将数据请求发送给对应节点。

3.1 环境搭建

以章节1为基础,搭建一个三主三从的cluster集群。

step1.准备配置文件

将/temp/slaveof/6001文件夹下的redis.conf配置文件复制6份, 并分别修改端口号为6001-6006:

shell 复制代码
/temp/cluster/6001/redis.conf 中端口port修改为 6001
/temp/cluster/6002/redis.conf 中端口port修改为 6002
/temp/cluster/6003/redis.conf 中端口port修改为 6003
/temp/cluster/6004/redis.conf 中端口port修改为 6004
/temp/cluster/6005/redis.conf 中端口port修改为 6005
/temp/cluster/6006/redis.conf 中端口port修改为 6006

step2.配置为集群模式

在redis.conf文件中添加配置项:

cluster-enabled yes

step3.启动所有节点

shell 复制代码
./redis-server /temp/cluster/6001/redis.conf
./redis-server /temp/cluster/6002/redis.conf
./redis-server /temp/cluster/6003/redis.conf
./redis-server /temp/cluster/6004/redis.conf
./redis-server /temp/cluster/6005/redis.conf
./redis-server /temp/cluster/6006/redis.conf

step4.创建集群

shell 复制代码
./redis-cli --cluster create --cluster-replicas 1 \
127.0.0.1:6001 \
127.0.0.1:6002 \
127.0.0.1:6003 \
127.0.0.1:6004 \
127.0.0.1:6005 \
127.0.0.1:6006

结果如下所示:

此时,主节点为6001,6002,6003, 对应从节点分别为6005,6006,6004.
step5.查看集群节点状态

使用info replication命令查看6001-6005主从节点的状态。
6001节点:

shell 复制代码
127.0.0.1:6001> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6005,state=online,offset=434,lag=1
master_replid:1b684bf671a34b36f80422b41a39eb887f0c15e0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:434
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:434

显示6001为主节点,6005为其从节点。
6005节点:

shell 复制代码
127.0.0.1:6005> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6001
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:546
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:1b684bf671a34b36f80422b41a39eb887f0c15e0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:546
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:546
相关推荐
Java4ye1 小时前
Netty 是如何解析 Redis RESP 协议的——请求篇
后端
刘铸纬8 小时前
Golang中defer和return顺序
开发语言·后端·golang
多多*9 小时前
每天一道面试题之浅浅讲一下java5中的自动装箱和自动拆箱
java·开发语言·spring boot·后端·spring
江湖十年9 小时前
Go 语言中的结构体内存对齐你了解吗?
后端·go
cjay_fighting9 小时前
redis7新特性、源码解析
数据库·redis·分布式
passion更好10 小时前
IT专业入门,高考假期预习指南
java·前端·人工智能·后端·python·机器学习·高考
谦宇10 小时前
Nest 接入消息队列 RabbitMQ
前端·后端·nestjs
破晓的历程11 小时前
Spring Boot的无缝衔接:深入解析与实践
数据库·spring boot·后端
肖哥弹架构11 小时前
解释器模式(Interpreter Pattern):电商平台优惠规则解析实战案例分析
前端·后端·程序员
长河12 小时前
Jackson使用详解
java·开发语言·后端