从Redis主进程bgsave导致的应用超时说起

背景:

问题还要来源于业务方的反馈,说是最近调用我们的接口不稳定,客户端打印的日志显示偶尔的请求耗时很长,百思不得其解,这才有了这次找bug的过程以及由此引申的关于Redis RDB文件的作用

耗时的原因:

首先说一下我们的服务接口,我们的服务接口其实是一个提供商品信息的接口,入参是一批商品id(商品数量不超过10个),然后遍历每个商品id从redis中获取数据,然后返回查找到的这一批商品的信息,按理说每次redis请求的耗时一般都是0.5ms左右,所以正常这个接口的耗时不会超过5ms,那么调用方说的服务接口耗时很长的原因是什么呢?

我们一开始怀疑的方向是以为有gc的问题,但是查找了好几个请求耗时比较长的时间点,并没有gc的问题,所以排除了gc的原因

随后,我们把怀疑的方向转向了服务器和redis实例之间的网络波动,毕竟网络波动这个容易当成超时的借口,在一个多星期没有查找到具体原因的时候这个一直被我们当成对外的说辞,

一直到有一天,我们仔细看了一下接口的超时的时间点,我们发现了一个规律,超时的时间点是很有规律的差不多每五分钟出现一次超时的波峰,联想到最近DBA说开始使用redis的bgsave保存rdb文件,

我们才如梦初醒,这是和Redis的bgSave执行的时间点吻合的,那么为什么redis执行bgsave会导致超时呢?

Redis的bgsave操作导致的阻塞:

先来看一下Redis的bgsave具体的执行流程,主进程会fork一个子进程,然后子进程来执行把redis的内存快照写成rdb格式的磁盘文件,按理来说由子进程执行刷盘的操作,主进程本身不会阻塞的,那么原因是什么呢?

其实最大的原因是fork子进程的操作不是免费的,是有时间消耗的,我们先来看一下官方的说明:

Under Linux, fork() is implemented using copy-on-write pages, so the only penalty that it incurs is the time and memory required to duplicate the parent’s page tables, and to create a unique task structure for the child.

fork操作会复制父进程的页表接口以及创建子进程的进程结构数据,其中主要耗时在复制父进程的页表结构这一步,父进程占用的内存越大,父进程的页表也就也大,自然复制需要的时间也越多,一般来说,复制1G的内存的页表数据大概需要10ms-20ms,所以这就解释了为什么周期性的产生接口超时了.

如何解决既要bgsave又不想主进程卡主的问题?

这种既要又要的问题其实也容易解决,我们通过复制一个从节点,然后在从节点执行bgsave,主节点不执行bgsave操作。这样自然bgsave导致的停顿就发生在从节点上,由于从节点并不对外提供服务,所以没有关系,这样就解决了使用bgsave保存RDB文件,又不影响主Redis节点的问题

引申阅读:RDB文件的作用

可能有人就说了不执行bgsave可以吗,我不想要持久化,我redis只是作为缓存,其实我们这里使用bgsave除了为了持久化备份的需求外,主要是想要分析redis中的大key以及key的过期时间等有问题的redis的key,

有了RDB文件后,我们可以使用redis-rdb-tools(https://github.com/sripathikrishnan/redis-rdb-tools/)工具解析RDB文件,解析成JSON或者CSV文件后,我们就可以把这个数据导入到mysql中,主要输出的字段有:

输出字段说明:

database :key在redis的db

type :key类型

key :key值

size_in_bytes :key的内存大小(byte)

encoding :value的存储编码形式

num_elements :key中的value的个数

len_largest_element :key中的value的长度

expiry :key过期时间

这样我们就可以轻松的通过sql语句找出有问题的大key,以及每个key的过期时间了.

你可能感兴趣的:(redis,redis,数据库,java)