因为工作原因了解了一下MySQL Replication(复制)相关的内容,然后感觉好久没写博客了,所以有了这一篇博客。说是了解了一下,但是其实也没看多久,所以如果发现错误欢迎指出~
基本概念:
- Binlog:MySQL 层面的log,常用于同步,审计等等。Binlog中的基本单位为Event,有几种形态:
- Statement Based:记录SQL语句
- Row Based:记录对表中行的修改
- Mix:优先使用Statement Based,对于Statement Based不支持的语句,使用Row Based。
- Statement Based:记录SQL语句
- WAL:存储引擎层面的log,比如Rocksdb中的WAL,InnoDB中的Redolog。
- 以上两者间通过XA保证一致性
- Relay Log:Binlog在远端机器上的副本
- 位点:当前复制进行到的位置,一般保存在文件或者数据库中。
单机模式
在单机模式下,以上几个Binlog和WAL的工作流程(从上往下为时间轴):
当一个事务完成了Prepare并进入Commit流程后(对应最左侧SQL Commit)
首先会通知存储引擎层进入Prepare状态。对于Innodb,几乎无需进行任何操作,因为修改已经写入Redolog。对于MyRocks,此时需要将内存中的WriteBatch写入WAL。
在存储引擎Prepare完成后开始写入Binlog。
之后依据sync_binlog参数判断是否需要将binlog刷盘。
而后通知存储引擎Commit该修改。
最后响应用户。
当发生crash时,大致有以下几种情况:
- Engine中已经Commit:
Binlog中一定有记录,无需处理 - Engine中已经Prepared,Binlog中有记录:
通知 Engine Commit - Engine中已经Prepared ,Binlog中没有记录:
通知 Engine Rollback - Engine还没Prepare:
事务还没执行完,通知 Engine Rollback
这样在单机条件下就可以保证Binlog和WAL即使在crash后也依旧是一致的。
复制(replication)
结构
首先从节点会通过IO线程向主节点注册自身。并告知主节点,自身希望从Binlog的什么位置开始复制。
主节点收到从节点的注册后就会拉起一个线程开始向从节点发送数据。
IO线程接收到主库发来的Events就会放进relay log
SQL线程负责在从库apply这些relay log
常见的几种复制模式:
异步复制:
与单机模式的区别就是在通知存储引擎Prepare的同时,会推送这个事务给从节点,但并不等待从返回,直接继续。
在这种模式下,从节点的log可能是与主节点不一致的,如果主节点宕机,则会丢失还未被同步到从节点的事务。
半同步
为了尽可能的提高主从切换之后的数据一致性,MySQL还提供了称之为半同步的模式:
与异步的主要区别是,在通知存储引擎后,必须等待至少一个从节点(新版可以配置至少等待多少个节点完成)已经将发送过去的Event落盘并回复,之后才能给用户发送响应。
如果超过设定的超时时间还没有从节点反馈,则退化为异步复制。若之后又有从节点注册到主节点上,则恢复半同步复制。
这样当用户完成一次写请求时,可以保证对应的数据至少已经保存在两个数据库中了。
然而这种流程下依然存在着问题,设想以下这种情况:
主节点到从节点的网络突然中断,两次发送的enqueue请求均没有到达从节点。而在此时主节点宕机。
这样会出现两个问题:
- 主从数据不一致。首先由于主宕机,我们会切换到从节点继续服务。当旧的主节点重新拉起后,会发现该条事务同时出现在Binlog和WAL中,故会通知引擎层Commit该事务。然而该事务在新的主节点(旧的从节点)上并不存在,进而导致旧的主节点无法与新的主节点(旧的从节点)建立同步关系。
- 幻读。当存储引擎层Commit后,该事务造成的修改就对读请求可见了,然而主从切换之后,新的主节点(旧的从节点)并没有该条事务的记录。从客户端的视角来看就是之前能读取的数据突然不见了,也就是幻读了。注意主节点宕机时并未Respond该写事务,所以并不是数据丢失。
增强(无损)半同步
为了解决以上问题MySQL又引入了增强(无损)半同步。
大致改动就是在存储引擎层Commit之前就要得到从节点的回复,进而解决了以上的问题。
全同步模式
国内大部分对于MySQL的改造版基本都引入了全同步模式/强同步模式/最大保护(Max Protection)模式
与半同步的主要不同是当超时时间内无从节点回复时,不回退至异步模式,而是停止服务,直到有从节点接收到该事务并回复。
主要用于对一致性要求较高的场景,并常通过使用多个备节点来增加可用性。
从节点 Crash Safe
如果从节点发生crash,最直接的问题就是relaylog的位点可能和relaylog不吻合(先写入了relaylog,还没来得及更新位点就崩了)。同理SQL线程执行的位置也有可能和SQL位点不吻合。
MySQL采取的改进方式也很直接:将位点保存在数据库中,从而构造一个事务,通过事务的原子性来保证位点的更新和相关的操作是原子的。
//写累了,以下等有空来补
//2020.01.01 对不起,应该是弃坑了
GTID
性能优化
由于半同步模式必须等待从节点返回,所以延迟会比异步模式高。当然“延迟不应该影响吞吐”,所以MySQL为了半同步模式下的吞吐做了很多优化。