我在“redis持久化方式——AOF\RDB”中详细介绍了redis持久化的策略,一言蔽之,就是将数据库的内存数据保存在磁盘文件中,本文讲解大key对redis持久化的影响
什么是大key?
redis中的数据以key——value形式存储,大key指的是一个key对应的value占用空间较大,那么叫大value不就好了?对于一个key-value键值对,它的唯一标识是key,所以用大key形容一个键值对中的value占用空间大
大key会造成什么影响?
redis在持久化的过程中,核心的操作是数据的复制,大key会造成数据复制时间过长
大key对AOF、AOF重写的影响
1.AOF是逐条命令进行复制的,我们复习一下AOF的具体过程:server每执行一次写命令,就将该命令写入aof缓冲区,写入磁盘的时机由三种策略决定,分别是(1.每条新命令都写入磁盘 2.每秒将缓冲区的命令写入磁盘 3.由操作系统决定)
- Always 策略就是每次写入 AOF 文件数据后,就执行 fsync() 函数;
- Everysec 策略就会创建一个异步任务来执行 fsync() 函数;
- No 策略就是永不执行 fsync() 函数;
- 当使用 Always 策略的时候,如果写入是一个大 Key,主线程在执行 fsync() 函数的时候,阻塞的时间会比较久,因为当写入的数据量很大的时候,数据同步到硬盘这个过程是很耗时的。
当使用 Everysec 策略的时候,由于是异步执行 fsync() 函数,所以大 Key 持久化的过程(数据同步磁盘)不会影响主线程。
当使用 No 策略的时候,由于永不执行 fsync() 函数,所以大 Key 持久化的过程不会影响主线程。
2.由于redis主线程执行命令操作和写入aof缓冲区操作是串行的,如果一条命令中有大key,在写入命令到aof缓冲区时会阻塞主线程相当长一段时间,这段时间内服务器无法响应其它请求
3.AOF重写是在aof文件大于64M时触发的,如果aof文件写入大key,很快就会触发AOF重写,主进程fork出子进程进行AOF重写操作,fork时会复制父进程的页表给子进程,fork操作会阻塞主进程,如果此时aof文件过大,页表也会很大,复制页表的操作将会很耗时,也会阻塞主进程一段时间
4.大key对RDB的影响在于bgsave创建子进程时对父进程页表的复制操作
5.AOF和RDB创建子进程进行持久化时,除了复制页表会造成性能下降,还有一点:父进程修改页表中的内存数据,会触发写时复制,由于fork时父子进程的页表都被置为只读,当父进程修改内存页时,造成异常,触发写保护中断,中断处理程序先将要被修改的内存页复制一份给子进程,再执行父进程的修改操作,这里的复制操作也比较耗时
大 key 除了会影响持久化之外,还会有以下的影响。
- 客户端超时阻塞。由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。
- 引发网络阻塞。每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
- 阻塞工作线程。如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。
- 内存分布不均。集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较大。
如何避免大key造成的影响?
1.最好在设计阶段,就把大 key 拆分成一个一个小 key。或者,定时检查 Redis 是否存在大 key
2.如果该大 key 是可以删除的,不要使用 DEL 命令删除,因为该命令删除过程会阻塞主线程,而是用 unlink 命令(Redis 4.0+)删除大 key,因为该命令的删除过程是异步的,不会阻塞主线程。