Redis 持久化
RDB 持久化
RDB 全称 Redis Database Backup file(Redis 数据备份文件),也被叫做 Redis 数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当 Redis 实例故障重启后,从磁盘读取快照文件,恢复数据。
快照文件称为 RDB 文件,默认是保存在当前运行目录。
Redis 执行 save 是在主线程上,会造成线程堵塞。
Redis 停机时会执行一次 RDB。
Redis 内部有触发 RDB 的机制,可以在redis.conf文件中找到。
RDB 的其它配置也可以在redis.conf文件中设置:
bgsave 开始时会 fork 主进程得到子进程,子进程共享主进程的内存数据。完成 fork 后读取内存数据并写入 RDB 文件。
fork 采用的是copy-on-write技术:
- 当主进程执行读操作时,访问共享内存;
- 当主进程执行写操作时,则会拷贝一份数据,执行写操作。
RDB 方式bgsave的基本流程:
- fork 主进程得到一个子进程,共享内存空间。
- 子进程读取内存数据并写入新的RDB文件。
- 用新 RDB 文件替换旧的 RDB 文件。
RDB 的缺点:
- RDB 执行间隔时间长,两次 RDB 之间写入数据有丢失的风险。
- fork 子进程、压缩、写出 RDB 文件都比较耗时。
AOF 持久化
AOF 全称为 Append Only File(追加文件)。Redis 处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
AOF 默认是关闭的,需要修改redis.conf配置文件来开启 AOF:
# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"
AOF 的命令记录的频率也可以通过redis.conf文件来配:
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
因为是记录命令,AOF 文件会比 RDB 文件大的多。而且 AOF 会记录对同一个 key 的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让 AOF 文件执行重写功能,用最少的命令达到相同效果。
Redis 也会在触发阈值时自动去重写 AOF 文件。阈值也可以在redis.conf中配置:
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
RDB 和 AOF 各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。
Redis 主从
单节点 Redis 的并发能力是有上限的,要进一步提高 Redis 的并发能力,就需要搭建主从集群,实现读写分离。
搭建主从架构
有临时和永久两种模式:
-
修改配置文件(永久生效)
- 在
redis.conf中添加一行配置:slaveof <masterip> <masterport>
- 在
-
使用 redis-cli 客户端连接到 redis 服务,执行 slaveof 命令(重启后失效)
slaveof <masterip> <masterport>
主从数据同步原理
全量同步
主从第一次同步是全量同步:
master 如何判断 slave 是不是第一次来同步数据?这里会用到两个很重要的概念:
- Replication Id:简称 replid,是数据集的标记,id 一致则说明是同一数据集。每一个 master 都有唯一的 replid,slave 则会继承 master 节点的 replid。
- offset:偏移量,随着记录在 repl_baklog 中的数据增多而逐渐增大。slave 完成同步时也会记录当前同步的 offset。如果 slave 的 offset 小于 master 的 offset,说明 slave 数据落后于 master,需要更新。
因此 slave 做数据同步,必须向 master 声明自己的 replication id 和 offset,master 才可以判断到底需要同步哪些数据。
简述全量同步的流程:
- slave 节点请求增量同步。
- master 节点判断 replid,发现不一致,拒绝增量同步。
- master 将完整内存数据生成 RDB,发送 RDB 到 slave。
- slave 清空本地数据,加载 master的 RDB。
- master 将 RDB 期间的命令记录在 repl_baklog,并持续将 log 中的命令发送给 slave。
- slave 执行接收到的命令,保持与 master 之间的同步。
增量同步
主从第一次同步是全量同步,但如果 slave 重启后同步,则执行增量同步。
repl_baklog 大小有上限,写满后会覆盖最早的数据。如果 slave 断开时间过久,导致尚未备份的数据被覆盖,则无法基于 log 做增量同步,只能再次全量同步。
可以从以下几个方面来优化 Redis 主从就集群:
- 在 master 中配置
repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘 IO。 - Redis 单节点上的内存占用不要太大,减少 RDB 导致的过多磁盘 IO
- 适当提高 repl_baklog 的大小,发现 slave 宕机时尽快实现故障恢复,尽可能避免全量同步
- 限制一个 master 上的 slave 节点数量,如果实在是太多 slave,则可以采用主-从-从链式结构,减少 master 压力
Redis 哨兵
Redis 提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用:
监控:Sentinel 会不断检查您的 master 和 slave 是否按预期工作。
自动故障恢复:如果 master 故障,Sentinel 会将一个 slave 提升为 master。当故障实例恢复后也以新的 master 为主。
通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给 Redis 的客户端。
哨兵的作用和原理
服务状态监控
Sentinel 基于心跳机制监测服务状态,每隔 1 秒向集群的每个实例发送 ping 命令。
- 主观下线:如果某 sentinel 节点发现某实例未在规定时间响应,则认为该实例主观下线。
- 客观下线:若超过指定数量(quorum)的 sentinel 都认为该实例主观下线,则该实例客观下线。
quorum 值最好超过 Sentinel 实例数量的一半。
选举新的 master
一旦发现 master 故障,sentinel 需要在 salve 中选择一个作为新的 master。
- 首先会判断 slave 节点与 master 节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该 slave 节点。
- 然后判断 slave 节点的 slave-priority 值,越小优先级越高,如果是 0 则永不参与选举。
- 如果 slave-prority 一样,则判断 slave 节点的 offset 值,越大说明数据越新,优先级越高。
- 最后是判断 slave 节点的运行 id 大小,越小优先级越高。
如何实现故障转移
当选中了其中一个 slave 为新的 master 后(例如:slave1),故障的转移的步骤如下:
- sentinel 给备选的 slave1 节点发送
slaveof no one命令,让该节点成为 master。 - sentinel 给所有其它 slave 发送
slaveof 192.168.150.101 7002命令,让这些 slave 成为新 master 的从节点,开始从新的 master 上同步数据。 - 最后,sentinel 将故障节点标记为 slave,当故障节点恢复后会自动成为新的 master 的 slave 节点。
搭建哨兵集群
- 创建一个
sentinel.conf文件
port 27001
sentinel announce-ip 192.168.150.101
sentinel monitor mymaster 192.168.150.101 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/tmp/s1"
- 启动命令
redis-sentinel sentinel.conf
RedisTemplate 哨兵模式
- 在 pom 文件中引入 redis 的 starter 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 然后在配置文件 application.yml 中指定 sentinel 相关信息:
spring:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.150.101:27001
- 192.168.150.101:27002
- 192.168.150.101:27003
配置读写分离
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
这里的 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 中包含"{}",且“{}”中至少包含 1 个字符,“{}”中的部分是有效部分。
- key 中不包含“{}”,整个 key 都是有效部分。
例如:key 是 num,那么就根据 num 计算,如果是 {summer} num,则根据 summer 计算。计算方式是利用 CRC16 算法得到一个 hash 值,然后对16384取余,得到的结果就是slot值。
添加一个节点到集群
redis-cli --cluster提供了很多操作集群的命令。
添加节点的命令:
数据迁移
利用cluster failover命令可以手动让集群中的某个 master 宕机,切换到执行cluster failover命令的这个 slave 节点,实现无感知的数据迁移。其流程如下:
手动的 Failover 支持三种不同模式:
- 缺省:默认的流程,如图1~6步。
- force:省略了对 offset 的一致性校验。
- takeover:直接执行第 5 步,忽略数据一致性、忽略 master 状态和其它 master 的意见。
RedisTemplate 访问分片集群
RedisTemplate 底层同样基于lettuce实现了分片集群的支持,而使用步骤与哨兵模式基本一致:
- 引入 redis 的 starter 依赖。
- 配置分片集群地址。
- 配置读写分离。
与哨兵模式相比,其中只有分片集群的配置方式略有差异
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