大家好,很高兴再次见到大家,我是你们的朋友全战军。
在分布式系统中,我们经常会考虑系统的高可用性,对于无状态的程序来说,高可用性实现起来比较简单,纵向和横向扩展也比较容易。但是对于数据密集型的应用,比如数据库高可用,扩展起来就不太容易。我们考虑数据库高可用的时候,主要考虑当系统崩溃,出现意外中断的时候,尽量保持数据库的可用性,保证业务不受影响;其次,备库,只读副本节点需要实时保持与主节点的数据一致,在数据库切换的时候,要保持数据的一致性,不会出现数据丢失或者数据不一致影响业务的情况。很多分布式数据库已经解决了这个问题,而且也可以很灵活地满足业务的需求,比如同步、半同步、数据副本数、主从切换等(下面会提到),但是我们平时使用的社区官方版本.7以及之前的版本(不包括其他Mysql分支如、、)在支持分布式和系统可用性方面还不是很完善。 针对这一系列的问题,下面就来分析一下如何解决这个问题。
期间发现有MHA在线切换,提交了,导致和slave数据不一致。Pull #86 /-
.png
为了什么
在讲MHA之前,我先提前普及一下,什么是MHA?是指当活跃的服务或应用程序意外终止时,迅速启用冗余或备用的服务器、系统、硬件或网络来接管其工作。(故障转移)与swap 操作基本相同,但通常是自动完成的,不需要警告提醒,而swap 则需要手动完成。对于需要高可用性和高稳定性的服务器、系统或网络,系统设计人员通常会设计功能。
简单来说就是当系统中某个服务不可用的时候,系统其他服务模块能够自动继续提供服务。有很多设计精良的开源软件设计都会自动包含进来,比如负载均衡nginx,可以支持检测,当检测到某个()异常的时候,会自动无缝切换回正常。分布式数据密集型应用也会包含进来,包括副本集,etcd/node选举,副本集等,当某些数据节点异常的时候,选举数据节点为//,甚至像镜像队列,kafka这样的消息队列也会包含进来。
数据复制
上面提到,如果系统支持的话,必须保证有或者一定会有新的“”(这里//是统称)继续提供服务。上面提到,很多开源软件设计都内置了数据同步功能,即所有数据的插入、删除、更新都在上进行,然后数据会同步到slave上。简单来说就是slave数据需要保持一致。这里的同步方式可以像mysql-bin log那样,通过日志的方式实现,在日志中记录()、()、()等操作,然后将这些语句转发到各个从库。各个从库解析并执行SQL语句,就像接收客户端请求一样,在从库中重放数据;也可以通过传输预写日志(wal)的方式,日志先写到磁盘,比如LSM tree实现的引擎,因此日志都是包含所有数据库写入的-only字节序列。可以使用一模一样的日志在另一个节点上构建副本。 主库在将日志写入磁盘的同时,可以通过网络发送给其他从节点,比如etcd状态机同步;还有一种方式就是直接通过集群内部的进程发送需要同步的数据,比如镜像队列。
下图是数据复制的一个应用场景:一个用户写入写数据库,然后其他用户可能会从备库读取数据。这个数据可能是最新的,也可能备库因为延迟的原因不是最新的。针对这种场景,复制系统分为几种复制模式。
复制系统的一个重要细节是复制是同步()还是异步()。
关于复制方法:
同步复制():
同步复制的意思是当有数据更新请求到节点时,需要保证这个更新操作在从节点执行成功之后,才能返回给客户端。从库保证有最新的、与主库完全一致的数据副本。如果主库突然宕机,我们可以肯定在从库上还是能找到数据,但是这种方式也有缺点,如果从库没有响应(比如crash了,或者网络故障,或者其他原因),主库就无法处理写操作,主库必须block所有的写,等待同步副本再次可用,在这个过程中,数据库是无法更新和插入数据的。
异步复制():
异步复制是指当有请求更新某个节点的数据时,该操作直接返回给客户端,不需要从属确认,由从属后台更新数据。这种方法的缺点是,如果主服务器发生故障且无法恢复,则任何尚未复制到从属服务器的写入都将丢失,这意味着即使客户端已经确认成功,也无法保证写入是持久的()。这种方法的优点是,即使所有从属服务器都落后,主服务器仍可以继续处理写入操作,服务继续运行。
半同步复制(半):
半同步复制是一种中间策略,当数据更新请求发送到某个节点时,需要确保该操作在某个从节点上也执行成功,才能最终返回给客户端。如果同步从节点变慢,可以将异步从节点变成同步的。这样既能保证可靠性,又能保证一定的数据一致性(这可能会导致数据不一致,数据延迟)。
mysql半同步复制(mysql semi-):
从节点执行完更新操作后会立即将数据复制到从节点,从节点收到数据并写入中继日志(无需执行)后返回成功信息,必须收到从节点返回成功信息后才返回对客户端的响应。只有当数据复制出现异常(从节点不可用或者数据复制所用的网络异常)时,才会暂停对客户端的响应(MySQL 默认为 10 秒左右),并将复制模式降级为异步复制。当数据复制恢复正常后,复制模式将恢复为半同步复制。
mysql 数据同步和
MySQL支持比较严格的ACID,是一个性能和稳定性都非常好的关系型数据库。但是对分布式的支持并不是很友好,虽然实现了NDB,但是好像使用得并不多,国内还是普遍使用基本的主从复制方式。MySQL支持上面说的各种数据复制方式,所以各种场景只需要选择对应的复制方式即可。比如对可用性要求高,对数据一致性要求低,可以选择异步复制;对数据一致性要求高的场景,金融场景可以选择强同步复制;互联网场景可能对可用性和一致性有一定的要求,但是要求不是特别高,可以选择半同步复制。下面简单介绍一下MySQL主从同步的逻辑
首先启动mysql,然后通过mysql slave上的一个I/O线程从mysql读取,然后传输到mysql slave的relay log中,然后mysql slave的sql线程从relay log中读取relay log并应用到mysql slave的数据库中,这样就实现了主从数据同步功能。
但是MySQL本身并没有实现这一点,所以当出现异常时,需要制定一个策略来实现和处理数据库的切换。其逻辑是当出现异常时,自动将slave提升为主数据库,然后通知其他slave新的状态,继续从新的状态同步数据。这里就不得不用到MySQL高可用管理工具MHA了。MHA可以在0~30秒内自动完成数据库的故障转移操作,并且在故障转移过程中,最大程度的保证数据的一致性,实现真正的高可用。关于MHA的细节这里就不多说了,官方wiki里都有介绍(文档确实很详细,作者考虑了很多常见的场景,很多参数都是可以配置的)。
这里只分析他实现的架构和原理,结构如下(官网图片略模糊)MHA架构图
mha由两部分组成:
mha(管理节点):部署在单独的机器上,用于管理多从集群(最好和MySQL相关服务器一起管理),也可以部署在从节点上。其功能是对多个MySQL服务进行管理、检测、选举、检查连接、故障转移等。
mha节点(数据节点):运行在每台mysql服务器上,其功能是复制二进制日志;然后在具有最新数据的slave上生成差异中继日志,应用差异日志;最后在不停止SQL线程的情况下删除中继日志。
原则:
(1)保存崩溃时的二进制日志事件();
(2) 识别包含最新更新的从属设备;
(3)将不同的中继日志应用到其他从属服务器;
(4)应用已保存的二进制日志事件();
(5)使其他从服务器连接到新的从服务器进行复制;
(6)使其他从服务器连接到新的从服务器进行复制;
mha需要解决的问题:
如何确定新的:
由于MySQL没有像etcd那样的分布式集群决策节点,所以这里的选举节点就是MHA节点。MHA主要参考几个因素:1、配置文件中手动配置的候选参数**=1**(例如:优先选择同一机房或者同一机架的机器),2、根据各个slave中最新的二进制文件,可以提升最新的slave节点,选择新的
如何保证数据一致性:
MHA最大程度的保证数据不丢失,当MySQL出现异常,但是机器在正常提供服务的时候,MHA会对比该节点与即将成为该节点的slave的节点之间的数据差异,然后将这些差异数据复制到新的slave上,再利用这部分数据差异补全数据。
如果MySQL出现异常,机器异常,系统保存的二进制文件无法访问,无法进行复制,这种情况下会跳过复制过程,直接从salve候选中选择一个新的,如果使用MySQL 5.5的半同步复制,可以大大降低数据丢失的风险。复制bin-log
如何在节点间复制数据:
由于MySQL没有这样的bin-log复制功能,所以我们有一个自定义的需求,需要实现复制,这里MHA需要依赖ssh协议,也就是通过scp协议来传输文件(搭建MHA需要保证各个主机之间可以通过ssh互相通信)。
其他从节点如何知道新的:
当候选者晋升时,MHA会使用MySQL改变当前集群中所有从属的同步源。
如何解决网络分区问题:
在上面的网络结构中,我们可以猜测系统可能出现了一个很大的问题,就是网络分区。网络分区是指系统因为网络分离被分裂成两个集群,各个集群之间互不信任。对于无状态的系统来说,几乎没有影响,请求正常处理,比如nginx;当数据系统出现分区问题时,如果系统设计或者配置不合理,就会导致数据不一致,这个问题修复起来会非常复杂。所以etcd等天然支持分布式计算的数据系统,都有避免网络分区导致数据不一致的机制。解决办法是让集群中大多数可以正常通信的节点正常提供服务。比如你的集群有5个节点,分区导致一个分区有2个节点,另一个分区有3个节点,那么2个节点的分区就会被认为是异常,无法正常提供服务。也会有一些特定的算法可以解决类似的问题,比如raft。
比如下图中,有3个节点,那么信任集群的最小节点数应该是2个,这样节点C就会被标记为异常,不会正常提供服务。
mha的网络分区和上面说的有一点不同,由于集群中只有一个mha(注意这里只能部署一个,不能部署多个,否则会出现异常),所以mha不存在脑裂问题。这里的网络分区指的是mha节点和mysql节点之间的分区,如下:mha与mysql之间的网络分区
当MHA和MySQL出现两个分区的时候,MHA认为MySQL出现异常,但实际上MySQL和MySQL的slave都是正常工作的,提供服务的。但是此时MHA还是会发生切换。对于应用程序来说(如果前端有负载均衡器的话),可能两个都是,导致数据不一致。对于这种情况,MHA提供了一种二次检测的方法,即多链路检测。一个链路是MHA直接检测MySQL节点,另一个链路是MHA通过SSH登录到其他slave上检测MySQL是否正常。通过这种方式,可以解决MHA和MySQL的网络分区问题,防止错误切换。
客户端应用程序自动恢复
一般来说内置的分布式系统是可以自己恢复服务的,比如etcd,它们的客户端和集群可以自动感知集群节点的变化。客户端连接一组集群地址,见下面的例子,etcd连接的[]是一个数组。这样可以保证当某个节点IP故障,或者机器无法访问,机器异常,或者机器负载增加的时候,有其他节点来处理。etcd连接如下:cfg := . {
: []{“:2379”},
: ., // 设置当
预计时间。,
但是MySQL默认的连接方式,应用程序或者其他数据库默认的连接方式都是MySQL驱动,不可能连接数组。所以我们的解决方案是减少客户端感知,减少逻辑改动,让客户端跟以前一样只需要连接一个IP地址。这里的IP地址就是代理IP地址,有多种方式(这里不考虑分片等高级路由,只考虑应用程序连接,可以利用代理的高可用,配合互相备份)
客户端的体验最好由代理来提供。原理上,代理会解析 MySQL 协议,然后根据不同的库、表、请求类型,路由(读写分离)到后端合适的 MySQL 服务器。但是因为增加了这么 7 层代理解析,性能会有所损失,一般在 20% 左右。以上代理各有各的优势和作用,具体可以看看相关比较。我们需要做的就是在代理后端配置我们的服务应用组,配置读写分离,并能够在异常时进行切换。
四层代理不会解析MySQL七层协议,只会解析四层,所以保证MySQL后端端口通就可以了,当检测到后端不可用时,会切换到后端,由于是四层协议,所以不能配置自动读写分离,只能单独配置端口和从端口(如果配置可以自定义,有脚本可以切换,自定义脚本可以配置主从同步延时)
该方法最终的逻辑是:
手动配置VIP:在机器上配置一个虚拟VIP,当MySQL出现异常时,MHA使用配置的脚本,当出现异常时,在新的slave上使用脚本启动一个新的IP,这样对客户端没有任何影响,配置VIP比较简单。
关于配置过程[配置过程](),这里已经解释得很清楚了,就不再详细分析了。
我们的生产环境其实就是用它做读写分离的,它的文档特别全,选择它的原因是稳定高效,可以和MHA无缝配合,MHA不需要配置任何IP切换等逻辑,MHA切换的时候会自动感知系统中的角色,可以感知切换,对应用没有影响,如下图:自动识别roke
总结:
原来MySQL官方社区版本的高可用性问题就解决到这里了,通过MHA+的方式,这个方案可以以最小的代价改变现有的系统,提高系统的可用性和稳定性。前面提到MySQL之前的版本(5.7之前)对于集群的支持比较弱,但其实MySQL一直在发展,社区也开发了很多解决方案,比如,,,MySQL官方也开发了一个GA使用MySQL Group利用分布式协议来解决数据一致性问题。非常期待后续会有越来越多的解决方案被提出来,更好的解决MySQL的高可用性问题。
扫一扫在手机端查看
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。