kafka,mysql,redis主从复制方案比较之mysql

1,059 阅读10分钟

上一篇中根据自己的理解大概聊了下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集合进行判断找到差集;找同位点的方法中,备库向主库要,备库决定,主库不作任何判断。找位点这个工作可以认为是放到了主库内部自动完成。