“ AOF日志重写到底会不会阻塞主线程?”
01
—
AOF介绍
redis的AOF日志,是redis持久化的一种方式,它是一种write after log,即先执行命令后记录日志。这样的好处是日志不会记录执行失败的命令,同时记录日志不会阻塞当前命令执行。
记录AOF是在主线程中执行的,所以也会阻塞主线程。这个跟AOF的写回策略有关,这个配置项参数叫appendfsync,在redis.conf文件中,默认值是everysec。下面是3种写回策略的比较:
写回策略 |
策略说明 |
优点 |
缺点 |
always |
执行命令同步写盘 |
基本不丢失命令 |
性能损耗大 |
everysec |
每秒写一次盘 |
比always性能损耗小 |
可能丢失1秒内命令 |
no |
操作系统控制写盘 |
性能损耗最新 |
可能会丢失很多命令 |
这样,我们就需要在性能和可靠性之间做一些取舍了。
当redis上执行的命令越来越多,AOF日志文件会变得很大,这样AOF文件追加命令会很慢,而且操作系统对文件大小也有一定的限制,再者如果使用AOF做主从同步或数据恢复的话,因为命令记录太多会导致耗时很长。redis解决这个问题的手段就是AOF日志重写。
02
—
AOF重写介绍
在redis.conf文件中,有下面2个参数来控制AOF重写:
auto-aof-rewrite-percentage 100 #AOF文件大小较上次重写超过100%时进行重写 auto-aof-rewrite-min-size 64mb #aof文件大小超过64m时重写
下面我们执行6条命令:
192.168.59.146:6379> set name jinjnzhu OK 192.168.59.146:6379> set password 123456 OK 192.168.59.146:6379> set name jinjunzhu1 OK 192.168.59.146:6379> set password 1234567 OK 192.168.59.146:6379> set name jinjunzhu2 OK 192.168.59.146:6379> set password 12345678 OK
之后我们查看name和password的值:
192.168.59.146:6379> get name "jinjunzhu2" 192.168.59.146:6379> get password "12345678"
这2个key的值被赋予了最新的一次赋值,虽然我们执行了6条命令,但是AOF重写后日志文件就剩了最后2条命令。
03
—
AOF重写对性能的影响
在上小节的介绍中,如果AOF文件较上次重写超过了100%,就要进行重写。但是如果日志特别大,AOF重写后把日志写回磁盘也是一个非常耗时的操作,那么AOF重写是否会阻塞主线程呢?
- AOF重写并不是在主线程中,而是redis会fork一个bgrewriteaof子进程,这样就不会阻塞主线程执行了。
- fork子进程的过程是要在主线程中执行的,这时候主线程需要拷贝内存页表,这个页表记录了虚拟内存和物理内存的映射关系,如果内存很大,拷贝过程花费的时间就会很大,而这个拷贝过程中主线程是阻塞的。
- fork子进程完成后,主线程和bgrewriteaof子进程使用的是同一块儿内存空间,这时如果有新的写请求到来,并且写命令是已经存在的key,主线程会使用CopyOnWrite的方式,为这个key申请新的内存空间,进行写操作。如果这个key是一个bigkey,那也会耗时很多。
下面我画了一个简单的图,主线程fork出bgrewriteaof子进程时,复制了一个页表给子进程用,跟自己指向相同的内存空间。但是AOF重写过程中收到了foo这个key的写命令,这时主线程需要拷贝一份数据到新的内存空间进行修改。
在AOF重写的过程中,如果有新的写命令到来了,会影响AOF重写吗?
当然不会,新的写命令不仅会记录到AOF日志的缓存区,还会记录到重写的新AOF日志缓存区,这样当AOF重写结束后,把重新缓存区数据写到新AOF文件,就不会丢失了。