MySQL主备高可用
MySQL是如何保证主备一致的
建议把节点B设置成read only 状态
有时候一些运营的查询语句会被放到备库上去查,设置只读可以防止误操作。
防止切换逻辑有bug,比如切换过程中出现双写,造成主备不一致。
可以用read only,来判断节点的角色。
binlog备份执行过程
在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。
在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接。
主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog,发给 B。
备库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。
sql_thread 读取中转日志,解析出日志里的命令,并执行。
binlog三种格式的对比
statement:记录的是具体的语句。可能会因去主备不一致。
row:记录的是逻辑上具体的行为。日志的更加的详细。
mixed:由于采用row会记录具体逻辑上的日志,所以占用的空间比statement更大。MySQl会自己判断这条语句是否可能引起主备不一致,如果有可能,就用statemnt。
使用row格式的好处在于,更方便恢复数据。
循环复制问题
双M架构和M-S结构,节点A和节点B之间总是互为主备。
业务逻辑上在节点A上更新一条语句后,把生成的binlog发送给节点B,节点B执行完也会生成binlog,这样节点A又会去执行节点B生成的binlog。从而造成循环执行,怎么解决?
- 规定两个库的server id必须不同,如果相同,则它们之间不能设定为主备关系。
- 一个备库接到到binlog并在重放的过程中,生成与原binlog的server id相同的新的bingo.
- 每个库收到从自己主库发送过来的日志后,先判断server id,如果跟自己相同,表示这个日志是自己生成的,则直接丢弃这个日志。
线上数据库设置成“非双1”的场景。
业务高峰期。
备库延迟,为了备库尽快赶上主库。
用备份恢复主库的副本,应用binlog的过程。
批量导入数据的时候。
MySQL怎么保证高可用
“同步延迟”
主库A执行完成一个事务,写入binlog,我们把这个时刻记为T1。
之后传给备库B,我们把备库接受的这个binlog称为T2。
备库B执行完成这个事务,把这个时间成为T3。
所谓的主备延迟,就是在同一个事务中,在备库完成的时间和主库执行完时间之间的差值,也就是T3 - T1。
可以在show slave status,seconds_behind_master用于表示当前备库延迟了多少秒。
主备延迟的主要原因是备库消费日志的速度(relay log),比主库生产的binlog的速度慢的多。
主备延迟的来源
- 有些部署条件下,备库的所有机器性能要比主库所在的机器性能差。
- 备库的压力大了。例如:一些后台分析的语句,只能在备库上跑,不能影响正常的业务。
解决方案:
- 一主多从。除了备库外,可以多接几个从库,让这些从库来分担读的压力。
- 通过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时的切换流程和数据结果。
步骤 2 中,主库 A 执行完 insert 语句,插入了一行数据(4,4),之后开始进行主备切换。
步骤 3 中,由于主备之间有 5 秒的延迟,所以备库 B 还没来得及应用“插入 c=4”这个中转日志,就开始接收客户端“插入 c=5”的命令。
步骤 4 中,备库 B 插入了一行数据(4,5),并且把这个 binlog 发给主库 A。
步骤 5 中,备库 B 执行“插入 c=4”这个中转日志,插入了一行数据(5,4)。而直接在备库 B 执行的“插入 c=5”这个语句,传到主库 A,就插入了一行新数据(5,5)。
可用性优先策略binlog_format=row时下的结果。
备库的B的(5,4)和主库A(5,5)这两行数据均不会被对方执行。
使用row格式的binlog时,数据不一致的问题更容易被发现。
主备切换的可用性优先策略会导致数据不一致。建议针对大多数应用来说,数据可靠性一般还是要优于可用性的。
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,就必须是“库名+表明+唯一键的值”。
相比按表并行分发策略,按行并行策略在决定线程分发的时候,需要消耗更多的计算资源,同时还存在一些约束条件。
要能够从binlog里面解析出表名、主键值和唯一索引的值。binlog必须为row。
表必须由主键。
不能有外键。表上如果有外键,级联更新的行不会记录在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一一对应。