Redis 开启持久化后可能的阻塞点
Fork AOF + DISK IO
Fork操作引起阻塞
redis进行rdb bgsave备份或者aof rewrite时,redis会先fork出一个子进程,由子进程接管后续的操作。主进程fork子进程的过程中,可能会造成业务阻塞。
unix-like的系统上,redis执行fork命令时,本质上是调用操作系统的fork()命令,linux下的fork调用利用了copy-on-write(COW)机制,COW不需要复制整个父进程的内存数据到子进程,子进程和父进程之间共享内存空间,两者只是虚拟空间不同。
COW的机制虽然节省了fork时复制主进程内存数据到子进程的开销,但是根据linux虚拟内存映射的机制,每个进程会存储一个页表(page table),将虚拟地址映射为物理地址,主进程fork子进程时操作系统需要为子进程申请单独的页表内存空间。
Linux系统中的页一般是4K大小,一个24GB大小的redis实例在进行fork操作时则需要24gb/4kb *8 = 48MB大小的内存空间。redis的内存越大,fork操作需要的页表空间也会越大,分配和初始化较大的内存块是消耗时间和CPU的操作。因为redis是单线程模型,这段时间无法进行正常的业务读写。
图片来源网络:虚拟内存与物理内存的映射关系
虚拟内存管理的核心是解决如何在小的物理内存上运行更大程序的问题,在linux中,解决这个问题的关键就是一个叫做page table(页表)的结构。linux把物理内存分为固定统一大小的块,称为页,一般为4kb。这样一个24gb大小的内存将包括24gb/4kb=610241024个页。这种方式称为paging。
AOF + 磁盘IO 引起阻塞
redis打开aof后,所有写操作将会记录到aof文件中。但是数据并不是直接写入文件的,它需要经过双重缓冲区:应用空间的应用bufer、内核空间的os buffer。
首先redis将写入操作追加到aof_buffer(应用缓冲区),随后,redis调用write系统函数,将aof_buffer里面的数据拷贝到os缓冲区。
当我们将数据write进文件时,内核首先将应用缓冲区中的数据拷贝到os缓冲区,数据一旦写到该缓冲区,write函数便会返回,此时的数据能够用read读回,页能够被其他进程读到,但是数据并没有落到磁盘文件。
图片来源于网络
延迟写:os缓冲区有一定大小,当缓冲区没有写满或者没有到一个同步周期时,会持续的把write()函数传递的数据写入到该缓冲区,而当缓冲区写满或者到了一个同步周期,则会把缓冲区的数据提交到输出队列,而后当数据达到输出队列队首,才开始真正的磁盘io操作,这种机制叫做延迟写。
为了保障磁盘上实际文件系统与os缓冲区中内容的一致性,linux系统提供了sync、fsync和fdatasync三个系统调用函数。
sync:该函数负责把所有内核空间中IO缓冲区修改过的内容推送到输出队列,然后就返回,它并不等待所有磁盘IO操作完成。 fsync: 与sync不同,它只会对指定文件描述符的单一文件生效,强制将该文件相连的所有修改过的数据传送到磁盘上,并且等待磁盘IO完成才返回。 fdatasync:与fsync类似,但是它仅仅影响文件数据部分。强制传送用户已写出的数据至物理存储设备。不包括文件本身的特征数据。
redis中后续os buffer中的数据写入到aof文件的时机由appendfsync参数决定,其采用的系统调用是fdatasync。
appendfsync always :同步持久化,命令写入buffer后即调用fsync写入aof文件,fsync完成后主线程才返回,性能差但数据完整性好。 appendfsync everysec :异步操作,命令写入buffer后调用write操作,后续fysnc操作由专门的线程每秒调用一次。 appendfsync no :异步操作,命令写入buffer后调用write操作,后续fsync由操作系统控制,linux按一定周期将缓冲区的数据回写硬盘。
综上,AOF使用两个系统调用来完成它的工作。一个是write(),用户将数据写入内核缓冲区,另一个是fdatasync(),用于将内核缓冲区内的数据回写到磁盘文件中。而write和fsync操作都可能是延迟的来源。
write操作引起阻塞
当系统范围内的同步正在进行时,或者当输出缓冲区已满,内核需要刷新磁盘以接受新的写操作时,write操作可能被阻塞。
fsync操作引起的阻塞
redis最常用的同步磁盘策略是everysec,用于平衡性能和数据安全性。这种策略redis使用专门的线程每秒钟执行fsync同步磁盘,当系统IO资源繁忙或者一次性写入大量数据时,也可能会造成redis主线程阻塞。

redis的主线程负责读写,aof同步线程负责每秒执行一次fsync操作,并记录上一次同步时间。
主线程在处理业务读写操作时会去对比上次AOF同步时间,没有正在完成的fsync或者有fsync正在执行,但执行时间不超过2秒,主线程继续操作。如果执行时间超过了2秒,主线程将会阻塞,直到上次的同步操作完成。
所以当redis进行大量写操作(比如set 一个几百MB的key),就可能因为aof 回写过慢(数据量太大,导致fsync超过2s)导致主线程读写阻塞。
参考:https://redis.io/docs/management/optimization/latency/ http://www.shutdown.cn/post/redis-aof-potential-block-points/

点个“赞 or 在看” 你最好看!

👇👇👇 谢谢各位老板啦!!!




