性能与可靠性,鱼与熊掌

170 阅读5分钟

image.png

前言

在众多的框架、数据库、中间件中,只要涉及到持久化,都会在性能与可靠性之间做一个抉择,而可供选择策略一般有三种:绝对性能绝对可靠各退一步

本文具体就对这些思想在实践中的应用做一下讨论,经过这些对比,基本上就能掌握这些思想的核心要点,也就达到了学习原理的目的:扩展思维

涉及到的内容:mysql·redolog,Redis·持久化,Kafka·ack

性能与可靠性

那么性能与可靠性,到底指的是什么呢?

性能:在这里,指的是响应速度,要尽可能快的响应客户端的请求,提升吞吐量

可靠性:在这里,指的是数据落盘,只要数据落盘,它就不会丢了(硬盘损坏不在讨论范围内)

所以,通常情况下,性能和可靠性之间是对立的,鱼和熊掌不能兼得,那么实际应用中,到底应该吃鱼还是吃熊掌呢?请接着往下看

Mysql·redolog

先介绍下innodb事务的写入流程

  1. 确保对应的Page已加载到buffer pool(若未加载,则进行磁盘IO)
  2. 写undolog,并将数据写入buffer pool
  3. 写redolog
  4. 写binlog
  5. 提交事务,响应客户端
  6. mysql有后台线程,会定期将buffer pool的数据写磁盘持久化

通过上述流程可以看出,即便事务已经提交了,数据也并不可靠:假设提交后,buffer pool还没来得及落盘,mysql就崩溃了,数据不就丢了吗?

所以在mysql重启之后,就需要用到第3步写的redolog,它里面的内容就是将这条sql重新执行一遍,这样数据就恢复了

或许大家会有个疑问,为什么不直接在事务提交前,就把buffer pool的数据落盘呢?搞一个redolog出来多此一举?

原因就是:性能,因为redolog的写入速度很快,但buffer pool的落盘很慢(前者即使有磁盘IO也是顺序写,后者是随机写,性能差别很大),这样事务的处理速度就能提升很多,但这个性能不是本文讨论的重点,请接着往下看

redolog细节

接下来进入正题,讲讲redolog的流程

实际上,redolog的写入,它也并非是直接进行磁盘IO(虽然磁盘顺序io性能已经挺不错了,但Mysql仍对此做了优化),这个优化就是redolog buffer,它是内存里面的一块空间,用于缓冲redolog。

写redolog第一步就是将其写入redolog buffer,然后在参数innodb_flush_log_at_trx_commit的控制下,决定后续的操作,这个参数有三个可选值

  1. 0:每次写redolog,只将其写入redolog buffer,等后台线程做持久化,如果mysql宕机,则会丢失数据---绝对性能
  2. 1(默认值):每次写redolog,都直接持久化到磁盘,这种方式不会造成数据丢失---绝对可靠,线上也推荐使用这个
  3. 2:每次写redolog,除了写redolog buffer以外,还会写入到操作系统的page cache,mysql宕机不会丢数据,但操作系统崩溃会丢---各退一步

mysql后台也会有一个1秒一次的线程,将redolog buffer内的数据写入操作系统page cache,以实现持久化

以上就是mysql在处理redolog写入时,在性能与可靠性上的取舍

Redis·持久化

Redis的持久化,有两种:RDB和AOF,RDB是后台执行的,因此对性能影响不大,这里讨论一下AOF

AOF(Append Only File):每当redis收到修改数据的命令时,都会将命令写入到aof文件,当redis宕机重启后,会先将aof里面的命令重跑一遍,这样就可以恢复宕机之前的数据了

开启aof,配置参数

appendonly yes

AOF也存在性能与可靠性的抉择,通过参数appendfsync来控制,它有也有三种值可以选择

  1. always:每次执行命令后,都立即写入磁盘做持久化---绝对可靠
  2. everysec:执行命令后,将命令写入缓冲区,然后每秒执行一次持久化---各退一步
  3. no:写入缓冲区就不管了,等OS自己的策略去完成持久化---绝对性能

对比一下Mysql·redolog,是不是很类似?

生产环境,一般采用everysec,能保证绝大部分的数据,性能也非常高

Kafka·ack

说完了单机的,再来说点跨网络的

kafka的producer往broker发送消息,发送之后,producer这边并不能保证数据一定发送成功,如果等,那么就会降低性能,如果不等,就有可能丢数据,所以这也存在性能与可靠性的抉择,通过参数ack来控制,也有三个可选值:

  1. 0:producer发送消息后,不等broker反馈就继续发送下一批,假如broker挂了,那么就存在丢数据的风险---绝对性能
  2. 1:producer要等broker的partition-leader收到数据并确认后,才继续发送消息,性能有所下降,但可靠性提高了---各退一步
  3. -1 / all:producer发送消息后,除了需要partition-leader确认,还需要它的follower确认,然后才会继续发送消息---绝对可靠

具体的可以参考文章ACK详解

总结

策略mysql·redologredis·aofkafka·ack
对应参数innodb_flush_log_at_trx_commitappendfsyncack
绝对性能0no0
绝对可靠2(默认)always-1/all
各退一步1everysec(默认)1(默认)

所以归纳总结一下就可以发现,思路都是差不多的,而且默认选择各退一步的居多,这种方式能保证绝大多数数据的可靠,但在宕机前的短时间内的数据则可能会丢失

但为什么mysql要默认选择绝对可靠呢?个人理解,是mysql在对待数据一致性追求上非常重视,所以牺牲一部分性能是可以接受的,在这一点上还可以了解下innodb的另一个机制:两次写