Sunskey

日拱一卒,不期而至

0%

MySQL主备高可用

MySQL主备高可用

MySQL是如何保证主备一致的

建议把节点B设置成read only 状态

  1. 有时候一些运营的查询语句会被放到备库上去查,设置只读可以防止误操作。

  2. 防止切换逻辑有bug,比如切换过程中出现双写,造成主备不一致。

  3. 可以用read only,来判断节点的角色。

binlog备份执行过程

  1. 在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。

  2. 在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接。

  3. 主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog,发给 B。

  4. 备库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。

  5. sql_thread 读取中转日志,解析出日志里的命令,并执行。

binlog三种格式的对比
  1. statement:记录的是具体的语句。可能会因去主备不一致。

  2. row:记录的是逻辑上具体的行为。日志的更加的详细。

  3. mixed:由于采用row会记录具体逻辑上的日志,所以占用的空间比statement更大。MySQl会自己判断这条语句是否可能引起主备不一致,如果有可能,就用statemnt。

使用row格式的好处在于,更方便恢复数据

循环复制问题

双M架构和M-S结构,节点A和节点B之间总是互为主备。

业务逻辑上在节点A上更新一条语句后,把生成的binlog发送给节点B,节点B执行完也会生成binlog,这样节点A又会去执行节点B生成的binlog。从而造成循环执行,怎么解决?

  1. 规定两个库的server id必须不同,如果相同,则它们之间不能设定为主备关系。
  2. 一个备库接到到binlog并在重放的过程中,生成与原binlog的server id相同的新的bingo.
  3. 每个库收到从自己主库发送过来的日志后,先判断server id,如果跟自己相同,表示这个日志是自己生成的,则直接丢弃这个日志。
线上数据库设置成“非双1”的场景。
  1. 业务高峰期。

  2. 备库延迟,为了备库尽快赶上主库。

  3. 用备份恢复主库的副本,应用binlog的过程。

  4. 批量导入数据的时候。

MySQL怎么保证高可用

“同步延迟”
  1. 主库A执行完成一个事务,写入binlog,我们把这个时刻记为T1。

  2. 之后传给备库B,我们把备库接受的这个binlog称为T2。

  3. 备库B执行完成这个事务,把这个时间成为T3。

所谓的主备延迟,就是在同一个事务中,在备库完成的时间和主库执行完时间之间的差值,也就是T3 - T1。

可以在show slave status,seconds_behind_master用于表示当前备库延迟了多少秒。

主备延迟的主要原因是备库消费日志的速度(relay log),比主库生产的binlog的速度慢的多。

主备延迟的来源
  1. 有些部署条件下,备库的所有机器性能要比主库所在的机器性能差。
  2. 备库的压力大了。例如:一些后台分析的语句,只能在备库上跑,不能影响正常的业务。

解决方案:

  • 一主多从。除了备库外,可以多接几个从库,让这些从库来分担读的压力。
  • 通过binlog输出到外部系统。比如Hadoop这类系统,让外部提供统计和查询的能力。

3.大事务。例如:不要用delete一次性删除太多的数据。大表的DDL,建议采用gh-ost方案。

4.备库的并行复制能力过低。

可靠性优先策略

1.判断备库B上的seconds_behind_master,如果小于某个值继续下一秒。否则重试

2.把主库A改成只读状态,即把readonly设置为true

3.判断备库B的seconds_behind_master,直到这个值变为0

4.把备库改写成可读写状态,把readonly设置为false;

5.把业务请求切到备库B

这个切换流程,一般是由专门的 HA 系统来完成的,我们暂时称之为可靠性优先流程。

可用性有限策略

把步骤4,5调整到最开始执行,不等主备数据同步,直接把连接切到备库B,并且让备库B可以读写,那么系统几乎没有不可用时间了。但是可能会产生数据不一致的情况。

例如:可用性策略,且binlog_format=mixed时的切换流程和数据结果。

  1. 步骤 2 中,主库 A 执行完 insert 语句,插入了一行数据(4,4),之后开始进行主备切换。

  2. 步骤 3 中,由于主备之间有 5 秒的延迟,所以备库 B 还没来得及应用“插入 c=4”这个中转日志,就开始接收客户端“插入 c=5”的命令。

  3. 步骤 4 中,备库 B 插入了一行数据(4,5),并且把这个 binlog 发给主库 A。

  4. 步骤 5 中,备库 B 执行“插入 c=4”这个中转日志,插入了一行数据(5,4)。而直接在备库 B 执行的“插入 c=5”这个语句,传到主库 A,就插入了一行新数据(5,5)。

可用性优先策略binlog_format=row时下的结果。

备库的B的(5,4)和主库A(5,5)这两行数据均不会被对方执行。

  1. 使用row格式的binlog时,数据不一致的问题更容易被发现。

  2. 主备切换的可用性优先策略会导致数据不一致。建议针对大多数应用来说,数据可靠性一般还是要优于可用性的。

MySQL的可用性时依赖主备延迟的,延迟时间越小,在出库故障的时候,服务恢复需要的时间就越短,可用性就越高。

备库延迟过高怎么解决

如果备库执行日志的速度持续低于主库生成日志的速度,那这个延迟就有可能成了小时级别。而且对于一个压力持续比较高的主库来说,备库很可能永远都追不上主库的节奏。

如果图中sql_thread 更新数据DATA是采用单线程,就会导致备库应用日志不够快,造成主备延迟。

5.6版本前,MySQL只支持单线程,由此在主库并发高,TPS高时就会出现严重的主备延迟问题。

多线程模型

图中的coordinator就是原来的sql_thread模型,只负责读取中转和分发事务。work线程的个数由slave_parallel_workers决定,建议设置在8~16之间最好。

coordinator在分发的时候,需要满足以下两个基本的要求

1.不能造成更新覆盖。这要求 更新同一行的事务,必须被分发到同一个worker中。

2.同一个事务不能被拆开,必须放到同一个worker中。

按表分发策略

每个事务在分发的时候,跟所有worker的冲突关系包括以下三种情况:

1.如果跟所有worker都不冲突,coornaditor线程就会把这个事务分配给最空闲的worker。

2.如果跟多于一个worker冲突,coordinator进入等待状态,直到和这个事务存在冲突关系的worker只剩一个。

3.如果只跟一个worker冲突,coordinator线程就会把这个事务分配给由冲突关系的worker。

缺点:如果碰到热点表更新的时候,所有的事务都会被分配同一个worker,就会变成单线程复制。

按行分发策略

如果两个事务没有更新相同的行,它们在备库上可以并行执行。显然这个模式binlog必须要求row。

按行复制和按表复制的数据结构查不多,为每一个worker,分配一个hash表,只要是按行分发,这时候的key,就必须是“库名+表明+唯一键的值”。

相比按表并行分发策略,按行并行策略在决定线程分发的时候,需要消耗更多的计算资源,同时还存在一些约束条件。

  1. 要能够从binlog里面解析出表名、主键值和唯一索引的值。binlog必须为row。

  2. 表必须由主键。

  3. 不能有外键。表上如果有外键,级联更新的行不会记录在binlog中,这样冲突检测就不准确。

缺点:1、耗费内存。2、耗费cpu。

MySQL5.6版本的并行复制策略

用于决定分发策略的hash表里,key就是数据库名。

优势: 1、构建hash的值时候跟快。 2、不要求binlog格式。

如果主库上有多个DB,并且各个DB的压力均衡,使用这个策略的效果会很好。

MariaDB的并行策略

Maria 并行复制 利用了redo log组提交的两个特性。

1.能够在同一组提交的事务,一定不会修改同一行。

2.主库上可以并行执行的事务,备库上也一定可以并行执行的。

在实现上,MariaDB是这么做的

1.一组提交事务,里面有一个相同的commit_id,下一组就是commit_id+1;

2.commit_id 直接写到binlog里面。

3.传到备库应用的时候,相同commit_id的事务分发到多个worker执行。

4.这一组全部执行完成后,coordinator再去去下一批。

缺点:要等第一组事务完全执行后,第二组事务才能开始执行,这样系统的吞吐量不够。这个方案很容易被大事务拖后腿。

MySQL 5.7的并行复制策略

1.配置DATABASE,表示使用MySQL5.6版本的按库执行

2.配置LOGICAL_CLOCK,提供了类似MariaDB的策略。

并行复制策略的思路:

1.同时处于prepare状态的事务,在备库执行时可以并行的。

2.处于prepare状态的事务,与处于commit状态的事务之间,在备库执行时也是可以并行的。

可以增加binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count参数,让主库提交慢一些,从而达到提升备库复制并发度的目的。

MySQL 5.7.22的并行复制

MySQL新增了一个新的并行度复制策略,基于WRITESET的并行复制。

相应地,新增binlog-transaction-dependency-tracking,来控制是否启用。

1.COMMIT_ORDER,同时进入prapare和commit来判断是否可以并行的策略。

2.WRITESET,表示的是对于事务涉及更新的每一行,计算出这一行的 hash 值,组成集合 writeset。如果两个事务没有操作相同的行,也就是说它们的 writeset 没有交集,就可以并行。

3.WRITESET_SESSION,是在 WRITESET 的基础上多了一个约束,即在主库上同一个线程先后执行的两个事务,在备库执行的时候,要保证相同的先后顺序。

hash值是通过“库名+表名+索引值+值”计算出来的,如果一个这个表上还有除主键索引外,那么对于每个唯一索引,insert语句对应的hash的值。

1.writeset是在主库生成后,直接写入,在备库执行的时候,节省了很多计算量。

2.不需要把整个事务的binlog都扫一遍才能决定分发到哪个worker,更省内存。

3.由于备库的分发策略不以来于binlog内容,所以binlog是statement格式也可以的。

主库出问题了,从库怎么办?

一主多从的结构

其中A和A’互为主备,从库B、C、D指向的是主库A。

相比一主一备,一主多从结构在切换完成后,A’会成为新的主库,从库B、C、D也要改接到A’,所以主备切换的复杂性也相应增加了。

当我们把节点B设置成节点A’的从库的时候,需要执行一条change master命令:

![image-20201208164303574](/Users/dxm/Library/Application Support/typora-user-images/image-20201208164303574.png)

关键在于如何设置从主库的master_log_name文件以及位置。

一种取同步位点的方法

1.等待新主库A’把中转日志(relay log)全部同步完成

2.在A’上执行show master status,得到当前A’最新的File和Position

3.取原主库A故障的时刻T

4.用mysqlbinlog工具解析A‘的File,得到T时刻的位点。

但是有可能存在主键冲突等错误。通常,在切换任务的时候,要先主动跳过这些错误。

1.主动跳过一个事务。set global sql_slave_skip_counter=1;每次碰到这些错误 ,执行一次跳过命令,直到不再出现停下来的情况。

2.通过设置slave_skip_errors参数,直接设置跳过制定的错误,等主备的同步关系建立后,并稳定执行一段时间后,把这个参数设置为空,以免真的出现主从数据不一致,也跳过了。

  • 1062 错误插入数据时唯一键冲突。
  • 1032 错误是删除数据时找不行。
GTID

GTID的全称是GTID 的全称是 Global Transaction Identifier,也就是全局事务 ID,是一个事务在提交的时候生成的,是这个事务的唯一标识。它由两部分组成,

格式是:GTID=server_uuid:gno

其中:

server_uuid是一个实例第一次启动时自动生成的,是一个全局唯一的值。

gno是一个整数,初始值是1,每次提交事务的时候分配这个事务,并加1。

事务id(transaction id)是在事务执行过程中分配的,如果这个事务回滚了,事务id也会增加,而gno是在事务提交的时候才会分配。

在GTID模式下,每一个事务都会跟一个GTID一一对应。