https://futures.yunsonbai.top/?hmsr=yunsonbai.top
贵金属行情

复习redis持久化-RDB

https://futures.yunsonbai.top/?hmsr=yunsonbai.top

引言

为了使得线上的数据更加的安全可靠,我们都知道Redis的数据是保存在内存中的,一旦Redis的服务器挂掉,会导致数据的丢失,所以我们在使用Redis的时候,一般会开启持久化功能,来保证数据不丢失,下边分享一下RDB和AOF两个持久化方式的知识点,本文先聊RDB。

RDB持久化命令及配置

Redis的RDB文件一般放在dir目录下,具体dir目录所在位置,可以查看redis.conf文件。

RDB文件是一个二进制文件,Redis可以把内存数据落地到RDB文件,也可以通过RDB文件恢复到down机前的状态。该持久化动作可以主动执行,也可以配置到配置文件中定期执行。RDB方式的持久化是给内存数据库做了个快照放到二进制文件中,也就是RDB文件包含Redis某个时刻的全部数据。

有两个命令可以执行RDB文件持久化,分别为save和bgsave,其中save会阻塞主进程进行持久化,这时候Redis服务器是不能接收和处理客户端发来的指令的,而bgsave则是派生一个子进程,进行同步,该命令不会阻塞主进程继续执行客户端传过来的指令,此时的不阻塞主进程,也是建立在fork完之后。这两个命令我们可以直接在客户端内执行,但是不建议在线上环境中执行save指令,会对线上业务造成很大的影响,当然也可以通过redis.conf配置来指定定期同步的时间,搜索save即可找到,默认为

save 900 1 # 在900秒内对服务器进行了至少一次修改
save 300 10 # 300秒内进行了至少10次修改
save 60 10000 # 在60秒内至少执行了10000次修改

Redis没有专门的载入RDB文件的命令,而是在启动时默认去寻找RDB文件,找到后则执行载入工作,但是如果开启了AOF持久化,则会加载aof文件进行加载。

注意,save和bgsave某个时刻只能存在一个,避免持久化的数据混乱。还有AOF方式的BGREWRITEAOF和BGSAVE进程也只会存在一个,目的是减小磁盘的负载。

配置文件中还有一条配置需要注意,stop-writes-on-bgsave-error 该值默认是yes,意思是如果 bgsave 存储快照失败,那么 redis 将阻止数据继续写入,如果将这个设置成 False 那么即使是 bgsave 快照写入磁盘失败,也不会让 redis 立即对外停止服务。

BGSAVE过程

当redis需要执行bgsave时,先检查当前是否存在save、bgsave进程,如果存在,则直接返回,当然如果是save正在执行,则直接报错,无法接受新的指令。

如果没有其他bgsave进程,则执行fork,创建出子进程,进行数据持久化,同时发送进程信号给父进程。

一些问题:

  • BGSAVE到底会不会阻塞主进程

    会,因为需要主进程进行fork才会出子进程,fork的时间快慢,关键看在复制共享页带来的开销,可以参考redis官方给出的文档,https://redis.io/topics/latency,按照Redis官方文档所述,24G的内存空间,由于内存页大小为4k,需要48MB(24G/4k * 8)的指针内存,也就是说当redis内存达到24G,fork一个子进程至少需要复制48M的内存(为什么是48M,后边聊),这需要消耗不少时间,所以可以的话,还是要用物理机部署Redis服务器。

    那是不是把页改大点,就能减少地址占的内存呢,比如扩大到8k。这个不建议,举个例子,你页越大,包含的数据越多,意味着本页存在被修改的数据可能性就越大,会使得COW效率降低,复制的内存可能会更大,在官方明确给出了说明。

  • fork出来的BGSAVE进程会不会占用主进程一样的内存大小?

    不是,只需要复制出来主进程的内存页指针,这也就是上边说的为啥24G的数据,只需要48M的内存。

    那为什么只有48M呢?这是因为使用了cow技术,即写时复制技术,linux优化了fork函数,实际fork出来的子进程,并不是完全复制父进程的全部内存数据,而是复制了主进程数据的地址空间指针,并且把主进程的内存权限设置为read-only,这样能加快fork的速度。

    那如果父进程或者子进程需要修改某块内存 数据怎么办?当主/子进程尝试修改某个内存数据时,因为内存数据是read-only权限,会触发页异常中断,陷入内核中断例程,内核就会把异常的页复制一份(注意,这里只复制需要修改的数据所在的页)给需要修改的进程,这样父子进程各有了一份数据,互不影响。而且redis的子进程只负责持久化操作,也就是读操作,不会修改数据,修改数据的场景只能发生在主进程。

    反思,如果你设计的Redis数据结构,使得大部分数据频繁更改,那这个RDB持久化子进程就不是单单内存页指针消耗的内存了,修改的内存数据越多,就意味着RDB持久化子进程占用的内存就越大。

  • RDB持久化子进程会非常耗cpu,子进程需要整理数据,并且需要将大量数据写入到磁盘中,如果内存数据过大,很可能让cpu跑满,这个要关注一下,尤其是线上cpu出现周期性的跑高,很可能和这有关系。

RDB持久化总结及延伸

  • RDB持久化的优势

    是经过内部压缩调整后的二进制文件,持久化的文件是最小的,而且加载速度要比AOF快的多。

  • RDB持久化能不能保证数据可靠

    从上边的描述可以看到,RDB持久化时需要一定的时间,并且不能实时进行,你总不希望你每条写入指令都执行一下save命令吧,那redis几乎起不到内存加速的作用了。也就是它落地的并不是最新的数据,而且有可能丢失fork出来bgsave子进程到下一次fork出bgsave子进程之前这段时间的数据,RDB持久话方式只能相对完整的让数据落盘。

  • 线上能不能使用RDB方式持久化

    在fork bgsave子进程的时候,明显会阻塞主进程,而且内存数据越大,阻塞的时间就越长,这对于高并发的应用场景来说,不太友好,你会看到监控线条周期性的突增,如果更大的并发被组塞到后边,有一定的可能让你的义务服务雪崩(比如会频繁的请求MySQL等)。

    另外,bgsave进程会消耗大量的cpu资源,这对于资源紧缺的场景也不太友好,会导致服务器周期性的抖动。

  • 一些建议

    数据量大时,尽量不要在主Redis服务器上做RDB持久化,若对数据丢失不太敏感,可以考虑在从服务器上做RDB持久化,这样能减轻主服务器的压力。

    在设计Redis数据库时,尽量存储热数据,不要当成mysql去使用,它毕竟数缓存服务。

    可能的话,尽量设计成,即便数据全量丢失,也可以通过业务逻辑,从mysql或其他数据库主动/被动的同步过来,能确保数据的安全。

    在内存使用时,尽量不要贴近最大内存去使用,留有一定的冗余。

yunsonbai wechat
公众号:技术and生活