MySQL笔记16
一、主从复制
1.原理
为应对项目访问量增加带来的 Redis 服务器操作延时问题,可采用主从(Master/Slave)架构。其中, Master 节点主要负责写操作,若干 Slave 节点主要负责读操作,二者均为独立的 Redis Server 实例。同时,可单独选一个 Slave 节点进行定期数据备份,以充分发挥 Redis 性能。 Master 和 Slave 的数据并非即时同步,而是在一段时间后趋于同步,保证数据的弱一致性和最终一致性。

全同步过程如下:
Slave 发送 Sync 命令到 Master
Master 启动一个后台进程,将 Redis 中的数据快照保存到文件中。
Master 将保存数据快照期间接收到的写命令缓存起来。
Master 完成写文件操作后,将该文件发送给 Slave。
使用新的 RDB 或 AOF 文件替换掉旧的 RDB 或 AOF 文件。
Master 将这期间收集的增量写命令发送给 Slave 端。
增量同步过程如下:
Master 接收到用户的操作指令,判断是否需要传播到 Slave。
将操作记录追加到 AOF 文件。
将操作传播到其他 Slave:对齐主从库;往响应缓存写入指令。
将缓存中的数据发送给 Slave。
2.配置
(1)一主双从
准备三台虚拟机,配置好主机名、IP 地址和 Redis 环境。本示例中为了方便,在一台虚拟机中配置三个 Redis 实例。

先在根目录下创建 rediscluster 目录,并且把 redis.conf 配置文件复制到这个目录中(方便起见,可关闭 AOF ):
![]()

然后在 rediscluster 目录下分别创建三个文件: redis-6379.conf、redis-6380.conf 和 redis-6381.conf。并分别写入文件内容:




分别启动三台 Redis 服务器,并验证服务:


分别使用三个窗口的 redis-cli 来连接这三台服务:

客户端连接上后,执行 info replication 命令:

从图中可以发现,这三台都是 master,也就是都是主服务器。
配置主从(假设我们希望 6379 是主服务器,而 6380 和 6381 是从服务器,则需要做如下配置。 ):
分别在 6380 和 6381 客户端中执行 slaveof <ip> <port> 命令:
![]()
![]()
注意:
在 redis-cli 客户端中执行 slaveof 命令只会对当前环境生效,重启后失效。要想永久生效,需要在 redis.conf 配置文件中添加 slaveof <masterip> <masterport> 配置。
在 Redis 5.0 后,新增了 replicaof 命令,作用与 slaveof 命令效果一致。
再次执行 info replication 命令查看,可以发现已经变为了从机了:

集群测试:
在主机 6379 中添加如下数据:

在从机上查询,发现数据已同步:


其他:
如果我们在从服务器中添加数据,则会报错。因为从服务器只能读。如果从机宕机了,重启后会变为主服务器,需要重新执行 slaveof <ip> <port> 命令;如果主机宕机了,重启后一切正常。

(2)主从切换
当 master 宕机后,需要将其中一个 slave 升级为新的 master ,以保证服务的连续性。 关键命令 slaveof no one 的作用是让一个 slave 节点停止复制原来的 master ,并将自己提升为 master 。假设 6379 端口的节点为原 master,当它宕机后,在 6380 端口的 slave 上执行 slaveof no one ,6380 就会变成新的 master ,后续的 slave (如果有的话)可以继续复制这个新 master 的数据,无需额外修改(前提是架构配置支持)。
3.哨兵模式
(1)概念
哨兵也叫 sentinel,它的作用是能够在后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

(2)配置
首先使用命令 redis-cli shutdown 停止三台 Redis 服务,再重新启动三台 Redis 服务器,并实现一主双从 :
![]()
![]()
![]()

最后新增一个窗口,在 /rediscluster 目录下新建 sentinel.conf 文件,文件名称不能写错,必须叫这个名称。文件内容如下:

参数说明:
monitor:监控
redismaster:为监控对象起的服务名称
1:为至少有多个个哨兵同意迁移的数量
(3)启动
执行如下命令来启动哨兵:

(4)验证
停止主服务器:
![]()
此时,哨兵就会介入,并进行选举,然后把选举成功的从面切换为主机:

我们把 6379 重新启动,则它会变为从机:

4.复制延时
(1)概念
在 Redis 主从架构中,所有写操作先在 Master 执行,再同步到 Slave。因此从 Master 到 Slave 存在数据同步延迟,系统繁忙时延迟会加剧,且 Slave 数量越多,延迟问题越严重。
(2)选举策略
优先级优先:通过 redis.conf 中的 replica-priority 配置优先级,值越小优先级越高,优先选择优先级高的服务器。
偏移量最大优先:偏移量反映从原 Master 同步数据的完整度,优先选择偏移量最大(数据最全)的服务器。
runid 最小优先:每个 Redis 实例启动时会生成唯一的 40 位 runid,优先选择 runid 最小的从服务器。
二、集群环境
1.介绍
功能限制:不支持处理涉及多个 keys 的命令,因这类操作需在节点间移动数据,会影响性能,高负载下可能引发不可预料的错误。
可用性保障:通过分区机制,在部分节点宕机或不可达时,仍能继续处理命令。
核心优势:
自动将数据分割到不同节点,实现数据分片。
部分节点失败或不可达时,集群可继续处理命令,保障服务连续性。

2.数据分片
集群共有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模,确定归属的哈希槽
集群中每个节点负责一部分哈希槽(例如 3 个节点 ABC 可分别承担 0-5500、5501-11000、11001-16384 号槽)。
这种结构支持灵活的节点增减:新增节点时,可从现有节点中划分部分槽分配给新节点;移除节点时,需先将其负责的槽迁移到其他节点,再移除空槽节点。
哈希槽的迁移过程不影响集群服务,增减节点或调整槽分布时,集群可持续处理命令,不会出现不可用状态。
3.主从复制模型
为应对部分节点失败或通信异常,集群为每个节点配置从节点(复制品),以此在主节点故障时维持服务。
若集群无主从复制,某主节点(如负责 5501-11000 槽的节点 B)故障,会因缺少对应哈希槽导致集群不可用。
当为每个主节点(A、B、C)配置从节点(A1、B1、C1)后,若主节点 B 故障,集群会选举从节点 B1 为新主节点,保障服务持续可用。
但如果主节点 B 和其从节点 B1 同时故障,集群会因缺少对应哈希槽而不可用。
4.一致性保证
Redis 并不能保证数据的强一致性,这意味着在实际集群中在特定的条件下可能会丢失写操作。
第一个原因是因为集群是用了异步复制,写操作过程:
客户端向主节点B写入一条命令
主节点B向客户端回复命令状态
主节点将写操作复制给他得从节点 B1, B2 和 B3
主节点对命令的复制工作发生在返回命令回复之后,因为如果每次处理命令请求都需要等待复制操作完成的话,那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。注意:Redis 集群可能会在将来提供同步写的方法。
Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区,并且一个客户端与至少包括一个主节点在内的少数实例被孤立。举个例子:假设集群包含 A 、B 、C 、A1 、B1 、C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1。假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1。
Z1仍然能够向主节点B中写入,如果网络分区发生时间较短,那么集群将会继续正常运作;如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了。
注意: 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的,这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项。
5.集群搭建
(1)集群结构

准备3台服务器,每台服务器运行两个redis,一主一从(在一台服务器中配置 3 个主节点,3 个从节点,用于演示节点的增加和删除。 ):

(2)删除持久文件
把之前主从复制中所产生的所有持久文件先清除:
rm -f dump*
(3)创建配置文件
在根下创建 cluster 目录:
mkdir /cluster/log
复制 redis.conf 文件到 /cluster 目录中,然后在该目录下分别创建 6 个配置文件: redis-6379.conf、redis-6380.conf、redis-6381.conf、redis-6382.conf、redis-6383.conf、redis-6384.conf 并写入文件内容。
vim redis-6379.conf
文件内容如下:

注意:bind 0.0.0.0 表示所有IP都可以访问,当然也可以配置为:bind 192.168.72.101 127.0.0.1,表示只有这两个IP地址可以访问。
如果希望开启 aof 持久化功能,则还需添加如下配置:

保存退出后,复制这个文件 5 份,分别改为 redis-6380.conf、redis-6381.conf、redis-6382.conf、redis-6383.conf 和 redis-6384.conf。然后使用 vim 编辑器,打开文件做修改。由于所有配置文件的配置项都是一样的,唯一不同的就是端口号,因此,只需要修改端口号即可。
修改 redis-6380.conf 文件为例:
用 vim 编辑打开文件后,在命令行模式下输入 :%s/6379/6380/g (把文件中所有的 6379 改为 6380 ,也可使用 sed 's/6379/6380/g' redis-6379.conf > redis-6380.conf )命令,然后回车即可完成修改。最后保存退出即可。
(4)启动所有服务
分别启动这 6 个 Redis 服务器:

也可以编写一个脚本来进行批量启动:


查看 Redis 服务进程,验证服务是否开启:

同时,在 /cluster 目录中,也会生成这 6 个服务器的节点配置文件名:

(5)创建集群
启动好全部 Redis 服务器后,接下来就是如何把这 6 个服务器按预先规划的结构来组合成集群了。
在做接下来的操作之前,一定要先确保所有 Redis 实例都已经成功启动,并且对应实例的节点配置文件都已经生成成功。
创建集群需要执行命令来创建集群,在 Redis 6 版本中集群管理以及集成到了redis-cli中,格式如下:
redis-cli --cluster create --cluster-replicas 副本数 主机IP:端口号 从机IP:端口号
注意:这个命令需要在 Redis 安装解压编译后的 src 目录下执行才可以,因为执行这个命令时,会去找 redis-trib.rb 文件。
切换到执行命令所在目录:
cd /usr/src/redis-6.2.7/src/
根据命令格式来创建集群:

参数说明:
redis-cli --cluster:代表集群操作命令
create:代表是创建集群
--replicas 1 或者 --cluster-replicas 1:指定集群中每个master的副本个数为1,此时节点总数 ÷ (replicas + 1)得到的就是 master 的数量。因此节点列表中的前 n 个就是 master ,其它节点都是 slave 节点,随机分配到不同 master 。
对于 Redis 的分配原则是:尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。
命令执行后的结果如下:

输入 yes ,开始创建集群:

可以通过命令查看集群状态:
redis-cli -p 6379 cluster nodes
(6)测试集群
尝试连接 6379 节点(连接任何一个节点都可以),存储一个数据:

结果会报错,因为对集群操作时,要给 redis-cli 加上 -c 参数:

-h:如果在配置文件中绑定了127.0.0.1就可以不用输入-h参数,否则需要输入
-p:参数是指定端口,如果不输入则表示端口为默认端口6379,如果不是默认端口则必须提供-p参数
-c:参数表示连接到集群
最终结果如下:

(7)查看节点信息
如果要查看集群中节点信息,可以通过 cluster nodes 命令:
![]()
(8)向集群添加值
在集群环境下,由于 redis-cli 每次录入、查询键值时,Redis 都会计算出该 key 对应的插槽值,并交给对应插槽所在的节点进行处理。如果不是该客户端对应服务器的插槽 Redis 会报错,并告知应前往的 Redis 实例地址和端口。
例如,当我们在集群下使用 mset 或 mget 来添加多个值或查询多个值时:

此时,我们可以使用自定义组来完成,通过一对大括号 {} 来定义一个相同的组名,从而使 key 中的 {} 内相同内容的键值对放到同一个 slot 中。因此上面示例修改为如下方式:

注意:大括号需要加到每个 key 的后面,不能有空格,自定义组的名称可以任意。
(9)向集群查询值
命令格式:
cluster getkeysinslot <slot> <count>
# <slot> 是目标哈希槽的编号
# <count> 是需要返回的键的数量
用于获取指定哈希槽(slot)中的键(key):

6.故障转移
检验当某个主节点宕机后,从节点是否能自动切换为主节点:
假设主机 6379 宕机:

然后连接另一个节点,如 6381 再执行 cluster nodes 命令查看集群状态:

从集群状态中可以发现,6379 所在的从机 6381 已经自动切换成了 master。此时把 6379 这台服务器启动:
![]()
启动后再次查看集群状态,6379 变成 6381 的从服务器,故障成功转移。
注意:当发生某段插槽的主从都宕机后,如果 redis.conf 配置文件中 cluster-require-full-coverage 参数的值为 yes ,那么整个集群都会挂掉;如果参数的值为 no ,该段插槽数据就全都不能使用,也无法存储。
7.集群扩容
(1)创建配置文件
向现有集群中添加两个节点,这两个节点分别为一主一从。主节点的端口号为 6385,从节点的端口号为 6386,然后分别创建好 redis-6385.conf 和 redis-6386.conf 配置文件且写入内容(具体内容看前面)。
(2)启动服务
启动这两台 Redis 服务器,查看进程确认是否开启

(3)添加主节点
添加节点到集群的语法格式为:
add-node new_host:new_port existing_host:existing_port
# 添加主节点时,无需声明:--cluster-slave --cluster-master-id <arg>
add-node 命令用于添加节点到集群中,参数说明如下:
new_host:被添加节点的主机地址
new_port:被添加节点的端口号
existing_host:目前集群中已经存在的任一主机地址
existing_port:目前集群中已经存在的任一端口地址
--cluster-slave:用于添加从(Slave)节点
--cluster-master-id:指定主(Master)节点的ID(唯一标识)字符串
假设把 6385 作为主节点,6386作为从节点,先使用命令来添加主节点:


(4)添加从节点
先查看 6385 节点的 ID 值:


可见 ID 值为:1982a39401caaf04d283545f2b8cf292eb64f211,再用如下命令来添加从节点:


最后查看集群状态:

(5)分配插槽
由于集群中增加了新节点,需要对现有数据重新进行分片操作。重新分片的语法如下:
reshard host:port
--cluster-from <arg>
--cluster-to <arg>
--cluster-slots <arg>
--cluster-yes
--cluster-timeout <arg>
--cluster-pipeline <arg>
--cluster-replace
reshard 命令用于重新分片,参数说明如下:
host:集群中已经存在的任意主机地址
port:集群中已经存在的任意主机对应的端口号
--cluster-from:表示slot目前所在的节点node ID,多个ID用逗号分隔
--cluster-to:表示需要分配节点的node ID
--cluster-slot:分配的slot数量
--cluster-yes:指定迁移时的确认输入
--cluster-timeout:设置migrate命令的超时时间
--cluster-pipeline:定义cluster getkeysinslot命令一次取出的key数量,不传的话使用默认值为10
--cluster-replace:是否直接replace到目标节点
节点添加成功后,发现没有分配插槽,所以此节点还没有使用。接下来为其分配槽:


执行上面的命令后,就会从指定的 6385 节点上分配 100 个槽给 6386 节点。查看节点信息可以发现已经分配成功:

8.集群缩容
(1)收回分片
执行如下命令把 3685 节点的槽分配给 3684 节点:


查看集群状态,可以发现 6385 的 slot 已经收回:

(2)删除从节点
语法格式:
del-node host:port node_id
del-node命令用于从集群中删除节点,参数说明如下:
host:集群中已经存在的主机地址
port:集群中已经存在的主机对应的端口号
node_id:要删除的节点ID
执行下面的语句删除 3686 从节点:



从节点 3686 已经成功从集群中删除。
(3)删除主节点
执行如下命令来删除 3685 主节点:



主节点 3685 已经成功从集群中删除。
步骤总结:
先将待删除节点的 slot(插槽)收回
然后删除从节点(若先删主节点,从节点会触发故障转移)
最后删除主节点
9.优缺点
(1)优点
扩容方便:当业务数据量或访问量增长时,可通过添加新节点(主从节点)的方式快速扩展集群的存储容量和处理能力,无需对现有集群架构做大的改动。例如电商平台业务扩张,用户和商品数据激增,可通过添加 Redis 集群节点来支撑。
分摊压力:集群将数据分片存储在不同节点,读写操作可分散到多个节点执行,有效分担单个节点的负载压力,提升整体性能。比如大量的读请求可由多个从节点分担,写请求由不同主节点分担。
无中心配置相对简单:Redis 集群采用去中心化架构,没有单一的中心节点,各节点地位平等,配置和管理相对简单,避免了中心节点故障导致整个集群瘫痪的风险。
(2)缺点
不支持多键操作:由于数据分片存储在不同节点,涉及多个键的操作(如同时对多个键进行操作的命令)无法保证原子性和一致性,所以 Redis 集群不支持多键操作。例如无法直接执行 MSET 命令对分布在不同节点的多个键同时设置值。
不支持多键的 Redis 事务,Lua脚本也不被支持:事务需要保证操作的原子性,Lua 脚本也依赖于操作的原子性执行,但因数据分片,多键的 Redis 事务和 Lua 脚本无法在集群中可靠执行,所以不被支持。比如无法通过事务同时对多个分布在不同节点的键执行一系列操作,也无法执行涉及多键的 Lua 脚本。
三、缓存相关
1.缓存穿透
(1)描述
用户查询数据时,Redis 缓存未命中,直接访问数据库;若请求量过大(如恶意攻击的非正常 URL 访问),会超出数据库承载能力,导致数据库崩溃。

(2)现象
应用服务器压力变大
Redis缓存命中率降低
持续查询数据库
(3)原因
对于不存在的缓存和查询不到的数据,由于缓存是被动写入(仅在命中时才会触发写入逻辑),且出于容错考虑,若从存储层(如数据库)查不到数据就不写入缓存,这就导致该不存在的数据每次请求都要直接访问存储层查询,失去了缓存的作用。
(4)解决
对空值缓存:如果一个查询数据为空(不管数据是否存在),都对该空结果进行缓存,其过期时间会设置非常短。
设置可以访问名单:使用 bitmaps 类型定义一个可以访问名单,名单 id 作为 bitmaps 的偏移量,每次访问时与 bitmaps 中的 id 进行比较,如果访问 id 不在 bitmaps 中,则进行拦截,不允许访问。
采用布隆过滤器:布隆过滤器可以判断元素是否存在集合中,他的优点是空间效率和查询时间都比一般算法快,缺点是有一定的误识别率和删除困难。
进行实时监控:当发现 Redis 缓存命中率急速下降时,迅速排查访问对象和访问数据,将其设置为黑名单。
2.缓存击穿
(1)描述
当某个 key 对应的数据实际存在,但该 key 在 Redis 缓存中过期时,若此时有大量请求访问该数据,由于缓存已过期,这些请求会直接访问数据库,并将数据回设到缓存中。高并发下直接访问数据库,会导致数据库崩溃。

(2)现象
数据库访问压力瞬时增加
Redis 中没有出现大量 Key 过期
Redis 正常运行
数据库崩溃
(3)原因
Redis 中某个 Key 过期,且恰好有大量访问使用该 Key,导致缓存无法命中,请求直接访问数据层;这种情况最常出现在“热点”数据访问场景,最终引发数据库崩溃。
(4)解决
预先设置热门数据:在 redis 高峰访问时期,提前设置热门数据到缓存中,或适当延长缓存中 key 过期时间。
实时调整:实时监控哪些数据热门,实时调整 key 过期时间。
对于热点 key 设置永不过期。
3.缓存雪崩
(1)描述
key 中对应数据存在,在某一时刻,缓存中大量 key 过期,而此时大量高并发请求访问,会直接访问后端数据库,导致数据库崩溃。
注意:缓存击穿是指一个 key 对应缓存数据过期,缓存雪崩是大部分 key 对应缓存数据过期。
正常情况下:

缓存失效瞬间:

(2)现象
数据库压力急剧增大,最终导致数据库和 Redis 服务同时崩溃。
表现为大量缓存 key 集中过期,所有请求直接穿透缓存访问数据库,数据库因负载过高无法响应,进而引发连锁反应,使 Redis 服务也因依赖关系或资源耗尽而崩溃。
(3)原因
核心是大量缓存 key 在极短时间内集中过期。
(4)解决
构建多级缓存机制:nginx 缓存 + redis 缓存 + 其他缓存。
设置过期标志更新缓存:记录缓存数据是否过期,如果过期会触发另外一个线程去在后台更新实时 key 的缓存。
将缓存可以时间分散:如在原有缓存时间基础上增加一个随机值,这个值可以在1-5分钟随机,这样过期时间重复率就会降低,防止大量 key 同时过期。
使用锁或队列机制:使用锁或队列保证不会有大量线程一次性对数据库进行读写,从而避免大量并发请求访问数据库,该方法不适用于高并发情况。
