前言
在生产环境中,为了实现Redis的高可用性,可以采用持久化、主从复制、哨兵模式和 Cluster集群的方法确保数据的持久性和可靠性。这里首先介绍一下使用持久化实现服务器的高可用。主从复制、哨兵模式和集群介绍请参考:Redis主从复制、哨兵模式和集群-CSDN博客
目录
一、Redis 高可用方法
1. 持久化
最简单的高可用方法,主要作用是数据备份,即定期将数据保存到硬盘。
2. 主从复制
是高可用 Redis 的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
3. 哨兵
主从复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。
4. Cluster 集群
通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。
二、Redis 持久化
1. 概述
Redis 是内存数据库,数据都是存储在内存中,为了避免服务器断电等原因导致Redis进程异常退出后数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。
2. 实现方式
① RDB 持久化:将 Reids在内存中的数据库记录定时保存到磁盘上,类似于快照。
② AOF 持久化:将 Reids 的操作日志以追加的方式写入文件,类似于MySQL的binlog。实时性更好,进程意外退出时丢失的数据更少。
3. RDB 持久化
RDB 持久化是指在达到触发条件后将内存中当前进程中的数据用二进制压缩存储,生成dump.rdb 文件;当Redis重新启动时,可以读取快照文件恢复数据。
3.1 触发条件
(1)手动触发
① save 命令:会阻塞 Redis 服务器进程,直到 RDB 文件创建完。
② bgsave 命令:会创建一个子进程,由子进程来负责创建 RDB 文件,父进程(即 Redis 主进程)则继续处理请求,注意仍需待子进程处理完毕通知父进程才可以继续处理下一个请求。
(2)自动触发
配置文件中通过3个“save m n”满足任意一个时,都会引起 bgsave 的调用
[root@localhost ~]# vim /etc/redis/6379.conf
格式:
save m n # 指定m秒内发生n次变化,会触发bgsave;这里的save是bgsave的格式写法
219 save 900 1 # 指定900秒内发生1次变化,会触发bgsave
220 save 300 10
221 save 60 10000
相关配置:
254 dbfilename dump.rdb # 指定RDB文件名
264 dir /var/lib/redis/6379 # 指定RDB文件和AOF文件所在目录
242 rdbcompression yes # 是否开启RDB文件压缩
其他触发机制:
① 在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行 bgsave 命令,并将 rdb 文件发送给从节点。
② 手动关闭 redis,如执行:shutdown、/etc/initd.d/redis.6379 stop/restart 命令时,会自动执行 rdb 持久化。注意 kill 通知不会触发。
3.2 执行流程
① 当 rdb 持久化被触发后,正常命令、操作等信息会被 redis 父进程接收
② 父进程接收到"快照"和"持久化"指令,会判断是否存在子进程,如果存在,则执行数据处理返回信息;如果不存在,则 fork 创建子进程
③ fork 创建子进程过程中会阻塞父进程,此时父进程是不能接受任何的请求操作
④ 子进程创建完毕后会返回信息给父进程,此时父进程将不再阻塞可以接收其他命令,不过需要等待子进程返回上一条完毕信息给父进程,才可以处理下一条命令信息。即可以接收,但需要排队处理
⑤ 子进程的作用就是用来生成 *.rdb文件(覆盖原文件,或者压缩替换原来的文件)
⑥ 当 rdb 文件生成完毕后信号通知父进程,父进程会接收其他命令、更新统计信息
3.3 启动时加载
- 当服务器启动时自动执行 rdb 文件的载入工作
- AOF 的优先级更高,因此当 AOF 开启时,Redis 会优先载入 AOF 文件来恢复数据
- 服务器载入 rdb 文件期间处于阻塞状态,直到载入完成为止
- Redis 载入rdb 文件时,会对 rdb 文件进行校验,如果文件损坏,则记录日志,启动失败
4. AOF 持久化
将Redis执行的每次写、删除命令记录到单独的日志文件中,查询操作不会记录; 当Redis重启时再次执行AOF文件中的命令来恢复数据。相当于增量备份,与RDB相比AOF的实时性更好。
4.1 开启 AOF
在配置文件中默认开启RDB,AOF 需要手动开启。
[root@localhost ~]# vim /etc/redis/6379.conf
700 appendonly yes # 开启AOF,默认no
704 appendfilename "appendonly.aof" # 指定AOF文件名称
796 aof-load-truncated yes
# 是否忽略最后一条可能存在问题的指令
# 需要注意的是:当最后一条存在问题,忽略这条指令;当最后一条没有问题,保留这条指令
4.2 执行流程
4.2.1 命令追加(append)
将Redis的写命令追加到缓冲区 aof_buf,而不是直接写入文件,避免频繁写入,导致硬盘 IO 成为Redis 负载的瓶颈。在 AOF 文件中,除了用于指定数据库的 select 命令是由 Redis 添加,其他均来自客户端。
4.2.2 文件写入(write)和文件同步(sync)
根据不同的同步策略将 aof_buf 中的内容同步到硬盘;当用户调用 write 函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但也带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失;因此系统同时提供了fsync、fdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。Redis提供了多种AOF缓存区的同步文件策略:
- appendfsync always:一直触发 AOF 的持久化,应用有强制一致性要求的场景
- appendfsync no:不进行持久化
- appendfsync everysecond:每秒钟触发一次持久化,建议使用,类似于负载均衡的场景
4.2.3 文件重写(rewrite)
定期重写 AOF 文件,达到压缩的目的、减小 AOF 文件的体积。需要注意的是,AOF 重写是把 Redis 进程内的数据转化为写命令,同步到新的 AOF 文件;不会对旧的 AOF 文件进行任何读取、写入操作。重写一般针对以下情况:
- 过期的数据不再写入文件
- 无效的命令不再写入文件
- 多条命令可以整合的
4.3 文件重写触发
(1)手动触发
直接调用 bgrewriteaof 命令,该命令的执行与 bgsave 有些类似:都是 fork 子进程进行具体的工作,且都只有在 fork 时阻塞。
(2)自动触发
修改 /etc/redis/6379.conf 相关配置,只有当auto-aof-rewrite-min-size和auto-aof-rewrite-percentage两个选项同时满足时,才会自动触发AOF重写,即bgrewriteaof操作。
[root@localhost ~]# vim /etc/redis/6379.conf
771 auto-aof-rewrite-percentage 100 # 当前AOF文件大小(即aof_current_size)是上次日志重写时AOF文件大小(aof_base_size)两倍时,发生BGREWRITEAOF操作
772 auto-aof-rewrite-min-size 64mb # 当前AOF文件执行BGREWRITEAOF命令的最小值,避免刚开始启动Reids时由于文件尺寸较小导致频繁的BGREWRITEAOF
4.4 重写流程
① 父进程会先判断有没有其他的子进程在运行,如果有 bgrewriteaof 的直接返回,如果是 bgsave 等他执行完再执行
② 如果没有其他的子进程,父进程就会 fork 子进程,此时父进程是阻塞的,子进程会创建好后信息通知父进程,此时父进程可以继续接收其他命令
③ redis 会先将写入的命令暂存在缓冲区里,根据 sync 策略同步的硬盘里
④ 父进程在 fork 子进程后响应的命令会同时记录到 aof_rewrite_buf 当中
⑤ fork 后的数据也会被写入到旧的 aof 文件中
⑥ 子进程根据合并规则重写生成新的 aof 文件
⑦ 子进程完成新的 aof 文件生成后向父进程发送信号,父进程更新统计数据
⑧ fork 后的数据也会被写入到新的 aof 文件
⑨ 新的aof文件 替换旧的 aof 文件
4.5 启动时加载
- 当AOF开启时,Redis 启动时会优先载入 AOF 文件来恢复数据;只有当 AOF 关闭时,才会载入RDB 文件恢复数据
- 当AOF开启,但AOF文件不存在时,即使RDB文件存在也不会加载
- Redis载入AOF文件时,会对AOF文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败
- 如果是 AOF 文件结尾不完整(机器突然宕机等容易导致文件尾部不完整),且 aof-load-truncated 参数开启,则日志中会输出警告,Redis 忽略掉 AOF 文件的尾部,启动成功
5. RDB 和 AOF 的优缺点
① RDB 持久化
优点:
- RDB 文件紧凑,体积小,网络传输快,适合全量复制,会压缩数据
- 恢复速度比 AOF 快很多
- 备份整个结果,主从复制优先使用 rdb,同步性能高
缺点:
- 数据快照的持久化方式决定了必然做不到实时持久化
- RDB 文件需要满足特定格式,兼容性差
- bgsave 在进行 fork 操作时 Redis 主进程会阻塞
- 子进程向硬盘写数据也会带来 IO 压力
② AOF 持久化
优点:
- 支持秒级持久化、兼容性好
- 有重写功能,可以压缩释放空间
缺点:
- 文件大、恢复速度慢、对性能影响大
- 对于 AOF 持久化,向硬盘写数据的频率大大提高(everysec策略下为秒级),IO 压力更大,甚至可能造成 AOF 追加阻塞问题
- AOF文件的重写会有fork时的阻塞和子进程的IO压力问题
6. RDB 和 AOF 优先级
① AOF 优先级高于 RDB,如果两种持久化方式都开启,Redis 会优先使用 AOF 来恢复数据,因为 AOF 记录了每次写操作,可以保证数据的完整性
② RDB 是一种快照持久化方式,它会定期将内存中的数据集快照写入磁盘,但是这种方式可能会丢失最后一次快照后的所有数据
③ 虽然 AOF 优先级高于 RDB,但是在某些情况下,可能需要同时使用这两种方式,以提高数据的安全性和可靠性
④ 如果两种持久化方式都关闭,Redis 会在内存中保存数据,但是一旦服务器重启,数据就会丢失
三、内存性能管理
Redis 需要在保持高性能的同时有效管理内存,因为内存是 Redis 的关键资源。通过合理配置Redis 的数据持久化机制,可以平衡性能与数据安全之间的关系,确保系统的稳定性和可靠性。
1.内存使用与碎片
1.1 查看 Redis 内存使用
127.0.0.1:6379> info memory
# Memory
used_memory:7564808 # 表示 Redis 实例当前使用的内存量为 7564808 字节,约为 7.21 兆字节
used_memory_rss:11739136
used_memory_rss_human:11.20M # 表示 Redis 进程占用的实际物理内存(包括共享库、堆栈等)为 11739136 字节,约为 11.20 兆字节
used_memory_peak_perc:94.27% # 表示 Redis 实例使用的内存峰值为 8024592 字节,约为 7.65 兆字节,占已分配内存的 94.27%。
used_memory_dataset:1737080 # 表示 Redis 数据集占用的内存量为 1737080 字节
mem_fragmentation_ratio:1.56 # 是内存碎片率,表示 Redis 内存碎片化的程度。在这种情况下,内存碎片率为 1.56
1.2 内存碎片率与 Redis 资源性能
Redis内存碎片指的是未被充分利用的内存空间,通常由于内存分配和释放的不规则或频繁操作而产生。内存按块平均分配,比如一块32字节,若此时写入redis的每条命令均为24字节,那么空出来无法使用的8字节就是碎片。
在Redis中,内存碎片率可以通过 mem_fragmentation_ratio 指标来评估,即操作系统分配的内存值 used_memory_rss 除以 Redis 使用的内存总量值 used_memory 计算得出。即MFR=used_memory_rss/used_memory。
注意:当内存使用量(used_memory)很小的时候,这个值参考价值不大。所以,建议used_memory至少1G以上才考虑对内存碎片率进行监控。
mem_fragmentation_ratio:
- 内存碎片率稍大于1是合理的,这个值表示内存碎片率比较低,也说明 Redis 没有发生内存交换
- 内存碎片率超过1.5,说明Redis消耗了实际需要物理内存的150%,其中50%是内存碎片率。需要在redis-cli工具上输入shutdown save 命令,让 Redis 数据库执行保存操作并关闭 Redis 服务,再重启服务器
- 内存碎片率低于1的,说明Redis内存分配超出了物理内存,操作系统正在进行内存交换。需要增加可用物理内存或减少 Redis 内存占用
1.3 内存使用率
redis 实例的内存使用率超过可用最大内存,操作系统将开始进行内存与 swap 空间交换。
避免内存交换发生的方法:
- 针对缓存数据大小选择安装 Redis 实例
- 尽可能的使用Hash数据结构存储
- 设置key的过期时间
2. 内存回收key
内存清理策略,保证合理分配redis有限的内存资源。当达到设置的最大阀值时,需选择一种key的回收策略,默认情况下回收策略是禁止删除。配置文件中修改 maxmemory-policy 属性值:
vim /etc/redis/6379.conf
598 # maxmemory-policy noeviction
# 示当内存达到上限时,Redis 不会驱逐任何数据,而是会拒绝写入操作并向客户端返回一个错误
volatile-lru # 使用LRU算法从已设置过期时间的数据集合中淘汰数据(移除最近最少使用的key,针对设置了TTL的key)
volatile-ttl # 从已设置过期时间的数据集合中挑选即将过期的数据淘汰(移除最近过期的key)
volatile-random # 从已设置过期时间的数据集合中随机挑选数据淘汰(在设置了TTL的key里随机移除)
allkeys-lru # 使用LRU算法从所有数据集合中淘汰数据(移除最少使用的key,针对所有的key)
allkeys-random # 从数据集合中任意选择数据淘汰(随机移除key)
noenviction # 禁止淘汰数据(不删除直到写满时报错)
保存文件并重启 Redis 服务以使更改生效
3. Redis 缓存的穿透、击穿与雪崩
3.1 穿透
指的是针对不存在于缓存中的数据进行大量请求,导致这些请求直接访问数据库。这可能是因为恶意请求或者查询不存在的键导致的。
在 maxmemory-policy 方面,可以设置为 noeviction,禁止淘汰数据,确保即使缓存未命中也不会导致数据库被大量无效请求冲击。
3.2 击穿
发生在某个热点数据过期后,此时有大量并发请求同时访问该热点数据,导致大量请求直接访问数据库。
在 maxmemory-policy 方面,可以使用 lur(最近最少使用)或者 ttl(设置过期时间)策略来尽可能保留热点数据,减少缓存击穿的概率。
3.3 雪崩
指的是缓存中大量的数据同时失效,导致大量请求直接访问数据库,造成数据库负载剧增。为了避免缓存雪崩,可以给缓存数据设置不同的过期时间,使其分散失效时间点;另外,使用热点数据预热,保证缓存中的数据不会同时失效。
在 maxmemory-policy 方面,同样可以使用 lur 或 ttl 策略来合理管理缓存的过期和淘汰,以减少缓存雪崩的风险。