从零开始学习Redis(四):分布式缓存(Redis集群)
之前做的redis缓存(点评项目)都是单节点部署,但单点redis存在一些问题
单点redis的问题:
数据丢失问题:Redis是内存存储,服务重启可能会丢失数据
并发能力问题:单节点Redis并发能力虽不错,但无法应对高并发场景
故障恢复问题:如果Redis宕机,则服务不可用,需要一种自动的故障恢复手段
存储能力问题:Redis基于内存,单节点能存储的数据难以满足海量需求
而Redis集群可以对以上问题给出解决方案:
数据丢失问题:实现Redis数据持久化,将数据持久化到磁盘
并发能力问题:搭建主从集群,实现读写分离
故障恢复问题:利用Redis哨兵,实现健康检测和自动恢复
存储能力问题:搭建分片集群,利用插槽机制实现动态扩容
以上的解决方案也是要学习的Redis的四个点:Redis持久化,Redis主从,Redis哨兵,Redis分片集群。
Redis持久化(解决数据丢失问题)
RDB持久化
RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
快照文件称为RDB文件,默认是保存在当前运行目录,默认开启
save:redis主进程执行RDB会阻塞所有命令,适合在服务停机之前使用
bgsave:开启子进程异步执行RDB,避免主进程受到影响
save "" :禁用RDB
RDB方式bgsave的基本流程:
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据,完成fork后子进程读取内存数据写入RDB文件,用新RDB文件替换旧的RDB文件
fork采用的是copy-on-write技术:
· 当主进程执行读操作时,访问共享内存;
· 当主进程执行写操作时,则会拷贝一份数据,执行写操作。
RDB执行时机:
save:默认服务停止时
bgsave:根据conf文件里的配置,例如save 60 1000代表60秒内至少执行1000次修改则触发RDB
RDB缺点:
RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
fork子进程,压缩,写出RDB文件都比较耗时
AOF持久化(解决数据安全问题)
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
AOF默认关闭,要修改conf配置文件来开启

conf配置AOF命令记录频率:
- appendfsync always:每执行一次写命令,立即记录到AOF,性能最差
- appendfsync everysec:写命令执行完先放入AOF缓冲区,每隔一秒将缓冲区数据写到AOF,是默认方案,性能适中,最多丢失一秒数据
- appendfsync no:写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘,性能最好但安全性最差
cat appendonly.aof:查看AOF文件
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
Redis会在触发阈值时自动去重写AOF文件,阈值在conf文件中配置:

| RDB | AOF | |
| 持久化方式 | 定时对整个内存做快照 | 记录每一次执行的命令 |
| 数据完整性 | 不完整,两次备份之间会丢失 | 相对完整,取决于刷盘策略 |
| 文件大小 | 会有压缩,文件体积小 | 记录命令,文件体积很大 |
| 宕机恢复速度 | 很快 | 慢 |
| 数据恢复优先级 | 低,因为数据完整性不如AOF | 高,因为数据完整性更高 |
| 系统资源占用 | 高,大量CPU和内存消耗 | 低,主要是磁盘IO资源 但AOF重写时会占用大量CPU和内存资源 |
| 使用场景 | 可以容忍数分钟的数据丢失,追求更快的启动速度 | 对数据安全性要求较高常见 |
Redis主从(解决并发能力问题)
搭建主从架构
单节点的Redis并发能力是有上限的,要进一步提高Redis的并发能力,就要搭建主从集群,实现读写分离。一个主节点master,多个从节点slave/replica
为什么使用主从集群?
因为Redis大多是读多写少的场景,读的压力较大,使用主节点执行写操作,多个从节点执行读操作,通过拆分读写请求大大提升了Redis读的并发能力。主节点把数据同步给每一个从节点保证读的结果相同。
如何将B作为A的slave(从)节点?
在B节点执行命令:slaveof A的IP A的port
数据同步原理
主从第一次同步是全量同步
slave做数据同步,必须向master声明自己的replication id 和offset
Replication Id:简称replid,数据集的标记,id一致说明说同一数据集。每个master都有唯一的replid,slave会继承master节点的replid
offset:偏移量,随着记录在repl_baklog缓冲区的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
全量同步流程:
- slave节点请求增量同步,
- master判断replid,发现不一致,拒绝增量同步,做全量同步
- master将完整内存数据生成RDB,发送RDB到slave
- slave清空本地数据,加载master的RDB
- master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave

如果slave重启后同步,则执行增量同步

注:repl_baklog大小有上限,写满后会覆盖最早的数据。如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步。
优化Redis主从集群:
· 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘I0。
· Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘I0
· 适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
· 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
全量同步和增量同步区别
全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave
什么时候执行全量同步?
slave节点第一次连接master节点时
· slave节点断开时间太久,repl_baklog中的offset已经被覆盖时
什么时候执行增量同步?
· slave节点断开又恢复,并且在repl_baklog中能找到offset时
slave节点宕机恢复后可找master节点同步数据,那master节点宕机怎么办?
当master节点宕机的一瞬间,redis会选取一个slave成为新的master,当之前的master节点恢复后就将其变为slave节点,实现自动故障恢复,而这一动作是由Redis的哨兵机制来实现的。
Redis哨兵(解决故障恢复问题)
哨兵的作用和工作原理:
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:
哨兵也是一个集群。
- · 监控:Sentinel 会不断检查您的master和slave是否按预期工作
- · 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
- · 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
1 那么哨兵如何监控检测服务状态呢?
Sentinel基于心跳机制检测服务状态,每隔一秒向集群的每个实例发送ping命令:
如果某Sentinel节点发现某实例未在规定时间内响应,则认为是主观下线
若超过指定数量(quorum)的Sentinel都认为该实例主观下线,则该实例客观下线。其实就是类似于投票,quorum值最好超过Sentinel实例数量的一半。
2 发现master故障后,Sentinel要在slave中选一个作为新的master,选择依据:
如果slave节点与master节点断开时间超过指定值(down-after-milliseconds*10)则会排除该slave节点,然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举。如果slave-priority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高。offset一样,slave节点的运行id越小优先级越高。
3 选中了slave后,进行故障转移:
sentinel给被选中的slave发送slaveof no one命令,让该节点成为master,sentinel给其他slave发送slaveof 新master的IP 新master的port,让这些slave成为新master的从节点,开始从新master同步数据,再将故障节点标记为slave,修改其配置,添加slaveof 新master的IP 新master的port,故障节点恢复后自动成为master的slave节点
RedisTemplate的哨兵模式
当redis的主从集群节点因自动故障转移而发生变化时,RedisTemplate底层利用lettuce实现了节点的感知和自动切换,及时更新连接信息。
1 在pom文件引入redis的starter依赖:

2 在配置文件中指定sentinel相关信息:

3 在启动类里配置主从读写分离

ReadFrom是配置Redis的读取策略,是一个枚举,包括:
- MASTER:从主节点读取
- MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
- REPLICA:从slave(replica)节点读取
- REPLICA_PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master
Redis分片集群(解决海量存储和高并发写)
分片集群结构
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:海量数据存储问题和高并发写的问题
使用分片集群可以解决上述问题,分片集群特征:
- · 集群中有多个master,每个master保存不同数据
- · 每个master都可以有多个slave节点
- · master之间通过ping监测彼此健康状态
- · 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
散列插槽
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到。

数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
- · key中包含”0)",且“0”中至少包含1个字符,“0)”中的部分是有效部分
- · key中不包含“0)”,整个key都是有效部分
例如:key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。
为什么要将数据与插槽绑定?
redis的节点可能被删除或宕机,若数据与节点绑定,数据就有丢失风险。但如果是将数据与插槽绑定,节点宕机时,我们可以将该节点的插槽转移到活着的节点,数据跟着插槽走,根据插槽就可以找到数据。
Redis如何判断某个key应该在哪个实例?
- · 将16384个插槽分配到不同的实例
- · 根据key的有效部分计算哈希值,对16384取余
- · 余数作为插槽,寻找插槽所在实例即可
如果要将同一类数据固定的保存在同一个Redis实例,我们可以让这一类数据使用相同的有效部分,例如key都以{typeld}为前缀。
集群伸缩
集群(Redis Cluster)的伸缩是指通过添加节点(扩容)或移除节点(缩容)来动态调整集群的处理能力,核心是通过哈希槽(hash slot)的迁移实现数据在节点间的重新分布。
添加节点(扩容):
1. 准备新节点
新节点需配置为集群模式(cluster-enabled yes),并启动。
port 6380 # 新节点端口
cluster-enabled yes
cluster-config-file nodes-6380.conf # 集群配置文件(自动生成)
cluster-node-timeout 15000启动新节点:redis-server redis.conf2. 将新节点加入集群
redis-cli --cluster add-node 新节点地址 现有集群节点地址redis-cli --cluster add-node 127.0.0.1:6380 127.0.0.1:63793. 迁移哈希槽到新节点
语法:redis-cli --cluster reshard 集群任意节点地址redis-cli --cluster reshard 127.0.0.1:6379执行后需交互配置:
- 输入需迁移的槽位数量(例如迁移 1000 个槽);
- 输入新节点的
node-id(作为目标节点,接收槽位); - 输入源节点的
node-id(指定从哪些节点迁移,输入all表示从所有主节点均衡迁移); - 输入
yes确认迁移。
移除节点(缩容)
缩容的目标是将待移除节点的所有哈希槽迁移到其他节点,再将节点从集群中删除。
1. 迁移待移除节点的所有哈希槽
redis-cli --cluster reshard 127.0.0.1:6379交互配置:
- 输入待迁移的槽位数量(即待移除节点当前负责的所有槽位,可通过
cluster slots查看); - 输入目标节点的
node-id(接收槽位的其他主节点); - 输入待移除节点的
node-id(作为源节点); - 输入
yes确认迁移。
验证:通过cluster countkeysinslot 槽位号检查待移除节点的所有槽位是否已迁移完成(键数量为 0)。
2. 移除节点
# 语法:redis-cli --cluster del-node 集群任意节点地址 待移除节点的node-id
redis-cli --cluster del-node 127.0.0.1:6379 待移除节点的node-id故障转移
我们已知当集群中有一个master宕机后,redis会自动提升一个slave为新的master,但有时候因为机器老旧而需要更换新的节点,我们就会使用手动故障转移,实现无感知的机器的升级,也叫数据迁移。
利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。
数据迁移步骤:

手动的Failover支持三种不同模式:
- · 缺省:默认的流程,如图1~6歩(建议使用)
- · force:省略了对offset的一致性校验
- · takeover:直接执行第5步,忽略数据一致性、忽略master状态和其它master的意见
RedisTemplate访问分片集群
RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:
1. 引入redis的starter依赖(略)
2. 配置分片集群地址
3. 配置读写分离(略)
与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:

