总的来说是两方面:
一方面AOF日志持久化策略在Always的情况下执行写指令会直接写入磁盘,在写入大key时,会阻塞主进程较长时间
另一方面,AOF重写和RDB持久化都会由主进程fork出一个子进程,这样在遇到大key的情况下,子进程复制父进程页表和写时复制的时候,都会造成主进程的阻塞
对于AOF日志持久化的影响
AOF持久化有三种策略,这三种策略决定了执行fsync也就是写入磁盘的时机
如果是Always,则会在每次执行写操作的时候,会直接调用fsync()将内核缓冲区的数据写入磁盘,这时如果写入的是大key,就会阻塞主进程比较长的时间
如果是EverySec,由于执行写操作后写入内核缓冲区后,是由每隔1s异步执行fsync()所以不会影响主进程
如果是No,对于Redis来说,交由操作系统决定合适执行fsync(),所以也不会影响主进程
对于AOF重写和RDB持久化的影响
AOF重写和RDB快照都会交由Redis主进程fork出的一个子进程来完成,而在fork函数的执行过程中,由于写时复制技术,子进程不会直接复制父进程的物理内存,而是复制父进程的页表,两个页表指向一个物理内存进行共享,并且页表项属性为只读。但是,如果遇到大key,Redis占用的物理内存就会比较大,这样,子进程复制的页表也会比较大,主进程就会发生阻塞现象(因为fork函数是由主进程调用的)。
另外,当主进程对物理内存进行了修改,会触发CPU的写保护中断,而写保护中断处理函数中,会将主进程修改的物理内存空间复制一份给子进程,这个时候,如果主进程修改的是大key,复制的过程就会比较长,这时也会阻塞主进程
- fork函数耗时很大时,如何调优?
- 控制单个实例的内存占用在10GB以下,fork函数就能很快返回
- Redis如果只是作为缓存使用,不关心数据安全问题,就可以关闭RDB和AOF重写,这样就不会调用fork函数了
- 在主从架构中,要适当调大 repl-backlog-size,避免因为 repl_backlog_buffer 不够大,导致主节点频繁地使用全量同步的方式,全量同步的时候,是会创建 RDB 文件的,也就是会调用 fork 函数。
另外,Linux固定分配内存页的大小是4KB,如果修改成大页模式(默认是关闭的),也就是2MB,那么在写时复制时,父进程修改了100B,Redis也要复制2MB的大页,这也是很损耗Redis的性能的!
禁用方法:echo never > /sys/kernel/mm/transparent_hugepage/enabled
大key的其它影响
大key除了影响持久化,还有其它的影响:
- 客户端阻塞。Redis执行命令是单线程的,操作大key会阻塞Redis,造成客户端迟迟没有响应
- 网络阻塞。访问量较大的情况下,并且访问的是大key时,产生的流量很大,可能阻塞网络
- 内存分布不均。集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较大。
如何避免大key?
设计阶段就要将大key拆成一个个小key,并且在使用的过程中经常检查是否存在大key。
另外,删除大key的时候不能使用del命令,应该使用unlink异步删除,这样才不会阻塞主进程