想到redis,你的第一反应是什么呢?redis很快,我们一般一用它做缓存,再想想他为什么快呢?也许你的第一反应和我的第一反应是一样的,因为他是基于内存存储的,IO多路复用等。那么既然是基于内存存储的,那要是redis当宕机了那岂不是内存的数据都无法恢复了(在一些特殊情况下数据比较重要的情况)。那redis是如何解决这一问题?那就是redis的持久化机制。
redis 有两种持久化方式,RDB和AOF,今天我们主要先聊聊RDB持久化数据
从RDB的两个命令说起:SAVE 和 BGSAVE
在同步到磁盘和持续写入这个过程是如何处理数据不一致的情况呢?这几句是他redis为什么用子进程而不用子线程的原因之一,一个进程他可以包含多个线程,这多个线程是使用的这个进程分配下来的内存地址进行工作的。所以说多个进程之间的内存地址是相互独立的了,所以这也就是说不会出现在线程内加锁的情况,加锁必然会造成延时(除偏向锁等特殊锁类型)降低性能。
既然是两个进程那如何同步另一个进程中的数据到另一个进程呢?同步的这个过程内存中的数据是不断的在变化的,且两个进程也操作的同一个数据啊,线程安全的概念:线程安全是程式设计中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。(来自维基百科) 这岂不是进程安全问题?(自造)
我们先看redis的具体是怎么做的,下面是伪代码, 也就是调用了fork()方法。*在计算机编程领域,尤其是 Unix 和类 Unix 系统中,fork 都是一个进程用于创建自己拷贝的操作,它往往都是被操作系统内核实现的系统调用,也是操作系统在 nix 系统中创建新进程的主要方法。当程序调用了 fork 方法之后,我们就可以通过 fork 的返回值确定父子进程,以此来执行不同的操作: fork 函数返回 0 时,意味着当前进程是子进程; fork 函数返回非 0 时,意味着当前进程是父进程,返回值是子进程的 pid;在 fork 的 手册 中,我们会发现调用 fork 后的父子进程会运行在不同的内存空间中,当 fork 发生时两者的内存空间有着完全相同的内容,对内存的写入和修改、文件的映射都是独立的,两个进程不会相互影响。,可想而知,由于RDB存储的是某一时间点之前的快照,所以父进程写入新的数据并不会影响子进程。
def BGSAVE():
pid = fork()
if pid == 0:
# 子进程保存 RDB
rdbSave()
elif pid > 0:
# 父进程继续处理请求,并等待子进程的完成信号
handle_request()
else:
# pid == -1
# 处理 fork 错误
handle_fork_error()
通过上面的分析和理解,但是还会有一个问题,也就是内存中数据量很大的时候,这个子进程在拷贝父进程的内存数据的时候会耗费大量的CPU时间片导致客户端的线程一直处于就绪状态。那这个问题应该如何解决呢?
问题总是会被解决的,写时拷贝(COPY—ON-WRITE)意思就是在刚开始的时候调用fork()方法,并不会将主进程的物理空间复制一份出来给子进程,子进程在此时会和主进程共享同一块物理内存空间,但是拥有不同的虚拟空间。所以在这个时候并不会出现大量数据的复制操作而是给子进程搞一个引用就ok了。所以我们上面所担心的问题也就解决了。