另一个需要考虑的问题是,即使应用并没有停止服务,但是否可能丢失了数据。如果服务器遭遇灾难性故障,可能多少都会丢失一些数据,例如最近已经写入(最新丢失的)二进制日志但尚未传递到备库的中继日志中的事务。你能够容忍吗?大多数应用能够容忍;因为替代方案大多非常昂贵且复杂,或者有一些性能开销。例如,可以使用同步复制,或是将二进制日志放到一个通过DRBD进行复制的设备上,这样就算服务器完全失效也不用担心丢失数据。(但是整个数据中心也有可能会掉电。)
一个良好的应用架构通常可以降低可用性方面的需求,至少对部分系统而言是这样的,良好的架构也更容易做到高可用。将应用中重要和不重要的部分进行分离可以节约不少工作量和金钱,因为对于一个更小的系统改进可用性会更容易。可以通过计算“风险敞口(riskexposure)”,将失效概率与失效代价相乘来确认高优先级的风险。画一个简单的风险计算表,以概率、代价和风险敞口作为列,这样很容易找到需要优先处理的项目。
我们经常听到导致数据库宕机最主要的原因是编写的SQL査询性能很差,真的是这样吗?2009年我们决定分析我们客户的数据库所遇到的问题,以找出那些真正引起宕机的问题,以及如何避免这些问题。结果证实了一些我们已有的猜想,但也否定了一些(错误的)认识,我们从中学到了很多。
我们首先对宕机事件按表现方式而非导致的原因进行分类。一般来说,“运行环境”是排名第一的宕机类别,大约35%的事件属于这一类。运行环境可以看作是支持数据库服务器运行的系统和资源集合,包括操作系统、硬盘以及网络等。性能问题紧随其后,也是约占35%;然后是复制,占20%;最后剩下的10%包含各种类型的数据丢失或损坏,以及其他问题。
我们对事件按类型进行分类后,确定了导致这些事件的原因。以下是一些需要注意的地方:
现在我们已经知道了主要宕机类别,以及有什么需要注意,下面我们将专门介绍如何获得高可用性。
第二步——通过冗余快速恢复——很不幸,这里是最应该注意的地方,但预防措施的投资回报率会很髙。接下来我们来探讨一些预防措施。
其实只要尽职尽责地做好一些应做的事情,就可以避免很多宕机。在分类整理宕机事件并追查导致宕机的根源时,我们还发现,很多宕机本来是有一些方法可以避免的。我们发现大部分宕机事件都可以通过全面的常识性系统管理办法来避免。以下是从我们的白皮书中摘录的指导性建议,在白皮书中有我们详细的分析结果。
团队成员是最重要的髙可用性资产,所以为恢复制定一个好的流程非常重要。拥有熟练技能、应变能力、训练有素的雇员,以及处理紧急事件的详细文档和经过仔细测试的流程,对从宕机中恢复有巨大的作用。但也不能完全依赖工具和系统,因为它们并不能理解实际情况的细微差别,有时候它们的行为在一般情况下是正确的,但在某些场景下却会是个灾难!
对宕机事件进行评估有助于提升组织学习能力,可以帮助避免未来发生相似的错误,但是不要对“事后反思”或“事后的调査分析”期待太髙。后见之明被严重曲解,并且一味想找到导致问题的唯一根源,这可熊会影响你的判断力。许多流行的方法,例如“五个为什么”,可能会被过度使用,导致一些人将他们的精力集中在找到唯一的替罪羊。很难去回顾我们解决的问题当时所处的状况,也很难理解真正的原因,因为原因通常是多方面的。因此,尽管事后反思可能是有用的,但也应该对结论有所保留。即使是我们给出的建议,也是基于长期研究导致宕机事件的原因以及如何预防它们所得,并且只是我们的观点而已。
这里我们要反复提醒:所有的宕机事件都是由多方面的失效联合在一起导致的。因此,可以通过利用合适的方法确保单点的安全来避免。整个链条必须要打断,而不仅仅是单个环节。例如,那些向我们求助恢复数据的人不仅遭受数据丢失(存储失效,DBA误操作等),同时还缺少一个可用的备份。
可以采用两种方法来为系统增加冗余:增加空余容量和重复组件。增加容量余量通常很简单——可以使用本章或前一章讨论的任何技术。一个提升可用性的方法是创建一个集群或服务器池,并使用负载均衡解决方案。如果一台服务器失效,其他服务器可以接管它的负载。有些人有意识地不使用组件的全部能力,这样可以保留一些“动态余量”来处理因为负载增加或组件失效导致的性能问题。
出于很多方面的考虑会需要冗余组件,并在主要组件失效时能有一个备件来随时替换。冗余组件可以是空闲的网卡、路由器或者硬盘驱动器——任何能想到的可能失效的东西。完全冗余MySQL服务器可能有点困难,因为一个服务器在没有数据时毫无用处。这意味着你必须确保备用服务器能够获得主服务器上的数据。共享或复制存储是一个比较流行的办法,但这真的是一个高可用性架构吗?让我们深入其中看看。
磁盘复制技术是另外一个获得跟SAN类似效果的方法。MySQL中最普遍使用的磁盘复制技术是DRBD,并结合Linux-HA项目中的工具使用(后面会介绍到)。
DRBD是一个以Linux内核模块方式实现的块级别同步复制技术。它通过网卡将主服务器的每个块复制到另外一个服务器的块设备上(备用设备),并在主设备提交块之前记录下来由于在备用DRBD设备上的写入必须要在主设备上的写入完成之前,因此备用设备的性能至少要和主设备一样,否则就会限制主设备的写入性能。同样,如果正在使用DRBD磁盘复制技术以保证在主设备失效时有一个可随时替换的备用设备,备用服务器的硬件应该跟主服务器的相匹配。带电池写缓存的RAID控制器对DRBD而言几乎是必需的,因为在没有这样的控制器时性能可能会很差。
如果主服务器失效,可以把备用设备提升为主设备。因为DRBD是在磁盘块层进行复制,而文件系统也可能会不一致。这意味着最好是使用日志型文件系统来做快速恢复。一旦设备恢复完成,MySQL还需要运行自身的恢复。原故障服务器恢复后,会与新的主设备进行同步,并假定自身角色为备用设备。
从如何实际地实现故障转移的角度来看,DRBD和SAN很相似:有一个热备机器,开始提供服务时会使用和故障机器相同的数据。最大的不同是,DRBD是复制存储——不是共享存储——所以当使用DRBD时,获得的是一份复制的数据,而SAN则是使用与故障机器同一物理设备上的相同数据副本。换句话说,磁盘复制技术的数据是冗余的,所以存储和数据本身都不会存在单点失效问题。这两种情况下,当启动备用机器时,MySQL服务器的缓存都是空的。相比之下,备库的缓存至少是部分预热的。
DRBD有一些很好的特性和功能,可以防止集群软件普遍会遇到的一些问题。一个典型的例子是“脑裂综合征”,在两个节点同时提升自己为主服务器时会发生这种问题。可以通过配置DRBD来防止这种事件发生。但是DRBD也不是一个能满足所有需求的完美解决方案。我们来看看它有哪些缺点:
我们倾向于只使用DRBD复制存放二进制日志的设备。如果主动节点失效,可以在被动节点上开启一个日志服务器,然后对失效主库的所有备库应用这些二进制日志。接下来可以选择其中一个备库提升为主库,以代替失效的系统。
讨论:主动一主动访问模式的共享存储怎么样?
在一个SAN、NAS或者集群文件系统上以主动一主动模式运行多个实例怎么样?MySQL不能这么做。因为MySQL并没有被设计成和其他MySQL实例同步对数据的访问,所以无法在同一份数据上开启多个MySQL实例。(如果在一份只读的静态数据上使用MyISAM,技术上是可行的,但我们还没有见过任何实际的应用。)
MySQL的一个名为ScaleDB的存储引擎在底层提供了操作共享存储的API,但我们还没有评估过,也没有见过任何生产环境使用。在mysql5.5发布时时它还是beta版。
当使用同步复制时,主库上的事务只有在至少一个备库上提交后才能认为其执行完成。这实现了两个目标:当服务器崩溃时没有提交的事务会丢失,并且至少有一个备库拥有实时的数据副本。大多数同步复制架构运行在主动-主动模式。这意味着每个服务器在任何时候都是故障转移的候选者,这使得通过冗余获得高可用性更加容易。
1.MySQLCluster
MySQL中的同步复制首先出现在MySQLCluster(NDBCluster)。它在所有节点上进行同步的主-主复制。这意味着可以在任何节点上写入;这些节点拥有等同的读写能力。每一行都是冗余存储的,这样即使丢失了一个节点,也不会丢失数据,并且集群仍然能提供服务。尽管MySQLCluster还不是适用于所有应用的完美解决方案,但正如我们在前一章提到的,在最近的版本中它做了非常快速的改进,现在已经拥有大量的新特性和功能:非索引数据的磁盘存储、增加数据节点能够在线扩展、使用ndbinfo表来管理集群、配置和管理集群的脚本、多线程操作、下推(push-down)的关联操作(现在称为自适应查询本地化)、能够处理BLOB列和很多列的表、集中式的用户管理,以及通过像memcached协议一样的NDBAPI来实现NoSQL访问。在下一个版本中将包含最终一致运行模式,包括为跨数据中心的主动-主动复制提供事务冲突检测和跨WAN解决方案。简而言之,MySQLCluster是一项引人注目的技术。
现在至少有两个为简化集群部署和管理提供附加产品的供应商:Oracle针对MySQLCluster的服务支持包含了MySQLClusterManager工具;Severalnines提供了ClusterControl工具,该工具还能够帮助部署和管理复制集群。
2.PerconaXtraDBCluster
PerconaXtraDBCluster是一个相对比较新的技术,基于已有的XtraDB(InnoDB)存储引擎增加了同步复制和集群特性,而不是通过一个新的存储引擎或外部服务器来实现。它是基于Galera(支持在集群中跨节点复制写操作)实现的,这是一个在集群中不同节点复制写操作的库。跟MySQLCluster类似,PerconaXtraDBCluster提供同步多主库复制,支持真正的任意节点写入能力,能够在节点失效时保证数据零丢失(持久性,ACID中的D),另外还提供高可用性,在整个集群没有失效的情况下,就算单个节点失效也没有关系。
Galera作为底层技术,使用一种被称为写入集合(write-set)复制的技术。写入集合实际上被作为基于行的二进制日志事件进行编码,目的是在集群中的节点间传输并进行更新,但是这不要求二进制日志是打开的。
PerconaXtraDBCluster的速度很快。跨节点复制实际上比没有集群还要快,因为在完全持久性模式下,写入远程RAM比写入本地磁盘要快。如果你愿意,可以选择通过降低每个节点的持久性来获得更好的性能,并且可以依赖于多个节点上的数据副本来获得持久性。NDB也是基于同样的原理实现的。集群在整体上的持久性并没有降低;仅仅是降低了本地节点的持久性。除此之外,还支持行级别的并发(多线程)复制,这样就可以利用多个CPU核心来执行写入集合。这些特性结合起来使得PerconaXtraDBCluster非常适合云计算环境,因为云计算环境中的CPU和磁盘通常比较慢。
在集群中通过设置auto_increment_offset和auto_increment_increment来实现自增键,以使节点间不会生成冲突的主键值。锁机制和标准InnoDB完全相同,使用的是乐观并发控制。当事务提交时,所有的更新是序列化的,并在节点间传输,同时还有一个检测过程,以保证一旦发生更新冲突,其中一些更新操作需要丢弃。这样如果许多节点同时修改同样的数据,可能产生大量的死锁和回滚。
PerconaXtraDBCluster只要集群内在线的节点数不少于“法定人数(quorum)”就能保证服务的高可用性。如果发现某个节点不属于“法定人数”中的一员,就会从集群中将其踢出。被踢出的节点在再次加入集群前必须重新同步。因此集群也无法处理“脑裂综合征”;如果出现脑裂则集群会停止服务。在一个只有两个节点的集群中,如果其中一个节点失效,剩下的一个节点达不到“法定人数”,集群将停止服务,所以实际上最少需要三个节点才能实现高可用的集群。
PerconaXtraDBCluster有许多优点:
当然我们也需要提及PerconaXtraDBCluster的一些缺点:
PerconaXtraDBCluster和Galera都处于其生命周期的早期,正在被快速地修改和改进。在mysql5.5发布时,正在进行或即将进行的改进包括群体行为、安全性、同步性、内存管理、状态转移等。未来还可以为离线节点执行诸如滚动式schema变更的操作。
复制管理器是使用标准MySQL复制来创建冗余的工具。尽管可以通过复制来改善可用性,但也有一些“玻璃天花板”会阻止MySQL当前版本的异步复制和半同步复制获得和真正的同步复制相同的结果。复制无法保证实时的故障转移和数据零丢失,也无法将所有节点等同对待。
复制管理器通常监控和管理三件事:应用和MySQL间的通信、MySQL服务器的健康度,以及MySQL服务器间的复制关系。它们既可以修改负载均衡的配置,也可以在必要的时候转移虚拟IP地址以使应用连接到合适的服务器上,还能够在一个伪集群中操纵复制以选择一个服务器作为写入节点。大体上操作并不复杂:只需要确定写入不会发送到一个还没有准备好提供写服务的服务器上,并保证当需要提升一台备库为主库时记录下正确的复制坐标。
这听起来在理论上是可行的,但我们的经验表明实际上并不总是能有效工作。事实上这非常糟糕,有些时候最好有一些轻量级的工具集来帮助从常见的故障中恢复并以很少的开销获得较髙的可用性。不幸的是,在mysql5.5时还没有听说任何一个好的工具集可以可靠地完成这一点。稍后我们会介绍两个复制管理器,其中一个很新,而另外一个则有很多问题。
我们发现很多人试图去写自己的复制管理器。他们常常会陷入很多人已经遭遇过的陷阱。自己去写一个复制管理器并不是好主意。异步组件有大量的故障形式,很多你从未亲身经历过,其中一些甚至无法理解,并且程序也无法适当处理,因此从这些异步组件中得到正确的行为相当困难,并且可能遭遇数据丢失的危险。事实上,机器刚开始出现问题时,由一个经验丰富的人来解决是很快的,但如果其他人做了一些错误的修复操作则可能导致问题更严重。
我们要提到的第一个复制管理器是MMM,本书的作者对于该工具集是否适用于生产环境部署的意见并不一致(尽管该工具的原作者也承认它并不可靠)。我们中有些人认为它在一些人工一故障转移模式下的场景中比较有用,而有些人甚至从不便用这个工具。我们的许多客户在自动一故障转移模式下使用该工具时确实遇到了许多严重的问题。它会导致健康的服务器离线,也可能将写入发送到错误的地点,并将备库移动到错误的坐标。有时混乱就接踵而至。
另外一个比较新一点的工具是YoshinoriMatsunobu的MHA工具集。它和MMM—样是一组脚本,使用相同的通用技术来建立一个伪集群,但它不是一个完全的替换者,它不会去做太多的事情,并且依赖干Pacemaker来转移虚拟IP地址。一个主要的不同是,MHA有一个很好的测试集,可以防止一些MMM遇到过的问题。除此之外,我们对该工具集还没有更多的认识,我们只和Yoshinori讨论过,但还没有真正使用过。
问题是许多用户不知道如何去证明自己有资格并评估复制“轮盘赌”是否适合他们。这有两个方面的原因。第一,他们并没有看到“玻璃天花板”,错误地认为一组虚拟IP地址、复制以及管理脚本能够实现真正的髙可用性。第二,他们低估了技术的复杂度,因此也低估了严重故障发生后从中恢复的难度。一些人认为他们能够使用基于复制的冗余技术,但随后他们可能会更希望选择一个有更强保障的简单系统。
其他一些类型的复制,例如DRBD或者SAN,也有它们的缺点——请不要认为我们将这些技术说得无所不能而把MySQL自身的复制贬得一团糟,那不是我们的本意。你可以为DRBD写出低质量的故障转移脚本,这很简单,就像为MySQL复制编写脚本一样。主要的区别是MySQL复制非常复杂,有很多非常细小的差别,并且不会阻止你干坏事。
在继续这个话题之前,我们先来定义一些术语。我们统一使用“故障转移(failover)”,有些人使用“回退”(fallback)表达同一意思。有时候也有人说“切换(switchover)”,以表明一次计划中的切换而不是故障后的应对措施。我们也会使用“故障恢复”来表示故障转移的反面。如果系统拥有故障恢复能力,故障转移就是一个双向过程:当服务器A失效,服务器B代替它,在修复服务器A后可以再替换回来。
你需要确定故障转移到底需要多快,也要知道在一次故障转移后替换一个失效组件应该多快。在你恢复系统耗尽的备件容量之前,会出现冗余不足,并面临额外风险。因此,拥有一个备件并不能消除即时替换失效组件的需求。构建一个新的备用服务器,安装操作系统,并复制数据的最新副本,可以多快呢?有足够的备用机器吗?你可能需要不止一台以上。
故障转移的缘由各不相同。我们已经讨论了其中的一些,因为负载均衡和故障转移在很多方面很相似,它们之间的分界线比较模糊。总的来说,我们认为一个完全的故障转移解决方案至少能够监控并自动替换组件。它对应用应该是透明的。负载均衡不需要提供这些功能。
在UNIX领域,故障转移常常使用HighAvailabilityLinux项目提供的工具来完成,该项目可在许多类UNIX系统上运行,而不仅仅是Linux。Linux-HA栈在最近几年明显多了很多新特性。现在大多数人认为Pacemaker是栈中的一个主要组件。Pacemaker替代了老的心跳工具。还有其他一些工具实现了IP托管和负载均衡功能。可以将它们跟DRBD和/或者LVS结合起来使用。
在一些应用中,故障转移和故障恢复需要尽量快速并具备原子性。即便这不是决定性的,不依靠那些不受你控制的东西也依然是个好主意,例如DNS变更或者应用程序配置文件。一些问题直到系统变得更加庞大时才会显现出来,例如当应用程序强制重启以及原子性需求出现时。
由于负载均衡和故障转移两者联系较紧密,有些硬件和软件是同时为这两个目的设计的,因此我们建议所选择的任何负载均衡技术应该都提供故障转移功能。这也是我们建议避免使用DNS和修改代码来做负载均衡的真实原因。如果为负载均衡采用了这些策略,就需要做一些额外的工作:当需要高可用性时,不得不重写受影响的代码。
以下小节讨论了一些比较普遍的故障转移技术。可以手动执行或使用工具来实现。
提升一台备库为主库,或者在一个主一主复制结构中调换主动和被动角色,这些都是许多MySQL故障转移策略很重要的一部分。正如本章之前提到的,我们不能认定自动化工具总能在所有的情况下做正确的事情——或者至少以我们的名誉担保没有这样的工具。
你不应该假定在发生故障时能够立刻切换到被动备库,这要看具体的工作负载。备库会重放主库的写入,但如果不用来提供读操作,就无法进行预热来为生产环境负载提供服务。如果希望有一个随时能承担读负载的备库,就要不断地“训练”它,既可以将其用于分担工作负载,也可以将生产环境的读査询镜像到备库上。我们有时候通过监听TCP流量,截取出其中的SELECT査询,然后在备库上重放来实现这个目的。PerconaToolkit中有一些工具可以做到这一点。
可以为需要提供特定服务的MySQL实例指定一个逻辑IP地址。当MySQL实例失效时,可以将IP地址转移到另一台MySQL服务器上。这和我们在前一章提到的思想本质上是相同的,唯一的不同是现在是用于故障转移,而不是负载均衡。
以下是它的一些不足之处:
浮动IP地址和IP接管能够很好地应付彼此临近(也就是在同一子网内)的机器之间的故障转移。但是最后需要提醒的是,这种策略并不总是万无一失,还取决于网络硬件等因素。
讨论:等待更新扩散
经常有这种情况,在某一层定义了一个冗余后,需要等待低层执行一些改变。在本章前面的篇幅里,我们指出通过DNS修改服务器是一个很脆弱的解决方案,因为DNS的更新扩散速度很慢,改变IP地址可给予你更多的控制,但在一个LAN中的IP地址同样依赖于更低层——ARP——来扩散更新。
可以使用代理、端口转发、网络地址转换(NAT)或者硬件负载均衡来实现故障转移和故障恢复。这些都是很好的解决方案,不像其他方法可能会引入一些不确定性(所有系统组件认同哪一个是主库吗?它能够及时并原子地更改吗?),它们是控制应用和服务器间连接的中枢。但是,它们自身也引入了单点失效,需要准备冗余来避免这个问题。
使用这样的解决方案,你可以将一个远程数据中心设置成看起来好像和应用在同一个网络里。这样就可以使用诸如浮动IP地址这样的技术让应用和一个完全不同的数据中心开始通信。你可以配置每个数据中心的每台应用服务器,通过它自己的中间件连接,将流量路由到活跃数据中心的机器上。图12-1描述了这种配置。
如果活跃数据中心安装的MySQL彻底崩溃了,中间件可以路由流量到另外一个数据中心的服务器池中,应用无须知道这个变化。
这种配置方法的主要缺点是在一个数据中心的Apache服务器和另外一个数据中心的MySQL服务器之间的延迟比较大。为了缓和这个问题,可以把Web服务器设置为重定向模式。这样通信都会被重定向到放置活跃MySQL服务器的数据中心。还可以使用HTTP代理来实现这一目标。
图12-1显示了如何使用代理来连接MySQL服务器,也可以将这个方法和许多别的中间件架构结合在一起,例如LVS和硬件负载均衡器。
有时候让应用来处理故障转移会更简单或者更加灵洁。例如,如果应用遇到一个错误,这个错误外部观察者正常情况下是无法察觉的,例如关于数据库损坏的错误日志信息,那么应用可以自己来处理故障转移过程。
虽然把故障转移处理过程整合到应用中看起来比较吸引人,但可能没有想象中那么有效。大多数应用有许多组件,例如cron任务、配置文件,以及用不同语言编写的脚本。将故障转移整合到应用中可能导致应用变得太过笨拙,尤其是当应用增大并变得更加复杂时。
但是将监控构建到应用中是一个好主意,当需要时,能够立刻开始故障转移过程。应用应该也能够管理用户体验,例如提供降级功能,并显示给用户合适的信息。
本章和前面两章提及的话题常常被放在一起讨论:复制、可扩展性,以及高可用性。我们已经尽量将它们独立开来,因为这有助于理清这些话题的不同之处。那么这三章有哪些关联之处呢?
在其应用增长时,人们一般希望从他们的数据库中知道三件事:
为了达到这些目的,人们常常首先增加冗余。结合故障转移机制,通过最小化MTTR来提供高可用性。这些冗余还提供了空闲容量,可以为更多的负载提供服务。
当然,除了必要的资源外,还必须要有一份数据副本。这有助于在损失服务器时避免丢失数据,从而增强持久性。生成数据副本的唯一办法是通过某种方法进行复制。不幸的是,数据副本可能会引入不一致。处理这个问题需要在节点间协调和通信。这给系统带来了额外的负担;这也是系统或多或少存在扩展性问题的原因。
数据副本还需要更多的资源(例如更多的硬盘驱动器,更多的RAM),这会增加开销。有一个办法可以减少资源消耗和维护一致性的开销,就是为数据分区(分片)并将每个分片分发到特定的系统中。这可以减少需要复制的重复数据的次数,并从资源冗余中分离数据冗余。
最后,需要选择一个对你和应用有意义的策略。决定选择一个完全的端到端(end-to-end)髙可用性策略并不能通过简单的经验法则来处理,但我们给出的一些粗略的指引也许会有所帮助。
如果需要很强的可用性保证,就需要诸如MySQLCluster、PerconaXtraDBCluster,或者Clustrix这样的集群产品。如果能容忍在故障转移过程中稍微慢一些,标准的MySQL复制也是个很好的选择。要谨慎使用自动化故障转移机制;如果没有按照正确的方式工作,它们可能会破坏数据。
或者也可以将应用放到云中。为什么不呢?这样难道不是能够立刻获得髙可用性和无限扩展能力吗?云厂商过多,这里就不做讨论。