当前位置: 首页 > 科技观察

MySQL高可用浅析:MySQLHA解决方案_0

时间:2023-03-14 09:41:44 科技观察

对于大多数应用来说,MySQL是最关键的数据存储中心,那么如何让MySQL提供HA服务是我们不得不面对的问题。当master宕机时,我们需要仔细思考如何尽可能保证数据不丢失,如何快速获知master宕机并进行相应的failover处理。在此,笔者将结合这段时间所做的MySQLproxy和toolsets相关工作,谈谈我们现阶段和未来项目中将采用的MySQLHA方案。(题图来自:comprendrechoisir.com)复制必须保证MySQL数据不丢失。复制是一个很好的解决方案,MySQL也提供了一套强大的复制机制。我们只需要知道,出于性能的考虑,replication采用异步方式,即写入的数据不会同步更新到slave。如果这时候mastercrash了,我们可能还是会面临数据丢失的风险。为了解决这个问题,我们可以使用半同步复制。半同步复制的原理很简单。当master处理完一个transaction后,会等待至少一个支持semi-synchronous的slave确认收到event,并写入relay-log后返回。这样即使master挂了,也至少有一个slave拿到了完整的数据。但是,半同步并不能100%保证数据不会丢失。如果master在完成事务并发送给slave的过程中crash了,仍然可能会出现数据丢失的情况。只是与传统的异步复制相比,半同步复制可以大大提高数据的安全性。更重要的是,它并不慢。MHA的作者说他们在Facebook的生产环境中使用的是半同步(这里),所以我认为没有必要担心它的性能,除非你的业务重度已经完全超过了facebook或google。这篇文章提到,从MySQL5.7开始使用Loss-LessSemi-Synchronousreplication,所以数据丢失的概率很小。如果真的想完全保证数据不丢失,现阶段比较好的方法是使用MySQL集群方案gelera,通过同时写三份来保证数据不丢失。笔者并没有任何使用gelera的经验,只知道业内一些公司已经在生产环境中使用过,性能应该不是问题。不过gelera对MySQL代码的侵入性很大,可能不适合一些代码干净的同学:-)我们也可以使用drbd来实现MySQL的数据复制。MySQL官方文档中有一篇文档对其进行了详细的介绍,但是作者并没有使用这个方案,MHA的作者写了一些使用drdb的问题,这里仅供参考。在后续的项目中,笔者会优先使用半同步复制方案。如果数据真的很重要,我会考虑使用gelera。在Monitor之前,我们说了复制机制是为了保证master崩溃后尽可能不丢失数据,但是不能等到master跑了几分钟才知道有问题。所以一套好的监控工具是必不可少的。当mastercrash时,monitor可以快速检测并进行后续处理,比如通过邮件通知管理员,或者通知daemon快速进行failover。通常对于一个服务的监控,我们会使用keepalived或者heartbeat,这样当master挂掉的时候,我们可以方便的切换到备机上。但他们仍然无法立即检测到服务不可用。笔者所在的公司目前使用的是keepalived的方式,但是以后笔者更倾向于使用zookeeper来解决整个MySQL集群的监控和故障转移。对于任何一个MySQL实例,我们都有相应的代理程序。agent与MySQL实例放置在同一台机器上,定期向MySQL实例发送ping命令来检查其可用性。同时将agent挂载到zookeeper上面。这样我们就可以知道MySQL是否宕机了。主要有以下几种情况:机器宕机,MySQL和agent都会宕机。agent和zookeeper之间的连接自然会断开。以上三种情况,我们可以认为是MySQL机器有问题,zookeeper可以第一时间感知到。agent与zookeeper断开连接,zookeeper触发相应的childrenchanged事件,监听该事件的管控服务可以进行相应的处理。例如,如果是上面的前两种情况,管控服务可以自动进行failover,但是如果是第三种情况,则可能不处理,等待机器上的crontab或supersivord等相关服务来处理自动重启代理。使用zookeeper的好处是可以方便的监控整个集群,可以即时获取整个集群的变化信息并触发相应的事件通知感兴趣的服务,同时协调多个服务进行相关处理。而这些都是keepalived或者heartbeat做不到或者做起来太麻烦的事情。使用zookeeper的问题是部署比较复杂。同时,如果进行failover,如何让应用获取到最新的数据库地址也是一个比较头疼的问题。对于部署问题,我们需要确保MySQL与代理配对。好在这几天有了docker,真的很简单。至于改变第二个数据库地址的问题,不是只有zookeeper可以的。我们可以通知应用动态更新配置信息,VIP,或者使用proxy来解决。虽然zookeeper的好处很多,但是如果你的业务不复杂,比如只有一主一从,zookeeper可能不是最好的选择,也许keepalived就够了。Failover可以通过monitor非常方便的监控MySQL,同时在MySQL崩溃后通知相应的服务做failover处理。假设现在有这样一个MySQL集群,a是master,b、c是slave,当a发生故障后,我们需要做failover,那么b和c中应该选择哪个作为新的master呢?原理很简单,哪个slave拥有最新的原始master数据,就会被选为新的master。我们可以使用showslavestatus命令来了解哪个slave有最新的数据。我们只需要比较Master_Log_File和Read_Master_Log_Pos这两个关键字段。这两个值代表slave从master读取binlog文件的哪个位置。binlog的索引值越大,pos越大,slave就可以提升为master。这里不讨论多个slave可能被提升为master的情况。在前面的示例中,假设b被提升为master,我们需要将c重新指向新的masterb以启动复制。我们通过CHANGEMASTERTO重置了c的master,但是我们怎么知道b的binlog从哪个文件和位置开始复制呢?GTID为了解决这个问题,MySQL5.6引入了GTID的概念,即uuid:gid,uuid是MySQL服务器的uuid,全局唯一,gid是增量事务id。通过这两件事,我们就可以在binlog中记录一个事务的唯一标识。使用GTID,我们可以很方便的处理故障转移。还是前面的例子,假设此时b读到的a最后的GTID是3E11FA47-71CA-11E1-9E33-C80AA9429562:23,c是3E11FA47-71CA-11E1-9E33-C80AA9429562:15,当c指向当一个新的masterb被创建时,我们可以通过GTID知道它,只要我们在b的binlog中找到GTID为3E11FA47-71CA-11E1-9E33-C80AA9429562:15的事件,那么c就可以从它的next开始事件位置开始复印。虽然查找binlog的方式仍然是顺序查找,效率略低且比较暴力,但是比起猜测使用哪个文件名和位置要方便的多。谷歌也很早就有一个GlobalTransactionID补丁,但它只是使用了增量整形。LedisDB借鉴了它的思想来实现故障转移,但谷歌现在似乎正在逐步迁移到MariaDB。MariaDB的GTID实现和MySQL5.6不同,其实更麻烦。对于我的MySQL工具集go-mysql,这意味着编写两组不同的代码来处理GTID情况。以后是否支持MariaDB要视情况而定。虽然PseudoGTIDGTID是个好东西,但仅限于MySQL5.6+。目前大部分业务还在使用5.6之前的版本。笔者所在的公司是5.5,这些数据库至少在很长一段时间内不会升级到5.6。所以我们还是需要一个很好的机制来选择masterbinlog的文件名和位置。最初,作者打算研究MHA的实现。它采用先复制relaylog的方式来弥补缺失的事件,但是作者对relaylog不是很信任。另外MHA用的是perl这种我完全看不懂的语言,所以放弃了继续研究。幸运的是,笔者偶遇了orchestrator项目,这真是一个非常神奇的项目。它使用伪GTID方法。核心代码是这个创建数据库,如果不存在meta;如果存在,则删除事件meta.create_pseudo_gtid_view_event;分隔符;;如果不存在则创建事件meta.create_pseudo_gtid_view_event按计划每10秒开始设置@_create_eustatement:=concat('createasectdo_viewmegtaps\'',@pseudo_gtid,'\'aspseudo_gtid_unique_valfromdual');从@_create_statement准备st;执行圣;取消分配准备st;结束;;定界符;设置全局事件调度程序:=1;,每隔10s,一个uuid被写入一个view,这个会被记录在binlog中。虽然我们还是不能直接定位到GTID这样的事件,但是我们也可以定位到10s的时间间隔。这样我们就可以在一个小的间隔内比较两个MySQL的binlog。继续上面的例子,假设c中最后一个uuid的位置是s1,我们找到b中的uuid,位置是s2,然后将后面的事件一一比较。如果它们不一致,则可能存在问题并停止复制。遍历到c的最后一个binlog事件后,我们可以得到此时b的下一个事件对应的文件名和位置,然后让c指向这个位置开始复制。使用PseudoGTID需要slave启用log-slave-update选项。考虑到GTID也必须开启这个选项,我个人是完全可以接受的。以后作者自己实现的failover工具就是用PseudoGTID这种方式实现的。在《MySQL High Availability》一书中,作者使用了GTID的另一种方法。每次commit都需要把gtid记录在一个表中,然后用这个gtid找到对应的位置信息,但是这个方法需要业务MySQL客户端不支持,所以没有使用它。后记MySQLHA一直是一个比较深的领域。作者只列出了一些最近的研究。一些相关的工具会尽可能在go-mysql中实现。Update经过一段时间的思考和研究,笔者得到了很多经验和收获。设计的MySQLHA与之前有很多不同。后来发现自己设计的HA方案和这篇Facebook文章差不多。另外最近跟Facebook的人聊天,听说他们也在大力推行,所以感觉自己的方向是对的。对于新的HA,我会完全拥抱GTID。相比这个东西的出现,是为了解决原来的复制问题,所以我不会考虑没有GTID的MySQL低版本。幸运的是,我们的项目已经将MySQL升级到5.6,全面支持GTID。与fb文章改造mysqlbinlog支持半同步复制协议不同,我让go-mysql复制库支持半同步复制协议,这样就可以将MySQL的binlog实时同步到一台机器上。这可能是我和fb计划之间唯一的区别。只同步binlog肯定比原来的slave快。毕竟执行binlog中事件的过程是缺失的。另外,对于realslave,我们还是使用最原始的同步方式,没有使用semi-syncreplication。然后我们监控整个集群并通过MHA处理故障转移。以前总觉得MHA难懂,但其实它是一个非常强大的工具,真正看perl还是能看懂的。MHA已被多家公司用于生产环境,并经过测试。直接用肯定比自己写一个更划算。所以以后就不考虑zookeeper了,考虑自己写一个agent。