上一篇中根据自己的理解大概聊了下kafka中的leader副本与follower副本,follower副本通过不停的向leader副本发送fetch请求获取到records并写入本地log中来保证leader副本中的消息存在多个备份,当leader副本不工作的时候可以从ISR与leader副本保持同步的broker中选举出新的leader副本。
简单总结:
1) kafka的场景是leader副本中消息的备份,为了当leader副本挂掉后依然能够工作
2) 读写都是在leader副本上的,follower副本只负责备份
3) follower副本通过发送fetch请求来获取消息
4) 讲log中的消息分为可见消息和不可见消息,可见消息为follower副本们与leader副本保持一致的消息
这一篇中粗略地聊一下mysql中的主备,又是另一翻天地。
对比
1)mysql的场景可以是读写分离,为了提高读写性能;也可以是为了高可用;
2)可以设置主库写,备库度,也可以设置读写都在主库上
3)从库通过主库传过来的binlog进行同步
4)解决不了主从延时问题
mysql中主从同步场景
mysql中用主从复制的架构场景可以有多个
高可用场景:如果只有一台机器并且挂机了,那么系统将不可用,如果有主备,主库挂机可以让备库工作。
读写分离场景:mysql中如果只部署一台机器在大数据量的情况下会容易出现性能瓶颈,因此会考虑部署主从拓扑结构,一般的做法是写操作在主server上,读操作在从server上,以此来减轻主server的压力,提高读写性能,支持更多的读写请求。
备库一般设置成readonly,原因是1)运营类的查询语句会被放到备库查,防止误操作;2)切换过程中如果出现双写会造成主备不一致;3)通过readonly判断节点的角色。不用担心备库设置成readonly会影响执行主库更新的同步,因为执行的线程有super超级权限。
有了主从拓扑后,需要保证从机器上存储的与主机器上存储的数据是一致的,即同步机制,考虑一主一备的情况,当存在以下两种情况的时候会发生主备的切换1)主动切换场景,软件升级,主库按照计划下线;2)被动切换场景,主库主动掉线。
mysql通过什么保证主备一致?
答案是binlog。
先大概了解下binlog吧。binlog的用途广泛,可以用来做崩溃回复crash safe(根据redo log中prepare状态的事务在binlog中如果也完整就认为自动提交,如果不完整就不作数),可以用来给外部其他服务感知数据库数据的变化,可以用来做主备同步。
binlog的三种格式:row,statement,mixed,为何需要mixed呢?例如如果用statement,执行delete xxx limit 1,容易导致主备执行同一条语句不一致,如果用row,一次更新需要很多条的时候需要写入很多row造成数据量过大,设置成mixed,mysql会自动判断。
mysql主备同步的流程
mysql主从复制流程如下图所示:主库上,客户端更新数据时首先会记录到undo log中为mvcc以及事务回滚作准备,在内存中更新完成后写入redo log中为提高写入性能和crash-safe作准备,当引擎层更新处理后,引擎层与server层为了保持数据一致性,会进行两阶段提交,在binlog中写入操作为以后当备份作准备。备库上此时会有一个io线程与主库维持TCP连接,向主库获取binlog,主库收到请求后会有一个dump_thread线程将binlog发送给备库,备库收到后会写入本地当relay log,并且会有sql_thread从relay log中读取并执行。
mysql可以配置主从复制模式,异步复制即为主库发送binlog到备库后立刻返回ack给用户,不管备库有没有收到,问题是如果主库繁忙,性能下降,甚至宕机,从库变成主库,会出现数据不一致的情况。切换的时候有两种策略,1)可用性策略,即不管数据一致,保证可用性 2)数据不丢失策略,数据库对外不服务,等到同步后进行切换。
针对异步复制的问题,mysql可以设置同步复制或者半复制,主库等到备库发送收到的消息后再commit,然后发送请求给用户。问题是如果只有一台从库,且宕机,主库由于在等待,会被卡住。
解决方法是,可以再多配置一台从库,mysql主库设置为只要一台从库返回即可。这样做的需要至少3台机器,且性能比1台差。
| 高可用 | 丢数据 | 性能 | |
|---|---|---|---|
| 一主一从(异步复制,手动切换) | 否 | 可控 | 好 |
| 一主一从(异步复制,自动切换) | 是 | 是 | 好 |
| 一主二从(同步复制,自动切换) | 是 | 否 | 差 |
mysql主备延迟定义
如果备库执行主库的binlog,只能保证最终一致性,对于高可用来说,仅保证最终一致性还不够。
备库延迟时间seconds_behind_master=T3-T1备库延迟了多少秒时间定义:
T1:主库执行完成一个事务,写入binlog的时刻;
T2:传给备库,接收到的时刻;(T1~T2时间很短)
T3: 备库执行完事务的时刻;(主要时间在这里)
主备延迟的场景和原因:1)备库所在机器的性能差 2)备库压力大 3)大事务 4)大表DDL 5)备库的并行复制能力
附录:分发策略
从思路上来说先给出简单的策略,单线程->按表分发策略->按行分发策略
- 按表分发策略:
每个worker线程对应一个hash表,保存当前正在这个worker的执行队列里的事务所涉及的表,key是库名.表名,value是数字,表示队列中有多少个事务修改这个表。
每个事务在分发的时候,与worker有冲突关系包括以下三种:
1) 如果和所有的worker都不冲突,就会把这个事务分配给最空闲的worker
2)如果跟大于一个worker冲突,sql_thead就进入等待状态,直到和这个事务存在冲突关系的worker只剩下一个;
3)如果只跟一个worker有冲突,就会把这个事务分配给这个存在冲突关系的worker
场景:多个表负载均衡的场景,热点表就类似还是单线程复制了。
- 按行分发策略:
核心思路:如果两个事务没有更新相同的行,在备库上可以并行执行,要求binlog必须是row
与按表类似,为每个worker分配一个hash表,key是库名+表名+索引的名字+索引值,但是要消耗更多的计算资源
两种方案的约束条件:要能从binlog中解析出表名、主键值和唯一索引值,必须是row格式;必须有主键;不能有外键
按行的局限性:大事务 1)耗费内存,删除100万行,hash表要记录100万个项;2)耗费CPU,解析binlog,计算hash值,对于大事务来说,成本高。
解决方案:设置阈值,单个事务如果超过设置的行数,就退化为单线程模式
-
官方解法
-
msyql5.6 按表策略
-
mariaDB:基于组提交优化
1)能够在同一组里提交的事务,一定不会修改同一行;2)主库上可以并行执行的事务,备库上也一定可以并行执行;
做法: 1)一组提交的事务有一个相同的commit_id,下一组就是commit_id+1; 2)commit_id直接写到binlog里面; 3)备库吧相同的commit_id的事务分发到多个worker执行;4)这一组全部执行完取下一批
问题:没有真正实现模拟主库并发度的目标,主库一组事务在commit的时候,下一组事务是同时处于执行中的状态,而备库要等上一组执行完毕才能执行下一组; 容易被大事务拖后腿
- msyql5.7
配置为DATABASE使用mysql5.6的策略,配置为LOGICAL_CLOCK,采用mariaDB的策略,做了优化(commit状态的改成prepare状态)。
在执行状态的事务不能并行,因为会出现锁冲突,而在redo log prepare状态下就已经通过了锁的检测,因此,不用等到commit状态,只要能够到达redolog prepare状态,就可以了。
1)同时处于prepare状态的事务,在备库执行时可以并行;2)处于prepare状态的事务,与处于commit状态的事务之间,在备库执行时也是可以并行的。
binlog_group_commit_snyc_delay和binlog_group_commit_sync_no_delay_count参数,分别表示延迟多少微秒以后才调用fsync,和累积多少次后调用fsync,用于拉长binlog从write到fync的时间,减少binlog的写盘次数,制造更多的处于prepare阶段的事务,增加了备库的并行度,让主库提交的慢一点,让备库执行的快一些
- msyql5.7.22 writeset策略,总结下来就是可以选择配置用表策略还是mariadb的策略(无论commit状态还是prepare状态),为了减少计算量,更加通用针对binlog的各种格式,当对于表没有主键和外键约束的场景就退化为单线程。
附录:切换
当主库从0切换到1的时候,除了需要切换业务流量外,还需要把从库的主库都变成新的主库。
- 基于位点的主备切换 从库要设置变成某个主库的从库,需要执行change master命令,填写主库的host,密码,用户名等,还需要填写两个参数,即主库的log和位置,master_log_name和master_log_pos。这两个参数很难精确获取到,只能获取一个大概位置,为了考虑切换过程中不丢数据,总要找一个稍微往前一点的,再通过判断跳过备库上已经执行过的事务。
获取这两个参数过程:1)mysql1新主库把relay log都完成 2)mysql1上执行show master status,得到最新的file和pos 3) 找到mysql0上的鼓掌时刻T 4) 用mysqlbinlog工具解析mysql1的文件,得到T时刻的位点。
如何跳过重复事务呢?有两种办法1)主动跳过事务,set global sql_slave_skip_counter=1;start slave;切换过程中,可能不止重复执行一个事务,所以需要在mysql2,3,4刚开始连接到辛苦mysql1的时候,持续观察,每次遇到这种错误就停下来,执行一次跳过命令,直到不再出现停下来的情况 2)设置slave_skip_errors参数,直接设置跳过指定的错误,1062插入数据时唯一键冲突,1032删除数据时找不到行。
以上的获取位点略显复杂,需要手动处理很多事情,mysql5.6采用了新的方法GTID(全局事务ID,事务在提交的时候生成。每个mysql都维护了一个GTID集合。主要步骤:1)mysql2,3,4指定从库mysql1,基于主备协议建立连接 2)mysql2,3,4把各自的GTID发送给msyql1 3)mysql1计算出自己的GTID与发送过来的GTID的差集,存在自己没有而别人有的情况,直接返回错误,如果自己包含了别人的集合,就从自己的binlog文件里找到第一个不存在别人集合的事务发送给mysql2,之后就从binlog中顺序取发送给mysql2.
两种寻找位置方法的区别:GTID的主备关系里面,主库根据备库发送的GTID集合进行判断找到差集;找同位点的方法中,备库向主库要,备库决定,主库不作任何判断。找位点这个工作可以认为是放到了主库内部自动完成。