网站改版 百度收录,腾讯有做淘宝客网站吗,人工智能培训心得体会,网站开发大数据AOF 前言什么是AOF执行后写入的好处避免额外的检查开销不会阻塞当前写操作命令的执行 潜在风险数据丢失阻塞下一个命令 三种写回策略AOF重写机制AOF后台重写数据副本的生成写时复制写时复制的阻塞问题 AOF重写缓冲区子进程重写期间工作内容 总结 前言
RDB方式不能提供强一致性… AOF 前言什么是AOF执行后写入的好处避免额外的检查开销不会阻塞当前写操作命令的执行 潜在风险数据丢失阻塞下一个命令 三种写回策略AOF重写机制AOF后台重写数据副本的生成写时复制写时复制的阻塞问题 AOF重写缓冲区子进程重写期间工作内容 总结 前言
RDB方式不能提供强一致性如果Redis进程崩溃那么两次RDB之间的数据也随之消失。那数据丢失的太多我们很难忍受,Redis推出的AOF很好的解决了数据持久化的实时性.
什么是AOF
AOF是一个日志文件Redis的每个命令都将AOF以独立日志的方式记录每次写命令,AOF日志记录不同于MySQL先写入日志然后再执行操作两阶段提交AOF是执行成功的命令. AOF会先把命令追加在AOF缓冲区然后根据对应策略写入硬盘appendfsync. 当需要恢复时直接导入AOF文件以执行其中的记录并且记录是实时的AOF日志存储Redis服务器的顺序指令序列。 不过注意: 在 Redis 中 AOF 持久化功能默认是不开启的需要我们修改 redis.conf 配置文件的参数: AOF 日志文件其实就是普通的文本我们可以通过 cat 命令查看里面的内容 不过里面的内容如果不知道一定的规则的话可能会看不懂。 我这里以「set name xiaolin」命令作为例子Redis 执行了这条命令后记录在 AOF 日志里的内容如下图
执行后写入的好处
Redis 是先执行写操作命令后才将该命令记录到 AOF 日志里的这么做其实有两个好处。
避免额外的检查开销
因为如果先将写操作命令记录到 AOF 日志里再执行该命令的话如果当前的命令语法有问题那么如果不进行命令语法检查该错误的命令记录到 AOF 日志里后Redis 在使用日志恢复数据时就可能会出错。
先执行写操作命令再记录日志的话只有在该命令执行成功后才将命令记录到 AOF 日志里这样就不用额外的检查开销.
不会阻塞当前写操作命令的执行
因为当写操作命令执行成功后才会将命令记录到 AOF 日志。
潜在风险
数据丢失
执行写操作命令和记录日志是两个过程那当 Redis 在还没来得及将命令写入到硬盘时服务器发生宕机了这个数据就会有丢失的风险。
阻塞下一个命令
将命令写入到日志的这个操作是在主进程完成的执行命令也是在主进程也就是说这两个操作是同步的。 如果在将日志内容写入到硬盘时服务器的硬盘的 I/O 压力太大就会导致写硬盘的速度很慢进而阻塞住了也就会导致后续的命令无法执行。
就会导致写硬盘的速度很慢进而阻塞住了也就会导致后续的命令无法执行。
三种写回策略
Redis 写入 AOF 日志的过程如下图: 具体步骤:
Redis 执行完写操作命令后会将命令追加到 server.aof_buf 缓冲区然后通过 write() 系统调用将 aof_buf 缓冲区的数据写入到 AOF 文件此时数据并没有写入到硬盘而是拷贝到了内核缓冲区 page cache等待内核将数据写入硬盘;具体内核缓冲区的数据什么时候写入到硬盘由内核决定。 在 redis.conf 配置文件中的 appendfsync 配置项可以有以下 3 种参数可填
Always:每次写操作命令执行完后同步将 AOF 日志数据写回硬盘Everysec:每次写操作命令执行完后先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;No: 不由 Redis 控制写回硬盘的时机转交给操作系统控制写回的时机也就是每次写操作命令执行完后先将命令写入到 AOF 文件的内核缓冲区再由操作系统决定何时将缓冲区内容写回硬盘。
这 3 种写回策略都无法能完美解决「主进程阻塞」和「减少数据丢失」的问题,因为两个问题是对立的偏向于一边的话就会要牺牲另外一边. 具体原因:
Always:可以最大程度保证数据不丢失但是由于它每执行一条写操作命令就同步将 AOF 内容写回硬盘所以是不可避免会影响主进程的性能;No:交由操作系统来决定何时将 AOF 日志内容写回硬盘相比于 Always 策略性能较好,但是操作系统写回硬盘的时机是不可预知的如果 AOF 日志内容没有写回硬盘一旦服务器宕机就会丢失不定数量的数据。EverySec:是折中的一种方式避免了 Always 策略的性能开销也比 No 策略更能避免数据丢失当然如果上一秒的写操作命令日志没有写回到硬盘发生了宕机这一秒内的数据自然也会丢失。
大家根据自己的业务场景进行选择
如果要高性能就选择 No 策略如果要高可靠就选择 Always 策略如果允许数据丢失一点但又想性能高就选择 Everysec 策略。 我也把这 3 个写回策略的优缺点总结成了一张表格 深入到源码后你就会发现这三种策略只是在控制 fsync() 函数的调用时机。 当应用程序向文件写入数据时内核通常先将数据复制到内核缓冲区中然后排入队列然后由内核决定何时写入硬盘。 如果想要应用程序向文件写入数据后能立马将数据同步到硬盘就可以调用 fsync() 函数这样内核就会将内核缓冲区的数据直接写入到硬盘等到硬盘写操作完成后该函数才会返回.Always: 每次写入 AOF 文件数据后就执行 fsync() 函数Everysec:创建一个异步任务来执行 fsync() 函数No:永不执行 fsync() 函数;
AOF重写机制
AOF 日志是一个文件随着执行的写操作命令越来越多文件的大小会越来越大. 当 AOF 日志文件过大就会带来性能问题,比如重启 Redis 后需要读 AOF 文件的内容以恢复数据,如果文件过大整个恢复的过程就会很慢. Redis 为了避免 AOF 文件越写越大提供了 AOF 重写机制. 重写规则是:当 AOF 文件的大小超过所设定的阈值后Redis 就会启用 AOF 重写机制,来压缩 AOF 文件. AOF 重写机制是在重写时读取当前数据库中的所有键值对然后将每一个键值对用一条命令记录到「新的 AOF 文件」等到全部记录完后就将新的 AOF 文件替换掉现有的 AOF 文件. 举例: 在没有使用重写机制前假设前后执行了「set name xiaolin」和「set name xiaolincoding」这两个命令的话就会将这两个命令记录到 AOF 文件。 在使用重写机制后就会读取 name 最新的 value键值对 ,然后用一条 「set name xiaolincoding」命令记录到新的 AOF 文件之前的第一个命令就没有必要记录了因为它属于「历史」命令没有作用了. 重写工作完成后就会将新的 AOF 文件覆盖现有的 AOF 文件这就相当于压缩了 AOF 文件使得 AOF 文件体积变小了。 重写机制的妙处在于:尽管某个键值对被多条写命令反复修改最终也只需要根据这个「键值对」当前的最新状态然后用一条命令去记录键值对
注意: 为什么重写 AOF 的时候不直接复用现有的 AOF 文件而是先写到新的 AOF 文件再覆盖过去。
因为如果 AOF 重写过程中失败了现有的 AOF 文件就会造成污染,可能无法用于恢复使用。
AOF后台重写
写入 AOF 日志的操作虽然是在主进程完成的因为它写入的内容不多,一般不太影响命令的操作. But,在触发 AOF 重写时比如当 AOF 文件大于 64M 时就会对 AOF 文件进行重写,这时需要读取所有缓存的键值对数据,并为每个键值对生成一条命令然后将其写入到新的 AOF 文件重写完后就把现在的 AOF 文件替换掉。 这个过程其实是很耗时的所以重写的操作不能放在主进程里。
重写AOF是后台子进程(bgrewriteaof)来完成,有两个好处:
重写期间主进程可以继续处理命令请求从而避免阻塞主进程.子进程带有主进程的数据副本(注意是子进程).
子进程是怎么拥有主进程一样的数据副本的呢
数据副本的生成
主进程在通过 fork 系统调用生成 bgrewriteaof 子进程时子进程是怎么拥有主进程一样的数据副本的呢 也就是说两者的虚拟空间不同但其对应的物理空间是同一个。 这样一来子进程就共享了父进程的物理内存数据了这样能够节约物理内存资源页表对应的页表项的属性会标记该物理内存的权限为只读。
写时复制
当父进程或者子进程在向这个内存发起写操作时CPU 就会触发写保护中断,这个写保护中断是由于违反权限导致的然后操作系统会在「写保护中断处理函数」里进行物理内存的复制并重新设置其内存映射关系将父子进程的内存读写权限设置为可读写最后才会对内存进行写操作这个过程被称为「写时复制(Copy On Write)」。
写时复制顾名思义在发生写操作的时候操作系统才会去复制物理内存这样是为了防止 fork 创建子进程时由于物理内存数据的复制时间过长而导致父进程长时间阻塞的问题。
写时复制的阻塞问题
操作系统复制父进程页表的时候父进程也是阻塞中的不过页表的大小相比实际的物理内存小很多所以通常复制页表的过程是比较快的。 如果父进程的内存数据非常大那自然页表也会很大这时父进程在通过 fork 创建子进程的时候阻塞的时间也越久。 所以有两个阶段会导致阻塞父进程
创建子进程的途中,要复制父进程的页表等数据结构 阻塞的时间跟页表的大小有关页表越大阻塞的时间也越长创建完子进程后,如果子进程或者父进程修改了共享数据 发生写时复制这期间会拷贝物理内存如果内存越大自然阻塞的时间也越长
触发重写机制后主进程就会创建重写 AOF 的子进程此时父子进程共享物理内存重写子进程只会对这个内存进行只读重写 AOF 子进程会读取数据库里的所有数据并逐一把内存数据的键值对转换成一条命令再将命令记录到重写日志新的 AOF 文件。
但是子进程重写过程中主进程依然可以正常处理命令。 如果此时主进程修改了已经存在 key-value就会发生写时复制注意这里只会复制主进程修改的物理内存数据没修改物理内存还是与子进程共享的。 所以如果这个阶段修改的是一个 bigkey也就是数据量比较大的 key-value 的时候这时复制的物理内存数据的过程就会比较耗时有阻塞主进程的风险。
AOF重写缓冲区
重写 AOF 日志过程中如果主进程修改了已经存在 key-value,此时这个 key-value 数据在子进程的内存数据就跟主进程的内存数据不一致了这时要怎么办呢 为了解决这种数据不一致问题Redis 设置了一个 AOF 重写缓冲区这个缓冲区在创建 bgrewriteaof 子进程之后开始使用。 在重写 AOF 期间当 Redis 执行完一个写命令之后它会同时将这个写命令写入到 「AOF 缓冲区」和 「AOF 重写缓冲区」。
子进程重写期间工作内容
在 bgrewriteaof 子进程执行 AOF 重写期间主进程需要执行以下三个工作:
执行客户端发来的命令将执行后的写命令追加到 「AOF 缓冲区」将执行后的写命令追加到 「AOF 重写缓冲区」
当子进程完成 AOF 重写工作扫描数据库中所有数据逐一把内存数据的键值对转换成一条命令再将命令记录到重写日志后会向主进程发送一条信号信号是进程间通讯的一种方式且是异步的。 主进程收到该信号后会调用一个信号处理函数该函数主要做以下工作
将 AOF 重写缓冲区中的所有内容追加到新的 AOF 的文件中使得新旧两个 AOF 文件所保存的数据库状态一致新的 AOF 的文件进行改名覆盖现有的 AOF 文件。
信号函数执行完后主进程就可以继续像往常一样处理命令了。
在整个 AOF 后台重写过程中除了发生写时复制会对主进程造成阻塞还有信号处理函数执行时也会对主进程造成阻塞在其他时候AOF 后台重写都不会阻塞主进程。
总结
Redis 提供了三种将 AOF 日志写回硬盘的策略:Always、Everysec 和 No为了解决AOF体积越来越大,Redis 提供了 AOF 重写机制.用 AOF 日志的方式来恢复数据其实是很慢的因为 Redis 执行命令由单线程负责的而 AOF 日志恢复数据的方式是顺序执行日志里的每一条命令