4.持久化
4.1 RDB
RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发。
4.1.1 触发机制
手动触发分别对应save和bgsave命令:
- save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间的阻塞,线上环境不建议使用。
- bgsave命令:Redis进程执行fork操作创建子线程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
除了执行命令手动触发之外,Redis内部还存在自动触发RDB的持久化机制:
- 使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave;
- 如果从节点执行全量复制,主节点自动执行bgsave生成RDB文件并发送给从节点;
- 执行debug reload命令重新加载Redis时,也会自动触发save操作;
- 默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave。
4.1.2 流程说明
bgsave是主流的触发RDB持久化方式:
① 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB\AOF子进程,如果存在bgsave命令直接返回;
② 父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞;
③ 父进程fork完成后,bgsave命令返回“Background saving started”信息并不在阻塞父进程,可以继续响应其他命令;
④ 子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。
⑤ 进程发送信号给父进程表示完成,父进程更新统计信息。
4.1.3 RDB优缺点
RDB优点:
- RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。
- Redis加载RDB恢复数据远远快于AOF方式。
RDB缺点:
- RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高;
- RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式问题。
针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。
4.2 AOF
开启AOF功能需要设置配置:appendonly yes,默认不开启。AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同RDB持久化方式一致,通过dir配置指定。AOF的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load)。
流程:
① 所有的写入命令会追加到aof_buf(缓存区)中;
② AOF缓存区根据对应的策略向硬盘做同步操作;
③ 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的;
④ 当Redis服务器重启时,可以加载AOF文件进行数据恢复。
4.2.1 文件同步
Redis提供了多种AOF缓存区同步文件策略,由参数appendfsync控制;
- 配置为always时,每次写入都要同步AOF文件,在一般的硬盘上,Redis只能支持大约几百TPS写入,不建议使用;
- 配置为no时,由于操作系统每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但数据安全性无法保证;
- 配置为everysec,是建议的同步策略,也是默认配置,做到兼顾性能和数据安全性。理论上只有在系统突然挂掉的情况下丢失1秒的数据。
4.2.2 重写机制
随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入AOF重写机制压缩文件体积。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。
AOF重写降低了文件占用空间,而且更小的AOF文件可以更快地被Redis加载。
AOF重写过程可以手动触发和自动触发:
- 手动触发:直接调用bgrewriteaof命令;
- 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。(auto-aof-rewrite-min-size表示运行AOF重写时文件最小体积,默认64MB;auto-aof-rewrite-percentage代表当前AOF文件空间和上一次重写后AOF空间文件的比值)。
流程说明:
1) 执行AOF重写请求。
如果当前进程正在执行AOF重写,请求不执行并返回提示信息
如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成之后再执行,返回提示信息
2) 父进程执行fork创建子进程,开销等同于bgsave过程;
3.1)进程fork操作完成后,继续响应其他命令。所有修改命令依然写入AOF缓存区并根据appendfsync策略同步到硬盘,保证原有AOF机制正确性。
3.2)由于fork操作运用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然响应命令,Redis使用“AOF重写缓存区”保存这部分新数据,防止新AOF文件生成期间丢失这部分数据。
4)子进程根据内存快照,按照命令合并规则吸入到新的AOF文件。
5.1) AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息;
5.2) 父进程把AOF重写缓存区的数据写入到新的AOF文件;
5.3)使用新AOF文件替换老文件,完成AOF重写。
5.复制
在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他机器,满足故障恢复和负载均衡等需求。
5.1 配置
5.1.1 建立复制
参与复制的Redis实例划分为主节点(master)和从节点(slave)。复制的数据流是单向的,只能由主节点复制到从节点。配置方式有以下三种:
- 在配置文件中加入slaveof {masterHost} {masterPort}随Redis启动生效;
- 在redis-server启动命令后加入—slaveof {masterHost} {masterPort}生效;
- 直接使用命令:slaveof {masterHost} {masterPort}生效。
slaveof本身是异步命令,执行slaveof命令时,节点只保存主节点信息后返回,后续复制流程在节点内部异步执行。主从节点复制成功建立后,可以使用info replication命令查看复制相关状态。
断开复制:slaveof no one
5.1.2 数据同步
Redis在2.8及以上版本使用psync命令完成主从数据同步,同步过程分为:全量复制和部分复制。
- 全量复制:一般用于初次复制场景,Redis早期支持的复制功能只有全量复制,它会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销;
- 部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后如果条件允许,主节点会补发丢失数据给从节点。因为补发数据远远小于全量数据,可以有效避免全量复制的过高开销。
psync命令运行需要以下组件支持:
① 主从节点各自复制偏移量
参与复制的主从节点都会维护自身复制偏移量,主节点在处理完写入命令后,会把命令的字节长度做累加记录;从节点每秒上报自身的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量;从节点在收到主节点发送的命令后,也会累加记录自身的偏移量。
通过对比主从节点的肤质偏移量,可以判断主从节点数据是否一致。
② 复制积压缓存区
复制积压缓存区是保存在主节点上的一个固定长度的队列,默认大小为1MB,当主节点有连接的从节点时被创建,这时主节点响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓存区。
由于缓存区本质上是先进先出的定长队列,所以能实现保存最近已复制数据的功能,用于部分复制和复制命令丢失的数据补救。
③ 主节点运行ID
每个Redis节点启动后都会动态分配一个40位的十六进制字符串作为运行ID。运行ID的主要作用是用来唯一标识Redis节点。如果只使用ip+port的方式识别主节点,那么主节点重启变更了整体数据集(如替换RDB/AOF文件),从节点再基于偏移量复制数据将是不安全的,因此当运行ID变化后从节点将做全量复制。(需要注意的是,Redis关闭再启动后,运行ID会随之改变)
当需要调优一些内存相关配置,这些配置需要Redis重新加载才能优化已存在的数据,这时可以使用debug reload命令重新加载RDB并保持运行ID不变,从而有效避免不必要的全量复制。
5.1.3 心跳
主从节点在建立复制后,他们之间维护着长连接并彼此发送心跳命令。
5.2 开发与运维中的问题
通过复制机制,数据集可以存在多个副本(从节点)。这些副本可以应用于读写分离、故障转移、实时备份等场景。
5.2.1 读写分离
对于读占比较高的场景,可以通过把一部分读流量分摊到从节点来减轻主节点压力,同时需要注意永远只对主节点执行写操作。
当使用从节点响应读请求时,业务端可能会遇到如下问题:
① 复制数据延迟
Redis复制数据的延迟由于异步复制特性是无法避免的,延迟取决于网络带宽和命令阻塞情况。当然有相对缓解方案。
② 读到过期数据
当主节点存储大量设置超时的数据时,如缓存数据,Redis内部需要维护过期数据删除策略,删除策略主要有两种:惰性删除和定时删除:
惰性删除:主节点每次处理读取命令时,都会检查键是否超时,如果超时则执行del命令删除键对象,之后del命令也会异步发送给从节点。需要注意的是为了保证复制的一致性,从节点永远不会自动主动删除超时数据。
定期删除:Redis主节点在内部定时任务会循环一定数量的键,当发现采样的键过期时执行del命令,之后再同步给从节点。如果此时数据大量超时,主节点采样速度跟不上过期速度且主节点没有读取过期键的操作,那么从节点将无法收到del命令。这时在从节点上可以读取到已经超时的数据。Redis在3.2版本解决了这个问题,从节点读取数据之前会检查键的过期时间来决定是否返回数据。
③ 从节点故障
综上所述,使用Redis做读写分离存在一定的成本。
主从节点之间维护心跳和偏移量检查机制,保证主从节点通信正常和数据一致。
Redis的噩梦:阻塞
① 客户端最先感知阻塞等Redis超时行为,加入日志监控报警工具可快速定位阻塞问题,同时需要对Redis进程和机器做全面监控;
② 阻塞的内在原因:确认主线程是否存在阻塞,检查慢查询等信息,发现不合理使用API或数据结构的情况。关注CPU使用率防止单核跑满。当硬盘IO资源紧张时,AOF追加也会阻塞主线程;
③ 阻塞的外在原因:从CPU竞争、内存交换、网络问题等方面入手排查是否因为系统层面问题引起阻塞。
6.内存管理
Redis主要通过控制内存上限和回收策略实现内存管理。
6.1 设置内存上限
Redis使用maxmemory参数限制最大可用内存。限制内存的目的主要有:
① 用于缓存场景,当超过内存上限maxmemory时使用LRU等删除策略释放空间。
② 防止所有内存超过服务器物理内存。
需要注意的是,maxmemory限制的是Redis实际使用的内存量,也就是used_memory统计项对应的内存。由于内存碎片率的存在,实际消耗的内存可能会比maxmemory设置的更大,实际使用时要小心这部分内存溢出。
6.2 动态调整内存上限
Redis的内存上限可以通过config set maxmemory进行动态修改,即修改最大可用内存。
6.3 内存回收策略
Redis的内存回收机制主要体现在以下两个方面:
- 删除到达过期时间的键对象;
- 内存使用达到maxmemory上限时触发内存溢出控制策略。
6.3.1 删除过期键对象
Redis所有的键都可以设置过期属性,内部保存在过期字典中。由于进程内保存大量的键,维护每个键精准的过期删除机制会导致消耗大量的CPU,对于单线程的Redis来说成本过高,因此Redis采用惰性删除和定时任务删除机制实现过期键的内存回收。
- 惰性删除:惰性删除用于当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空,这种策略是出于节省CPU成本考虑,不需要单独维护TTL链表来处理过期键的删除。但是单独用这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。正因为如此,Redis还提供了另一种定时任务删除机制作为惰性删除的补充。
- 定时任务删除:Redis内部维护一个定时任务,默认每秒运行10次(通过配置hz控制)。定时任务中删除过期键逻辑采用了自适应算法,根据键的过期比例、使用快慢两种速率模式回收键。
流程说明:
① 定时任务在每个数据库空间随机检查20个键,当发现过期时删除对应的键;
② 如果超过检查数25%的键过期,循环执行回收逻辑直到不足25%或运行超时为止,慢模式下超时时间为25毫秒;
③ 如果之前回收键逻辑超时,则在Redis触发内部事件之前再次以快模式运行回收过期键任务,快模式下超时时间为1毫秒且2秒内只能运行1次;
④ 快慢两种模式内部删除逻辑相同,只是执行的过期时间不同。
6.3.2 内存溢出控制策略
当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。具体策略受maxmemory-policy参数控制,Redis支持6种策略:
① noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息(error)OOM command not allowed when used memory,此时Redis只响应读操作。
② volatile-lru:根据LRU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退noeviction策略。
③ allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
④ allkeys-random:随机删除所有键,直到腾出足够空间为止。
⑤ volatile-random:随机删除过期键,直到腾出足够空间为止。
⑥ volatile-ttl:根据键对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
内存溢出控制策略可以采用config set maxmemory-policy {policy}动态配置。
知识点参考《Redis开发与运维》 付磊 张益军