分布式缓存

单机的Redis存在四大问题

1

Redis持久化

Redis有两种持久化方案

  • RDB持久化
  • AOF持久化

RDB持久化

RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。

简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件恢复数据。快照文件称为RDB文件,默认是保存在当前运行目录。

RDB执行时机

RDB持久化在以下四种情况下会执行

  • 执行save命令
  • 执行bgsave命令
  • Redis停机时
  • 触发RDB条件时

1)save命令:会导致主进程执行RDB,这个过程中其它所有命令都会被阻塞。

只有在数据迁移时可能用到,其他时候尽量不用,会阻塞所有其他操作,业务就会中断

1
save

2)bgsave命令:执行后会开启独立进程完成RDB,主进程可以持续处理用户请求,不受影响。

1
bgsave

3)Redis手动关闭时:会先保存再关闭(强制关闭进程和宕机不会)

4)触发设定的RDB条件:在config文件中的配置

1
2
3
4
5
6
7
# 如果是save "" 则表示禁用RDB
# 900秒内,如果至少有1个key被修改,则执行bgsave
save 900 1
# 300秒内,如果有10个key被修改,则执行bgsave
save 300 10
# 60秒内,如果有1万个key被修改,则执行bgsave
save 60 10000

RDB原理

bgsave开始时会fork主进程得到子进程,子进程通过复制页表指向相同的共享内存来共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。

fork采用的是copy-on-write技术:

  • 当主进程执行读操作时,访问共享内存;
  • 当主进程执行写操作时,则会拷贝一份数据作为副本在副本上执行写操作

2

RDB小结

RDB方式bgsave的基本流程?

  • fork主进程得到一个子进程,共享内存空间
  • 子进程读取内存数据并写入新的RDB文件
  • 用新RDB文件替换旧的RDB文件

RDB的缺点?

  • RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
  • fork子进程、压缩、写出RDB文件都比较耗时

AOF持久化

AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件

3

AOF配置

AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:

1
2
3
4
# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"

AOF的命令记录的频率也可以通过redis.conf文件来配:

1
2
3
4
5
6
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案(常用)
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no

三种策略对比

4

AOF文件重写

因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。

通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,Redis 会 fork 一个子进程来执行重写操作,在子进程重写 AOF 文件的过程中,主进程会继续接收和处理写操作,Redis 会维护一个重写缓冲区(rewrite buffer),将这些新的写操作也记录下来。

重写可以显著减少 AOF 文件的大小,因为新文件只包含当前数据库状态所需的最小操作集,而不是所有历史操作日志。这不仅降低了磁盘空间占用,还加快了 Redis 重启时的恢复速度

Redis主从复制

单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。

5

主从数据同步原理

主从之间的数据是自动同步的,无需额外操作

全量同步

主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝slave节点

如何确定master和slave的数据是否一致

  • Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
  • offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。

slave原本也是一个master,有自己的replidoffset:

当第一次变成slave,与master建立连接时,发送的是自己的replidoffset

master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。

master将自己的replid和offset都发送给这个slaveslave存储后保证slavereplidmaster一致。

因此,master判断一个节点是否是第一次同步的依据,就是看replid是否一致

7

完整流程

  • slave节点请求增量同步
  • master节点判断replid,发现不一致,拒绝增量同步
  • master将完整内存数据生成RDB,发送RDBslave
  • slave清空本地数据,加载master的RDB
  • master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
  • slave执行接收到的命令,保持与master之间的同步

增量同步

只更新slave与master存在差异的部分数据

全量同步需要先做RDB,然后将RDB文件通过网络传输给slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slavemaster都是做增量同步

8

repl_backlog原理

master怎么知道slave与自己的数据差多少?靠全量同步时的repl_baklog文件记录。

这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。

repl_baklog中会记录Redis处理过的命令日志及offset包括master当前的offset,和slave已经拷贝到的offset

9

slavemaster的offset之间的差异,就是salve需要增量拷贝的数据了。

如果slave落后master一个环,就只能做全量同步,因为repl_baklog已经有部分数据是被覆盖了

14

主从同步优化

主从同步可以保证主从数据的一致性,非常重要。

可以从以下几个方面来优化Redis主从就集群:

  • 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
  • Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
  • 适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
  • 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力

主从从架构图:

15

小结

全量同步和增量同步区别?

  • 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave
  • 增量同步:slave提交自己的offset到mastermaster获取repl_baklog中从offset之后的数据传给slave

什么时候执行全量同步?

  • slave节点第一次连接master节点时
  • slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

什么时候执行增量同步?

  • slave节点断开又恢复,并且在repl_baklog中能找到offset

Redis哨兵

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。

哨兵原理

哨兵自动监测集群的情况,无需额外操作,即可将masterslave切换

集群结构和作用

哨兵的结构如图:

16

哨兵的作用如下:

  • 监控:Sentinel 会不断检查您的master和slave是否按预期工作
  • 自动故障恢复:如果master故障,Sentinel会**将一个slave提升为master**。当故障实例恢复后也以新的master为主
  • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端

集群监控原理

Sentinel基于心跳机制监测服务状态,每隔2秒向集群的每个实例发送ping命令:

•主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线

•客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

17

集群故障恢复原理

一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:

  • 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
  • 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
  • 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
  • 最后是判断slave节点的运行id大小,越小优先级越高

选出新的master后,实现切换的流程

  • sentinel给备选的slave节点发送slaveof no one命令,让该节点成为master
  • sentinel给所有其它slave发送slaveof new_master_host new_master_port命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
  • 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
18

小结

Sentinel的三个作用是什么?

  • 监控
  • 故障转移
  • 通知

Sentinel如何判断一个redis实例是否健康?

  • 每隔2秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线
  • 如果大多数sentinel都认为实例主观下线,则判定服务下线

故障转移步骤有哪些?

  • 首先选定一个slave作为新的master,执行slaveof no one
  • 然后让所有节点都执行slaveof 新master
  • 修改故障节点配置,添加slaveof 新master

在RedisTemplate中配置哨兵

在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。

引入依赖

在项目的pom文件中引入依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置Redis地址

然后在配置文件application.yml中指定redis的sentinel相关信息:

1
2
3
4
5
6
7
8
spring:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.150.101:27001
- 192.168.150.101:27002
- 192.168.150.101:27003

配置读写分离

在项目的启动类中,添加一个新的bean:

1
2
3
4
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}

这个bean中配置的就是读写策略,包括四种:

  • MASTER:从master读取
  • MASTER_PREFERRED:优先从master节点读取,master不可用才读取slave(replica)
  • REPLICA:****从slave(replica)节点读取
  • REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master(常用)

Redis分片集群

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:

  • 海量数据存储问题

  • 高并发写的问题

使用分片集群可以解决上述问题

19

搭建分片集群

分片集群特征:

  • 集群中有多个master,每个master保存不同数据

  • 每个master都可以有多个slave节点

  • master之间通过ping监测彼此健康状态

  • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

散列插槽

插槽原理

Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:

20

数据key不是与节点绑定,而是与插槽绑定。

redis会根据key的有效部分计算插槽值,分两种情况:

  • key中包含”{}”,且“{}”中至少包含1个字符,“{}”中的部分是有效部分
  • key中不包含“{}”,整个key都是有效部分

在执行命令前先利用CRC16算法得到一个hash值,然后对16384取余,得到slot值后会重定向到指定的节点

21

小结

Redis如何判断某个key应该在哪个实例?

  • 将16384个插槽分配到不同的实例
  • 根据key的有效部分计算哈希值,对16384取余
  • 余数作为插槽,寻找插槽所在实例即可

如何将同一类数据固定的保存在同一个Redis实例?

  • 这一类数据使用相同的有效部分,例如key都以{typeId}为前缀

集群伸缩

redis-cli –cluster提供了很多操作集群的命令,可以通过下面方式查看:

22

23

添加新节点到redis

执行命令:

1
redis-cli --cluster add-node  new_host:new_port existing_host:existing_port

如果不加可选参数就是把新节点直接当成master,新加入的节点是没有分配插槽的,slot为0

转移插槽

转移插槽命令格式如下:

24

故障转移

自动故障转移

当集群中有一个master宕机时

1
redis-cli -p 7002 shutdown

1)首先是该实例与其它实例失去连接

2)然后是疑似宕机:

25

3)最后是确定下线,自动提升一个slave为新的master:

26

4)当7002再次启动,就会变为一个slave节点了:

27

手动故障转移

利用cluster failover命令可以手动让集群中的某个master宕机

此时执行cluster failover命令的这个slave节点会成为master,实现无感知的数据迁移。

28

这种failover命令可以指定三种模式:

  • 缺省:默认的流程,如图1~6歩(常用)
  • force:省略了对offset的一致性校验
  • takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见

RedisTemplate访问分片集群

RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:

1)引入redis的starter依赖

2)配置分片集群地址

3)配置读写分离

与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:

1
2
3
4
5
6
7
8
9
10
spring:
redis:
cluster:
nodes:
- 192.168.150.101:7001
- 192.168.150.101:7002
- 192.168.150.101:7003
- 192.168.150.101:8001
- 192.168.150.101:8002
- 192.168.150.101:8003

总结

Redis持久化

Redis提供了多种持久化机制,确保数据在内存中的同时能够安全地保存到磁盘。

以下是Redis主要的持久化方式:

  1. 快照(Snapshotting,RDB)
    • Redis会在特定间隔时间内创建数据集的快照并将其保存到磁盘上。
    • 生成的文件称为RDB文件,包含某一时刻的数据快照。
    • 优点:对Redis的性能影响较小,因为在保存RDB文件时,Redis可以继续处理客户端请求。
    • 缺点:如果Redis在快照之间宕机,会丢失自上次快照以来的所有数据。
  2. 只追加文件(Append-Only File,AOF)
    • 每次写操作都会追加记录到AOF文件中。
    • Redis会在后台定期对AOF文件进行压缩,以减少文件大小并提高恢复速度。
    • 优点:AOF提供了更高的数据持久性,因为每次写操作都会被记录下来。
    • 缺点:由于每个写操作都要写入磁盘,AOF可能会影响Redis的写性能。
  3. 混合持久化(Hybrid Persistence)
    • Redis 6.0引入了一种新的持久化方式,结合了RDB和AOF的优点。
    • 在后台生成RDB快照的同时,AOF记录正在进行的写操作。
    • 优点:恢复速度快,数据持久性高。
    • 缺点:实现较为复杂,磁盘占用可能会增加。
  4. 内存中的复制(In-Memory Replication)
    • 虽然不是传统的持久化方式,但通过主从复制,可以实现数据的高可用性和持久性。
    • 主节点将数据复制到从节点,从节点作为数据的备份。
    • 优点:高可用性,快速故障恢复。
    • 缺点:需要额外的内存和服务器资源。

在实际应用中,选择哪种持久化方式取决于具体的业务需求和系统架构设计。一般情况下,可以结合使用RDB和AOF,以在性能和数据持久性之间取得平衡。

Redis集群

Redis集群是一种分布式实现,允许在多个Redis节点上分布数据和负载,以提高可用性、扩展性和性能。Redis集群有以下几种主要类型:

  1. 主从复制(Master-Slave Replication)
    • 一个主节点负责写操作,并将数据同步到多个从节点。
    • 从节点可以处理读操作,这样可以分担读负载。
    • 如果主节点故障,从节点可以手动或自动提升为主节点。
  2. 哨兵模式(Sentinel Mode)
    • 由Sentinel进程监控Redis主从集群的健康状态。
    • 自动故障转移(failover):如果主节点故障,Sentinel会自动将从节点提升为主节点。
    • 提供高可用性(HA),但不提供数据分片(sharding)。
  3. 分片集群:Redis Cluster
    • 原生的Redis分布式方案,支持数据分片和高可用性。
    • 数据分布在多个节点上,每个节点负责一部分数据。
    • 通过hash槽(hash slot)进行数据分片,总共有16384个hash槽,节点之间通过hash槽分配数据。
    • 支持自动故障转移和重新分片。

每种类型的Redis集群都有其适用场景和优缺点,选择哪种集群模式需要根据具体需求和应用场景来决定。