持久化
AOF
-
AOF(Append Only File) :对用户的插入、修改操作进行持久化
-
组成
- *数字:当前操作有几个部分
- $数字+命令、键或值:数字表示命令、键或值一共有多少字节
-
先执行写操作命令后,才将该命令记录到 AOF 日志
- 避免额外的检查开销,保证命令是正确的
- 不会阻塞当前写操作命令的执行
-
问题:
- 还没AOF,数据会丢失
- 可能会给「下一个」命令带来阻塞风险
-
-
redis.conf配置,默认不开启
写回策略
Why?
持久化到磁盘的时机?
持久化过程:
- Redis 执行完写操作命令,将命令追加到
server.aof_buf
缓冲区 - write() 系统调用,将 aof_buf 缓冲区的数据写入到 AOF 文件
- 缓冲区的数据什么时候写入到硬盘?
-
写回策略
- Always:每次写操作命令执行完后fsync()
- Everysec:每隔一秒fsync()
- No:操作系统决定
-
主进程阻塞 VS 数据丢失
- 高性能:No
- 高可靠:Always
- 折中:Everysec
重写机制
Why?
AOF 文件越来越大,重启速度越来越慢
How?
重写,压缩AOF文件
-
读取当前数据库将每一个键值对用一条命令记录到「新的 AOF 文件」,记录完后进行替换。
-
为什么新开文件?
- 重写失败,AOF 文件污染
-
重写时机
- AOF 文件大于 64M 时
后台重写
Why?
重写过程耗时长,不能主进程处理
How?
-
后台子进程 bgrewriteaof 进程重写
-
避免阻塞主进程
-
子进程带有主进程的数据副本(只读共享内存),重写时无需加锁
-
父子进程任意一方修改了该共享内存,就会发生「写时复制」
- 写时复制(Copy On Write):父进程或者子进程写共享的内存,CPU 写保护中断,进行物理内存复制,重新设置内存映射关系,将父子进程的内存读写权限设置为可读写,最后才会对内存进行写操作
-
写操作时才会去复制物理内存,防止 fork 创建子进程时,物理内存数据的复制时间过长导致父进程长时间阻塞的问题。
-
两个阶段会导致阻塞父进程:
- 创建子进程时:要复制父进程的页表等数据结构,阻塞时间跟页表的大小有关;
- 创建完子进程后,子进程或者父进程修改了共享数据:发生写时复制,期间会拷贝物理内存,内存越大,阻塞时间越长;
-
-
-
主进程修改了已经存在 key-value:发生写时复制,只会复制主进程修改的物理内存数据,没修改物理内存还是与子进程共享的。
- 修改bigkey:复制的物理内存数据的过程就会比较耗时,有阻塞主进程的风险。
-
主进程修改已存在 kv,子进程数据不一致:AOF 重写缓冲区
-
同时将这个写命令写入到 「AOF 缓冲区」和 「AOF 重写缓冲区」
-
子进程完成后,主进程
-
AOF 重写缓冲区中内容追加新的 AOF
-
新 AOF 名,覆盖现有的 AOF 文件。
-
-
阻塞时机:
-
写时复制
-
信号处理函数执行时
RDB
- AOF:记录操作命令;
- RDB:记录二进制数据。
Why?
AOF回复要一条条指令执行,速度慢
How?
-
直接把内存的数据生成一个快照,恢复时可直接加载到内存
-
两种快照方式:
-
save:在主线程生成 RDB 文件,会阻塞
-
bgsave:子进程生成 RDB 文件避免主线程的阻塞,时机:
-
# 900 秒之内,对数据库进行了至少 1 次修改; save 900 1 # 300 秒之内,对数据库进行了至少 10 次修改; save 300 10 # 60 秒之内,对数据库进行了至少 10000 次修改; save 60 10000
- 写时复制技术:主线程修改共享数据,物理内存复制一份,主线程在数据副本进行修改操作。
- bgsave 子进程可以把原来的数据写入到 RDB 文件。
-
-
-
缺点:
- 还没快照的数据会丢失(bgsave时修改的数据、后来修改的数据)
- bgsave时修改过多数据,内存占用变2倍
RDB 结合 AOF
Why?
AOF恢复效率低,RDB丢失数据多
How?
Redis 4.0:混合使用 AOF 日志和内存快照,也叫混合持久化。
aof-use-rdb-preamble yes
-
混合持久化
- AOF 重写日志时,副本数据子进程先RDB 方式写入,重写缓冲区数据以 AOF 方式写
- 写入完成后主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧 AOF 文件
-
加载速度很快
-
数据更少丢失
过期键会如何处理的?
RDB:
-
RDB 文件生成阶段:从内存状态持久化成 RDB(文件)时,对 key 进行过期检查,过期的键「不会」被保存到新的 RDB 文件中。
-
RDB 加载阶段:
- 主服务器:对文件中保存的键进行检查,过期键「不会」被载入到数据库中。
- 从服务器:不论键是否过期都会被载入到数据库中。但由于主从服务器在进行数据同步时,从服务器的数据会被清空。所以一般来说,过期键对载入 RDB 文件的从服务器也不会造成影响。
AOF:
- AOF 文件写入阶段:AOF 模式持久化时,如果数据库某个过期键还没被删除,那么 AOF 文件会保留此过期键,当此过期键被删除后,Redis 会向 AOF 文件追加一条 DEL 命令来显式地删除该键值。
- AOF 重写阶段:AOF 重写时,对 Redis 中的键值对进行检查,已过期的键不会被保存到重写后的 AOF 文件中,因此不会对 AOF 重写造成任何影响。
主从模式
-
从库不会进行过期扫描,从库对过期的处理是被动的。
-
主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。
大Key
Why?
写入很多大 Key,AOF 日志文件很大,很快就会触发 AOF 重写机制。
fork()
创建子进程时,如果页表很大,复制过程会很耗时,发生阻塞现象。
How?
如果 fork 耗时很大,比如超过1秒,优化调整:
-
单个实例的内存占用控制在 10 GB 以下,fork 函数能很快返回。
-
Redis 只当作缓存,不关心数据安全性:关闭 AOF 和 AOF 重写,不会调用 fork 函数。
-
主从架构:适当调大 repl-backlog-size
- 避免 repl_backlog_buffer 不够大,主节点频繁全量同步(创建 RDB 文件的,调用 fork 函数)
Why?
- Always 策略:修改大 Key 写时复制,阻塞的时间会比较久
How?
Linux 开启内存大页,会影响 Redis 的性能的
- 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度来执行的。
- 关闭内存大页(默认是关闭的)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
避免大 Key
-
设计阶段,就把大 key 拆分成一个一个小 key。
-
定时检查 Redis 是否存在大 key
-
该大 key 是可以删除的
- 不使用 DEL ,该命令删除过程会阻塞主线程,
- 用 unlink(Redis 4.0+)删除大 key,异步的,不会阻塞主线程。
-