目录
如果某个服务器程序,只有一个节点(只搞一个物理服务器,来部署这个服务器程序),那如果这个机器挂了,就意味着服务中断了,同时一个节点的服务器性能/支持的并发量也是比较有限的。
为了解决上述的单点问题,就引入了分布式系统。在分布式系统中,往往希望有多个服务器来部署redis服务,从而构成一个redis集群,此时就可以让这个集群给整个分布式系统中的其他服务,提供更稳定/更高效的数据存储功能。
Redis主要有以下几种部署模式:主从模式、主从+哨兵模式、集群模式。
主从复制
Redis 主从复制是一种数据同步机制,允许将一个 Redis 服务器(主节点)的数据复制到一个或多个 Redis 服务器(从节点)。主节点主要负责处理写 操作,而从节点通过复制主节点的数据来提供读服务。这种机制主要用于数据冗余、负载均衡和故障恢复。
之前只是单个redis服务器节点,此时这个机器挂了整个redis也就挂了。而上述的主从结构,这些redis的机器不太可能同时挂了。
注意 :主从模式,主要是针对于读 操作进行并发量和可用性的提高。而写操作的话,无论是可用性还是并发,都是非常依赖主节点的,主节点又不能搞多个(如果有多个主节点,会存在数据无法同步问题)
配置
配置redis主从结构,首先需要启动多个redis服务器。正常来说,每个redis服务器程序,应该在一个单独的主机上。(下面主要演示的是在一个主机上,运行多个redis服务器程序)
但是要注意,在一台主机上,运行多个redis服务器进程,需要保证多个redis-server的端口是不同的。(redis-server的默认端口是6379)。
如何指定redis-server的端口呢?
- 可以在启动程序的时候,通过命令行来指定端口 -p选项。
- 也可以直接在配置文件中,来设定端口号。
下面来介绍。一主二从的结构,通过配置文件来设定端口号。( 主节点配置不用变,主需要修改从节点配置即可**)**
-
创建一个redis-conf目录,将主节点的配置文件复制两份到该目录下,并命名为slave1,conf、slave2.conf,并修改配置文件中的端口号。
-
同时将配置文件中的daemonize选项改成yes,表示让redis-server按后台方式运行

- 使用redis-server ./slave1.conf启动一个新的redis服务器进程(slave2.conf也是如此)。

- 但是此时三个redis节点直接还没有主从关系,互相独立,要想形成主从结构还需要进一步进行配置。要想配置成主从结构,就需要使用slaveof。(这里以6379端口为主节点)
a. 在配置文件中加入slaveof {masterHost} {masterPort}随redis启动生效。
b. 在redis-server启动命令时加入 --slaveof {masterHost} {masterPort}生效。
c. 直接使用redis命令:slaveof {masterHost} {masterPort}生效。
- 主从结构设置完成后,使用netstat -anp|grep redis查看连接情况。

如上图所示,前面三个连接为三个redis-server服务器程序的连接。后面四个连接,其实也就如上图所画,两两为一对,描述的是其中一个redis从节点,和主节点之间建立的tcp连接。
查看主从结构信息
使用redis命令info replication。
主节点信息:

- offset:表示的是从节点和主节点之间,同步数据的进度。从节点和主节点之间的数据同步不是瞬间完成的。
- lag:表示延迟的意思。主从节点之间通过网络通信,当前在同一主机上所以不太会存在延迟。
- master_replid:相当于主节点的身份标识。
- master_repl_offset:表示主机点的数据进度。
- 最后四个选项表示挤积压缓冲区:支持部分同步机制的实现。本质上就是一个内存中的队列,会记录最近一段时间修改的数据。但是总量有限,所以随着时间的推移,会把之前旧的数据逐渐删除。
从节点信息:

connected_slave:连接的从节点的数量。说明从节点,还能继续连接从节点。
断开复制
slaveof命令不但可以建立复制,还可以在从节点执行slaveof no one来断开与主节点复制关系。

可以发现,从节点与主节点断开复制后,自己就变成主节点了(只是代表不在属于别人的从节点的意思,不是指变成了另外两个节点的主节点)。但是已经存在的数据,是不会抛弃的,不过后续原主节点针对数据做出的修改,从节点就无法再同步数据了。
注意:这里通过命令行的方式修改的主从结构是临时性的,如果重新启动了redis服务器,仍然会按照最初在配置文件中设置的内容来建立主从结构。
拓扑
redis的复制拓扑结构可以支持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:一主一从、一主多从、树状主从结构。
一主一从结构
⼀主⼀从结构是最简单的复制拓扑结构,用于主节点出现宕机时从节点提供故障转移⽀持。
当应用写命令并发量较高且需要持久化时,可以只在从节点上开启AOF,这样既可以保证数据 安全性同时也避免了持久化对主节点的性能干扰。但是这种设定方式,有一个验严重的缺陷,主节点一旦挂了,不能让它自动重启,如果自动重启,此时没有AOF文件,就会丢失数据,进一步主从同步,就会把从节点的数据也给删除。所以需要让主节点从从节点这里获取到AOF文件后,再启动。

一主多从结构
⼀主多从结构(星形结构)使得应用端可以利用多个从节点实现读写分离。对于读比重较大的场景,可以把读命令负载均衡到不同的从节点上来分担压力。
但是这种结构随着从节点数量的增加,同步一条数据,就需要传输多次,对主节点的网络带宽压力还是比较大的。

树形主从结构
树形主从结构(分层结构)使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主节点继续向下层复制(但是实际上仍是从节点,自身仍然是不能修改数据的)。这样的设定方式,就减少了主节点的网络带宽压力。
但是这种结构一旦数据进行修改了,同步延时的时间是比较长的。

主从复制的基本流程

1)保存主节点信息:保存主节点的ip和端口。
2)主从建立连接:建立TCP连接(三次握手),验证双方是否能正确读写数据。
3)发送ping命令:验证主节点是否能够正常工作。
4)权限验证:如果redis开启了密码,就会进行权限验证。
5)同步数据集:对于首次建立复制的场景,主节点会把当前持有的所有数据全部发送给从节点,这步操作耗时是最长的,分为两种情况:全量同步和部分同步。
6)命令持续复制:当从节点复制了主节点的所有数据之后,针对之后的修改命令,主节点会持续的把命令发送给从节点,从节点执行修改命令,保证主从数据的一致性。(实时复制)
数据同步
redis提供了psync命令,完成数据同步的过程。实际上psync不需要我们手动后执行,redis服务器会在建立好主从同步关系之后,自动执行psync。从节点执行psync,从主机点拉取数据。
同步过程分为:全量复制和部分复制。
- 全量复制:⼀般用于初次复制场景,Redis早期支持持的复制功能只有全量复制,它会把主节点全部数据⼀次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。
- 部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发数据给从节点。因为补发的数据远小于全量数据,可以有效避免全量复制的过高开销。
PSYNC的语法格式
psync replicationid offset
- 如果replicationid 设为?并且offset设为-1,此时就是在尝试进行全量复制.。
- 如果replicationid offset 设为了具体的数值,则是尝试进行部分复制。由主节点自行判定,看当前是否方便给部分数据,如果不方便就只能全量复制了。
replicationid/replid(复制id)
主节点重新启动或者从节点晋级成主节点,都会生成这个id。同一个主节点,每次重启,生成的replicationid也会变化。
从节点和主节点建立连接之后,就会获取到主节点的replicationid。可以通过info replication命令查看这个id。
offset(偏移量)
主从节点都会维护自身复制偏移量。主节点在处理完写入命令后,会把命令字节长度做累加记录,统计信息在info replication中的master_repl_offset指标中。
从节点在接受到主节点发送的命令后,也会累加记录自身的偏移量,统计信息在info replication中的slave_repl_offset指标中。
同时从节点还会每秒上报自身的复制偏移量给主节点,主节点也会保存从节点的复制偏移量。
replid+offset共同标识了一个数据集。如果两节点的replid和offset都相同,那么这两个节点上持有的数据,就一定相同。
全量复制的流程

1)从节点发送psync命令给主节点进行数据同步,由于是第⼀次进行复制,从节点没有主节点的运行ID和复制偏移量,所以发送psync ? -1。
2)主节点根据命令,解析出要进行全量复制,回复+FULLRESYNC响应。
3)从节点接收主节点的运行信息进行保存,如replid。
4)主节点执行bgsave进行RDB文件的持久化。
5)主节点发送RDB文件给从节点,从节点保存RDB数据到本地硬盘。
6)主节点将从生成RDB到接收完成期间执行的写命令,写入缓冲区中,等从节点保存完RDB文件后,主节点再将缓冲区内的数据补发给从节点,补发的数据仍然按照RDB的⼆进制格式追加写入到收到的EDB文件中,保持主从⼀致性。
7)从节点清空自身原有旧数据。
8)从节点加载RDB文件得到与主节点⼀致的数据。
9)如果从节点加载RDB完成之后,并且开启了AOF持久化功能,它会进行bgrewrite操作,得到最近的AOF⽂件。
Redis从2.8.18版本开始支持无硬盘模式全量复制:主节点在执行RDB生成流程时,不会生成RDB文件保存在硬盘中,而事故直接把生成的RDB数据通过网络发送给从节点,这样就节省了一系列写硬盘和读硬盘的操作开销。
部分复制的流程

1)当主从节点之间出现网络中断时,如果超过repl-timeout时间,主节点会认为从节点故障并中断复制连接。
2)主从连接中断期间主节点依然响应命令,但这些复制命令都因网络中断无法及时发送给从节点,所以暂时将这些命令滞留在积压缓冲区中。
3)当主从节点网络恢复后,从节点再次连上主节点。
4)从节点将之前保存的replicationId和复制偏移量作为psync的参数发送给主节点,请求进行部分复制。
5)主节点接到psync请求后,进行必要的验证。如果从节点的进度已经超出积压缓冲区的范围了,就只能进行全量复制了。如果没有超出范围,就根据offset去复制积压缓冲区查找合适的数据,并响应+CONTINUE给从节点。
6)主节点将需要从节点同步的数据发送给从节点,最终完成⼀致性。
实时复制
主从节点在建立复制连接后,主节点会把自己收到的修改操作,通过tcp长连接的方式,源源不断的传输给从节点,从节点就会根据这些请求来同时修改自身的数据。从而保持和主节点数据的⼀致性.
在进行实时复制时,需要保证连接处于可用状态,这就需要通过心跳包机制来维护。
主节点:默认每隔10s给从节点发送一个ping命令,从节点收到就返回pong。判断从节点的存活性和连接状态。
从节点:默认每隔1s给主节点发送一个特定的请求,上报当前从节点复制数据的进度。
如果主节点发现从节点通信延迟超过repl-timeout配置的值(默认60秒),则判定从节点下线,断 开复制客户端连接。从节点恢复连接后,心跳机制继续进行。
主从复制的缺点
- 从机多了,复制数据的延时非常明显。
- 主机挂了,从机不能升级成主机,替换原来的主机,只能通过人工干预的方式恢复。(为了解决这个问题,就引入了哨兵模式)
哨兵(Sentinal)
哨兵机制是通过独立的进程来体现的,和之前的redis-server是不同的进程。
redis-sential不负责存储数据,只是对其他的redis-server进程起到监控的效果。通常哨兵节点,也会搞一个集合(多个哨兵节点构成)。
Redis-Sential架构
这三个哨兵进程会监控所有的master和slave。(监控:与这些进程建立tcp长连接,通过连接,定期发送心跳包,判断某个主机是否挂了)

针对主节点故障情况的处理流程
- 主节点故障,从节点同步连接中断,主从复制停止。
- 哨兵节点通过定期监控发现主节点出现故障。哨兵节点与其他哨兵节点进行协商,达成多数认同主节点故障的共识,防止出现误判。
- 如果主节点确实挂了,哨兵节点之间使用Raft算法选举出⼀个leader角色,由该节点负责后续的故障转移工作。
- 哨兵leader开始执行故障转移:从节点中选择⼀个作为新主节点,就会自动控制该节点执行slaveof no one让其变成主节点,并且控制其他节点,修改slaveof 的主节点ip port,连接上新的主节点。
- 最后告知客户端,让客户端能够连接新的主节点。
如果后续之前挂了的主节点,修复故障了,就可以作为一个新的从节点,连接到现在新的主节点上。
Redis-Sential的核心功能:
- 监控:Sentinel节点会定期检测redis主从节点的工作状态。
- 故障转移:实现从节点晋升为主节点并维护后续正确的主从关系。
- 通知:Sentinel节点会将故障转移的结果通知给客户端。
注意:哨兵节点最高要搞奇数个,最少也应该是3个,和后续将到的选举leader有关。
使用docker搭建哨兵
-
使用docker获取到redis的镜像。
docker pull redis:5.0.9
-
通过docker-compose进行容器编排(通过一个配置文件,把具体要创建哪些容器,以及每个容器运行的各种参数,描述清楚)。
a)编排redis主从节点配置文件。创建/redis/redis-data/docker-compose.yml。
version: '3.7'
services:
master: #服务的名字,自己设定
image: 'redis:5.0.9' #基于哪个镜像创建
container_name: redis-master
restart: always #异常情况是否自动重启
command: redis-server --appendonly yes #启动服务器时,用到哪些选项
ports: #端口映射,宿主机端口:容器内部端口
- 6379:6379
slave1:
image: 'redis:5.0.9'
container_name: redis-slave1
restart: always
command: redis-server --appendonly yes --slaveof redis-master 6379 #docker会自动将容器名解析成ip
ports:
- 6380:6379 #注意这里是三个容器,所以都使用6379端口不会有冲突
slave2:
image: 'redis:5.0.9'
container_name: redis-slave2
restart: always
command: redis-server --appendonly yes --slaveof redis-master 6379
ports:
- 6381:6379
在创建docker-compose.yml的目录下启动所有容器。
docker-compose up -d
查看运行日志
docker-compose logs
b)编排redis哨兵节点配置文件。创建/redis/redis-sential/docker-compose.yml。
version: '3.7'
services:
sentinel1:
image: 'redis:5.0.9'
container_name: redis-sentinel-1
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel1.conf:/etc/redis/sentinel.conf #配置文件映射,前面是当前目录下创建的配置文件
ports:
- 26379:26379
sentinel2:
image: 'redis:5.0.9'
container_name: redis-sentinel-2
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel2.conf:/etc/redis/sentinel.conf
ports:
- 26380:26379
sentinel3:
image: 'redis:5.0.9'
container_name: redis-sentinel-3
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel3.conf:/etc/redis/sentinel.conf
ports:
- 26381:26379
networks:
default:
external:
name: redis-data_default #让三个哨兵节点,加入到redis服务器的局域网中
slave1.conf、slave2.conf、slave3.conf的内容。
bind 0.0.0.0
port 26379
sentinel monitor redis-master redis-master 6379 2 #监控哪个服务器
sentinel down-after-milliseconds redis-master 1000
#参数解析
第一个redis-master:服务器名
第二个redis-master:ip,docker会自动进行域名解析。
6379:填宿主机端口号
2:法定票数。只有当达到该数量的哨兵节点都觉得该节点挂了,才算该节点真的挂了。
down-after-milliseconds:心跳包超时时间。
启动所有容器。
docker-compose up -d
哨兵作用演示
手动将redis-master干掉。
docker stop redis-master
观察哨兵日志。

可以看到哨兵发现主节点sdown,进一步由于主节点宕机得票达到3/2,达到法定得票,于是redis-master被判定为odown。
- 主观下线(sdown):哨兵感知到主节点没心跳,判定为主观下线。
- 客观下线(odown):多个哨兵达成一致意见,才能认为master确实下线。
接下来哨兵选举出来新的master,在上图中,新选出的主节点是172.18.0.3 6379。

redis-master重启之后。
docker start redis-master
观察哨兵日志。可以看到刚才新启动的redis-master被当成了slave。

选举原理
哨兵leader的选举
选举哨兵的leader。这个选举的过程涉及到Raft算法。
观察日志,可以看到谁给谁投了票。

选举流程:
- 每个哨兵节点都给其他所有的哨兵节点,发起一个"拉票请求"。
- 收到拉票请求的节点,会回复一个"投票响应",响应结果有两种,投或不投。
- 一轮投票完成之后,发现得票超过盘数的节点,自动成为leader。(这也是为啥建议哨兵节点的数量设置成奇数个的原因。如果是偶数个,则增大了平票的概率,带来了不必要的开销)
- leader节点负责挑选一个slave称为新的master,当其他sential发现新的master出现,就说明选举结束了
leader挑选合适的slave成为新的master
挑选规则:
- 比较优先级,优先级高(数值小)的选中。优先级是配置文件中的配置项(slave-priority或者replica-priority)。
- 优先级相同的,比较replication offset谁复制的数据最多,谁选中。
- 上述都相同的,比较runid,谁小谁选中。
RunID在节点启动时自动随机生成的一串数字,并保持不变直到节点重启。用于区分集群中的不同节点。通过redis命令info server查看。