MySQL和Redis持久化横向对比


InnoDB是以页为单位来进行增删改查这些操作的,在访问页之前,需要到磁盘中把页加载到内存中的Buffer Pool中,所以Buffer Pool中的数据也有持久化的问题,这一点容易与Redis的持久化搞混,因此在这篇博客中对MySQL和Redis做一个横向对比。

1.MySQL

1.1预写式日志(Write-Ahead Logging WAL)

为什么不设计一个数据页修改后马上刷盘?首先是性能浪费,因为我们可能只是修改了一个字节,但是一页的默认大小是16KB,这样在进行磁盘IO时就很浪费;第二点就是一个事物修改的数据页可能不相邻,将这些页面刷新到磁盘时要进行随机IO,随机IO比顺序IO慢。

因此InnoDB引入了WAL机制,所有对数据的修改会先写入redo log buffer(redo 日志缓冲区),然后redo日志缓冲区里面的数据按照刷盘策略刷新到磁盘的Redo Log文件中。

具体的刷盘策略:

在事务提交时需要进行刷盘,通过参数 innodb_flush_log_at_trx_commit :

  • 0:依赖后台线程每秒刷新一次(风险最高)
  • 1(默认):每次事务提交都同步写入到磁盘,保证一定会写入成功
  • 2:事务提交时异步写入到磁盘,不能保证提交时肯定会写入,日志写入在操作系统的缓存,如果 MySQL 宕机但是操作系统没有宕机,也是可以恢复数据的。

还有其他的刷盘策略,例如:

  • redo log buffer 存储的数据超过了总容量的一半,就会将日志刷入到磁盘文件,这会影响执行效率,所以开发中应避免大事务
  • 服务器关闭时。

1.2 redo log

缓存淘汰策略:当Buffer Pool内存不足时,使用LRU算法淘汰最近最少使用的数据页,以腾出空间给新的数据页,redo log buffer被划分为若干连续的redo log block,日志缓冲区是顺序写入的,先写前面的block,写满后继续写下一个。

redo log记录的是物理逻辑日志,例如在日志里面写入的可能是将某个页面中偏移量为X的值更新为2,并且是追加写入的方式,因此是顺序IO,可以提升性能。并且redo 日志文件的磁盘空间不变,所以磁盘中的日志文件是被循环使用的,写完尾部重新写头部。

1.3异步刷脏页与Checkpoint

InnoDB将数据页缓存在内存的Buffer Pool中,修改后的页称为脏页。脏页都会串联起来形成一个Flush 链表,链表的节点也是控制块。

脏页什么时候会被刷入磁盘
若每次修改数据都刷入磁盘,则性能会很差,因此一般都会在一定时机进行批量刷盘。
InnoDB 的更新操作采用的是 Write Ahead Log 策略,即先写日志,再写入磁盘,通过 redo log 日志让 MySQL 拥有了崩溃恢复能力。
下面几种情况会触发脏页的刷新:

  • 当 redo log 日志满了的情况下,会主动触发脏页刷新到磁盘;

  • Buffer Pool 空间不足时,需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘;

  • MySQL 认为空闲时,后台线程会定期将适量的脏页刷入到磁盘;

  • MySQL 正常关闭之前,会把所有的脏页刷入到磁盘;


2.Redis

2.1 AOF

简介

AOF 全称为 Append Only File,每执行一条写命令,就把该命令追加写入到 AOF 文件中,当 Redis 服务重启时会顺序执行一遍 AOF 文件中的所有命令,以达到数据恢复的目的。

AOF日志是一种写后日志,先执行命令并写入内存,再将命令追加到AOF缓冲区。这种设计避免了语法错误命令被记录,同时减少日志校验开销。

由于 AOF 功能开启后对 Redis 性能会产生一定影响,因此 AOF 功能默认是关闭的。我们可以通过修改 redis.conf 配置文件中的配置参数来开启 AOF 功能

刷盘策略

Redis 是先执行写操作命令,然后将该命令记录到 AOF 缓冲区中,appendfsync参数控制刷盘策略

  • Always,同步写回:每个写命令执行完,立即同步磁盘;“同步写回”基本是数据零丢失,但是性能最差。
  • Everysec,每秒写回(默认):每秒同步一次,最多丢失1秒数据,平衡性能与安全。
  • No,操作系统控制的写回:由操作系统决定同步时机,性能最优但可能丢失较多数据。

这一点和MySQL的刷盘策略很像

文件重写

AOF文件存储的是协议文本,随着时间增长文件体积会变大,重写的目的是解决AOF文件膨胀问题,通过合并冗余命令生成紧凑的新文件。

原理:AOF 文件在重写时,Redis 会创建一个新的 AOF 文件,读取数据库中的所有键值对,然后用一条命令将每一个键值对记录到新的 AOF 文件中。比如说,当读取了键值对 “testkey”: “value” 之后,重写机制会记录 set testkey value 这条命令。每次执行重写时,主线程 fork 出 bgrewriteaof 子进程,由子进程来执行重写的工作,这样主线程没有阻塞,仍然可以处理新来的命令。重写期间的新写命令同时写入原AOF缓冲区和重写缓冲区,确保数据完整性。当重写完成时新文件生成后替换旧文件。、

触发 AOF 后台重写的条件:

  • 手动触发:执行BGREWRITEAOF命令。
  • 自动触发:文件大小超过auto-aof-rewrite-min-size(默认64MB)且增长比例达到auto-aof-rewrite-percentage(默认100%)

2.2 RDB

简介

RDB(Redis Database)是Redis默认采用的持久化方式,它以快照的形式将内存数据持久化到硬盘中,RDB会创建一个经过压缩的二进制文件,文件以“.rdb”结尾。

触发方式

  1. 手动触发:SAVE或BGSAVE命令,SAVE命令执行期间,Redis服务器将阻塞,直到“.rdb”文件生成完毕,线上环境不建议使用。

    而BGSAVE命令是异步版本的SAVE命令,它会fork出子进程来生成“.rdb”文件,父进城只会在fork操作时阻塞,时间很短。

  2. 通过redis.conf中的save <seconds> <changes>指令设置触发条件。例如:

    save 900 1    # 900秒内至少1次写操作触发
    save 300 10   # 300秒内至少10次写操作触发
    save 60 10000 # 60秒内至少10000次写操作触发

写时复制技术(Copy-On-Write)

fork函数创建的子进程和父进程是共享内存空间,子进程在读取数据的时候,父进程还可以修改数据,为了避免冲突,Redis 借助了操作系统提供的写时复制技术,简单来说:如果主线程是读操作,那么主线程和 bgsave 子进程相互不影响,但是,如果主线程要修改某个内存页,这个内存页就会被复制一份,主线程在这个副本上进行修改,子进程继续把原来的数据写入 RDB 文件。

2.3混合持久化

由于 RDB 文件记录的是某个时间段内的数据集,因此两次 RDB 期间的数据依然存在丢失的风险,但是制作 RDB 文件的频率太高又会对 Redis 性能带来影响。因此为了避免两次 RDB 期间数据的丢失,并且降低对性能所带来的影响, Redis 4.0 提出了一个混合使用 AOF 日志和内存快照的方法。可以通过 redis.conf 配置文件中的配置项 aof-use-rdb-preamble 来开启混合持久化功能。

利用 AOF 日志记录两次快照间的操作,因此, AOF 文件也不会太大,也可以避免重写开销。如下图所示,T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次做全量快照时,就可以清空 AOF 日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了。

Redis 官方推荐使用混合持久化方案。


文章作者: Fuchanglai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Fuchanglai !
赏
  目录