redis持久化有两种方式:AOF(Append Only File)和RDB(Redis DataBase)。
大Key对AOF的影响
AOF日志三种写回磁盘策略
- Always,每次写操作命令执行完后,同步将AOF日志数据写回硬盘
- Everysec,每次写操作命令执行完后,先将命令写入到AOF文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘。
- No,不有redis控制写回硬盘的时机,转交给操作系统控制写回的时机,也就是每次写操作命令执行完后,先将命令写入到AOF文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。
这个三种策略只是在控制fsync()函数的调用时机。
当应用程序向文件写入数据时,内核通常先将数据复制到内核缓冲区中,然后排入队列,然后由内核决定何时写入硬盘。
如果想要应用程序向文件写入数据后,能立马将数据同步到硬盘,就可以调用fsync()函数,这样内核就会将内核缓冲区的数据直接写入到硬盘,等到硬盘写操作完成后,该函数才会返回。
- Always策略就是每次写入AOF文件数据后,立马调用fsync()函数
- Everysec策略就会创建一个异步任务来执行fsync()函数。
- No策略就是永不执行fsync()函数
持久化最大key对这三种策略的影响。
- 使用Always策略的时候,如果写入的是一个大key,主线程在执行fsync()函数的时候,阻塞的时间会比较久,因为当写入的数据量很大的时候,数据同步到硬盘这个过程会很耗时。
- 使用Everysec策略的时候,由于是异步执行fsync()函数,所以大key同步到磁盘的时候不会影响主线程。
- 使用No的时候,由于用不执行fsync()函数,所以大key的持久化过程不会影响主线程。
大key对AOF重写和RDB的影响
当AOF日志写了很多大key,AOF日志文件的大小会很大,那么会快会触发AOF重写机制。
AOF重写机制和RDB快照的过程,都会分别调用fork()函数创建一个子进程来处理任务。
在创建进程的过程中,操作系统会把父进程的页表复制一份给子进程。这个页表记录着虚拟地址和物理地址的映射关系,而不会复制物理内存,也就是说两者虚拟空间不同,但是对应的物理空间是同一个。
这样,子进程就会共享父进程的物理内存数据了,这样能够节约物理内存的资源,页表对应的页表项属性会标记该物理内存的权限为只读。
随着redis存在这个越来越多的大key,那么redis会占用很多的内存,对应的页表就会越来越大。
在通过fork()创建函数子进程的时候,虽然不会复制父进程的物理进程,但是内核会把父进程的页表复制一份给子进程,如果页表恨到,那么这个复制过程就会很耗时,那么在执行fork()函数的时候就会发现阻塞现象。
而且fork()函数是由redis主线程调用的,如果fork()函数阻塞,那么意味这个就会阻塞redis主函数。由于redis执行命令是在主线程处理的,所以当redis主线程阻塞的时候,就无法处理后续客户端发来的命令。
创建完子进程后,父进程对共享内容中的最大key进行修改,那么内核就会发生写时复制,会把物理内存复制一份,由于大key占用的物理内存比较大,那么复制物理内存的过程也是比较耗时的,于是父进程就会发生阻塞。
大key除了会影响持久化之外,还会有以下影响:
- 客户端超时阻塞。由于redis执行命令是单线程处理,然后在操作大key是会比较耗时,那么就会阻塞redis,从客户端看,就是很久没有响应。
- 引发网络阻塞。每次获取大key产生的网络流量较大。如果一个key的大小是1M,每秒访问量为1000,那么每秒就会产生1000M的流量。
- 阻塞工作线程。如果使用del删除大key时,会阻塞工作线程,就没办法处理后续工作。
- 内存分布不均。集群模型在slot分片均匀的情况下,会出现数据和查询倾斜情况,部分有大key的redis节点占用内存多,QPS也会比较大。
避免大key
- 在设计时,把大key拆分成一个一个的小key。
- 定时检查redis是否存在大key
- 如果大key可以删除,不要使用del,使用unlink删除大key,因为该命令是异步的。