2025年1月5日 08:42 周日第13章 云端的MySQL
许多人在云中使用MySQL,有时候规模还非常庞大,这并不奇怪。从我们的经验来看,大多数人使用的是Amazon Web Services平台(AWS):特别是Amazon的弹性计算云(Elastic Compute Cloud,EC2),弹性块存储(Elastic Block Store,EBS),以及更小众的关系数据库服务(Relational Database Service,RDS)。
为了便于讨论MySQL在云中的应用,可以将其粗略分为两类。
IaaS(基础设施即服务)
Iaas是用于托管自有的MySQL服务器的云端基础架构。可以在云端购买虚拟的服务器资源来安装运行MySQL实例。也可以根据需求随意配置MySQL和操作系统,但没有权限也无法看到处于底层的物理硬件设备。
DBaaS(数据库即服务)
MySQL本身作为由云端管理的资源。用户需要先收到MySQL服务器的访问许可(通常是一个连接串)才能访问。也可以配置一些MySQL选项,但没有权限去控制或查看底层的操作系统或虚拟服务器实例。例如 Amazon运行MySQL的RDS。其中一些服务器并非真的使用MySQL,但它们能兼容MySQL协议和查询语言。
我们讨论的重点主要集中在第一类:云托管平台,例如AWS、Rackspace Cloud以及Joyent(1)。有许多很好的资源介绍如何部署和管理MySQL及其运行所需要的资源,并且也有非常多的平台来完全满足这样的需求,所以我们不会展示代码样例或讨论具体的操作技术。因此,本章关注的重点是,在云端运行MySQL还是在传统服务器上部署MySQL,它们在最终经济上和性能特性上的关键区别是什么。我们假定你对云计算很熟悉。这里不是对云计算概念的简单介绍,我们的目的只是帮助那些还不熟悉在云端部署MySQL的用户在使用时避免一些可能遇到的陷阱。
一般来说,MySQL能够在云中很好地运行。在云中运行MySQL并不比在其他平台困难,但有一些非常重要的差别。你需要注意这些差别并据此设计应用和架构来获得好的效果。某些场景下在云端托管MySQL并不是非常适合,有时候则很适合,但大多数时候云仅仅是另外一个部署平台而已。
云是一个部署平台,而不是一种架构,理解这一点很重要。架构会受平台的影响,但平台和架构明显不同。如果你把架构和平台搞混了,就可能会做出不合适的选择而给以后带来麻烦。这也正是我们要花时间讨论云端的MySQL到底有什么不同的原因。
13.1 云的优点、缺点和相关误解
#
云计算有许多优点,但很少是为MySQL特别设计。有一些书籍已经介绍了相关的话题(2),这里我们不再赘述。不过我们会列出一些比较重要的条目供参考,因为接下来会讨论到云计算的缺点,我们不希望你认为我们是在过分苛求云计算。
- 云是一种将基础设施外包出去无须自己管理的方法。你不需要寻找供应商购买硬件,也不需要维护和供应商之间的关系,更无须替换失效的硬盘驱动器等。
- 云一般是按照即用即付的方式支付,可以把前期的大量资本支出转换为持续的运营成本。
- 随着供应商发布新的服务和成本降低,云提供的价值越来越大。你自己无须做任何事情(例如升级服务器),就可以从这些提升中获益;随着时间推移你会很容易地获得更多更好的选择并且费用更低。
- 云能够帮助你轻松地准备好服务器和其他资源,在用完后直接将其关闭,而无须关注怎么处理它们,或者怎么卖掉它们收回成本。
- 云代表了对基础设施的另一种思考方式——作为通过API来定义和控制的资源——支持更多的自动化操作。从“私有云”中也可以获得这些好处。
当然,不是所有跟云相关的东西都是好的。这里有一些缺点可能会构成挑战(在本章稍后部分我们会列出MySQL特有的缺点)。
- 资源是共享并且不可预测的,实际上你可以获得比你支付的更多的资源。这听起来很不错,但却导致容量规划很难做。如果你在不知情的情况下获得了比理应享受到的更多的计算资源,那么就存在这样的风险:别人也许会索要他们应得的资源,这会使你的应用性能退化到应有的水平。一般来说,很难确切地知道本来应该得到多少(资源),大多数云托管服务提供商不会对此给出确切的答案。
- 无法保证容量和可用性。你可能以为还可以获得新实例,但如果供应商已经超额销售了呢?这在有很多共享资源的情况下会发生,同样也会发生在云中。
- 虚拟的共享资源导致排查故障更加困难,特别是在无法访问底层物理硬件的情况下无法检查并弄清到底发生了什么。例如,我们曾经看到过一些系统的iostat显示的I/O很正常或者vmstat显示的CPU很正常,而当实际衡量完成一个任务需要的时间时,资源却被系统上的其他东西严重占用了。如果在云平台上出现了性能问题,尤其需要去仔细地分析检测。如果对此并不擅长,可能就无法确认到底是底层系统性能差,还是你做了什么事情导致应用出现不合理的资源需求。
总的来说,云平台上对性能、可用性和容量的透明性和控制力都有所下降。最后,还有一些对云的误解需要记住。
云天生具备更好的可扩展性
应用、云的架构,以及管理云服务的组织是不是都是可扩展的。云并不是天生可扩展的,云也仅仅是云而已,选择一个可扩展的平台并不能自动使应用变得可扩展。的确,如果云托管提供商没有超售,那么你可以根据需求来购买资源,但在需要时能够获得资源仅仅是扩展性的一个方面而已。
云可以自动改善甚至保证可用时间
一般来说,个别在云端托管的服务器比那些经过良好设计的专用基础设施更容易发生故障或运行中断。但是许多人并没有意识到这一点。例如,有人这样写道:“我们将基础设施升级到基于云构建的系统以保证100%的可用时间和可扩展性”。而就在这之前AWS遭受了两次大规模的运行中断故障,导致很大一部分用户受影响。好的架构能够用不可靠的组件设计出可靠的系统,但通常更可靠的基础设施可以获得更高的可用性。(当然不可能有100%的可用时间的系统。)
另一方面,购买云计算服务,实际上是购买一个由专家构建的平台。他们已经考虑了许多底层的东西,这意味着你可以更专注于上层工作。如果构建自己的平台而对其中的那些细枝末节并不精通,就可能犯一些初学者的错误,早晚会导致一些宕机时间。从这一点来说,云计算能够帮助改善可用时间。
云是唯一能提供[这里填入任意的优点]的东西
事实上,许多云的优点是继承自构建云平台所用到的技术,即使不使用云也可以获得(3)。例如,通过管理得当的虚拟化和容量规划,可以像任何一个云平台那样简单快速地启动(spin up)一台新的机器。完全没必要专门使用云来做到这一点。
云是一个“银弹”(silver bullet)
虽然大部分人会认为这很荒谬,但确实有人会这么认为。实际上完全没有这回事。
无可否认,云计算提供了独特的优点,随着时间的推移,关于云计算是什么,以及它们在什么情况下会有帮助,我们会获得更多的共识。但有一点非常肯定:它是全新的,我们现在所做的任何预测都未必经得起时间的考验。我们会在本书讨论相对安全的部分,而将剩下的部分留给读者讨论。
13.2 MySQL在云端的经济价值
#
在一些场景下云托管比传统的服务器部署方式更经济。以我们的经验来看,云托管比较适合尚处于初级阶段的企业,或者那些持续接触新概念并且本质上是以适用为主的企业,例如移动应用开发者或游戏开发者。这些技术的市场随着移动计算的扩张出现了爆炸式增长,并且仍然是快速发展的领域。在许多情况下,成功的因素并不为开发者所控制,例如口口相传的推荐或者恰逢重要国际事件的时机。
我们已经帮助很多公司在云中构建移动应用、社交网络以及游戏应用。其中一个他们大量使用的策略是尽可能又快又便宜地开发和发布应用。如果一个应用碰巧变得流行了,公司将投入资源扩大其规模;否则就会很快终结这些应用。一些公司构建并发布的应用的生命周期甚至只有几个星期,在这样的环境下,可以毫不犹豫地选择云托管。
如果是一个小规模的公司,可能无法提供足够的硬件来自建数据中心以满足一个非常流行的Facebook应用的发展曲线。我们也协助过一些大型的Facebook应用进行扩展,它们能够以今人惊讶的速度增长——有时甚至会快到让一个主机托管公司耗尽资源。更为严重的是,这些应用的增长是完全无法预测的;它们可能只有极少量的用户(也可能突然有了爆炸性的用户数量增长)。我们在数据中心和云中都遇到过这样的应用。如果是一个小公司,云可以帮你避免前期快速注入大量的资金来获得更快更大规模的风险。
云的另一种潜在的大用途是运行不是很重要的基础设施,例如集成环境、开发测试平台,以及评估环境。假设部署周期是两个星期。你会每天每个小时都测试部署一次,还是只在项目最后的冲刺时测试?许多用户只是偶尔需要筹划和部署测试环境。在这种场景下,云可以帮助节约不少钱。
以下是我们使用云的两种方式。第一个是作为我们对技术职员面试的一部分,我们会询问如何解决一些实际的问题。我们使用AMI(Amazon Machine Images)来模拟一些被“破坏”的机器,然后让求职者登录并在服务器上执行一系列任务。我们不必开放他们到内部网络的授权,这种方案显然要方便得多。另一个是作为新项目的工作平台和开发服务器。有一个这样的项目已经在一台云端开发服务器上运行了数个月,而花费不足一美元!这在我们自己的基础设施上是不可能做到的。单是发送一封邮件给系统管理员申请开发服务器的时间价值就不止一美元。
但是另一方面,云托管对于长期项目而言可能会更加昂贵。如果打算长远地使用云,就需要花时间来计算一下(它是否划算)。除了猜想未来的创新能给云计算和商用硬件带来什么,还需要做基准测试以及一个完整的总体持有成本(TCO)账单。为了理清事情的本质并考虑全面所有相关的细节,你需要把所有的事情最终归结为一个数字:每美元的业务交易数。事情变化得太快,所以我们将这个留给读者思考。
13.3 云中的MySQL的可扩展性和高可用性
#
正如我们之前提到的,MySQL并不会在云端自动变得更具扩展性。事实上,如果机器的性能较差,会导致过早使用横向扩展策略。况且云托管服务器相比专用的硬件可靠性和可预测性要更差些,所以想在云端获得高可用性需要更多的创新。
但是总的来说,在云端中扩展MySQL和在其他地方扩展没有太多的差别。最大的不同就是按需提供服务器的能力。但是也有某些限制会导致扩展和高可用实现起来有点麻烦,至少在有些云环境中是这样的。例如,在AWS云平台中,无法使用类似虚拟IP地址的功能来完成快速原子故障转移。像这种对资源的有限控制意味着你需要使用其他办法,例如代理。(ScaleBase也值得去看看。)
云另外一个迷惑人的地方是梦想中的自动扩展——就是根据需求的增加或减少来启动或关闭实例。尽管对于诸如Web服务器这样的无状态部分是可行的,但对于数据库服务器而言则很难做到,因为它是有状态的。对于一些特定的场景,例如以读为主的应用,可以通过增加备库的方式来获得有限的自动扩展(4),但这并不是一个通用的解决方案。实际上,虽然许多应用在Web层使用了自动扩展,但MySQL并不具备在一个无共享(Shared Nothing)集群中的对等角色服务器之间迁移的能力。你可以通过分片架构来自动重新分片并自动增长或收缩(5),但MySQL本身是无法自动扩展的。
事实上,因为数据库通常是一个应用系统中主要或唯一的有状态并且持久化的组件,所以把应用服务迁移到云端是很普遍的事情,因为除数据库之外的所有部分都可以从云中收益——Web服务器、工作队列服务器、缓存等——而MySQL只需要处理剩下的东西。毕竟,数据库并非世界的中心。如果应用系统其他部分获得的好处,超过了让MySQL运行得足够好而投入的额外开销和必需的工作量,那这不是一个是否会发生的问题,而是怎么发生的问题。要回答这个问题,最好先了解你在云中可能碰到的额外的挑战。这些通常围绕着数据库服务器的可用资源。
13.4 四种基础资源
#
MySQL需要四种基础资源来完成工作:CPU周期、内存、I/O,以及网络。这四种资源的特性和重要程度在不同的云平台上各不相同。可以通过了解它们的不同之处和对MySQL的影响,以决定是否选择在云中托管MySQL。
...
2025年1月5日 08:42 周日第12章 高可用性
本章将讲述我们提到的复制、可扩展性以及高可用性三个主题中的第三个。归根结底,高可用性实际上意味着“更少的宕机时间”。然而糟糕的是,高可用性经常和其他相关的概念混淆,例如冗余、保障数据不丢失,以及负载均衡。我们希望之前的两章已经为清楚地理解高可用性做了足够的铺垫。跟其他两章一样,这一章也不仅仅是关注高可用性的内容,一些相关的话题也会综合阐述。
12.1 什么是高可用性
#
高可用性实际上有点像神秘的野兽。它通常以百分比表示,这本身也是一种暗示:高可用性不是绝对的,只有相对更高的可用性。100%的可用性是不可能达到的。可用性的“9”规则是表示可用性目标最普遍的方法。你可能也知道,“5个9”表示99.999%的正常可用时间。换句话说,每年只允许5分钟的宕机时间。对于大多数应用这已经是令人惊叹的数字,尽管还有一些人试图获得更多的“9”。
每个应用对可用性的需求各不相同。在设定一个可用时间的目标之前,先问问自己,是不是确实需要达到这个目标。可用性每提高一点,所花费的成本都会远超之前;可用性的效果和开销的比例并不是线性的。需要保证多少可用时间,取决于能够承担多少成本。高可用性实际上是在宕机造成的损失与降低宕机时间所花费的成本之间取一个平衡。换句话说,如果需要花大量金钱去获得更好的可用时间,但所带来的收益却很低,可能就不值得去做。总的来说,应用在超过一定的点以后追求更高的可用性是非常困难的,成本也会很高,因此我们建议设定一个更现实的目标并且避免过度设计。幸运的是,建立2个9或3个9的可用时间的目标可能并不困难,具体情况取决于应用。
有时候人们将可用性定义成服务正在运行的时间段。我们认为可用性的定义还应该包括应用是否能以足够好的性能处理请求。有许多方法可以让一个服务器保持运行,但服务并不是真正可用。对一个很大的服务器而言,重启MySQL之后,可能需要几个小时才能充分预热以保证查询请求的响应时间是可以接受的,即使服务器只接收了正常流量的一小部分也是如此。
另一个需要考虑的问题是,即使应用并没有停止服务,但是否可能丢失了数据。如果服务器遭遇灾难性故障,可能多少都会丢失一些数据,例如最近已经写入(最新丢失的)二进制日志但尚未传递到备库的中继日志中的事务。你能够容忍吗?大多数应用能够容忍;因为替代方案大多非常昂贵且复杂,或者有一些性能开销。例如,可以使用同步复制,或是将二进制日志放到一个通过DRBD进行复制的设备上,这样就算服务器完全失效也不用担心丢失数据。(但是整个数据中心也有可能会掉电。)
一个良好的应用架构通常可以降低可用性方面的需求,至少对部分系统而言是这样的,良好的架构也更容易做到高可用。将应用中重要和不重要的部分进行分离可以节约不少工作量和金钱,因为对于一个更小的系统改进可用性会更容易。可以通过计算“风险敞口(risk exposure)”,将失效概率与失效代价相乘来确认高优先级的风险。画一个简单的风险计算表,以概率、代价和风险敞口作为列,这样很容易找到需要优先处理的项目。
在前一章我们通过讨论如何避免导致糟糕的可扩展性的原因,来推出如何获得更好的可扩展性。这里也会使用相似的方法来讨论可用性,因为我们相信,理解可用性最好的方法就是研究它的反面——宕机时间。接下来的小节我们会讨论为什么会出现宕机。
12.2 导致宕机的原因
#
我们经常听到导致数据库宕机最主要的原因是编写的SQL查询性能很差,真的是这样吗?2009年我们决定分析我们客户的数据库所遇到的问题,以找出那些真正引起宕机的问题,以及如何避免这些问题(1)。结果证实了一些我们已有的猜想,但也否定了一些(错误的)认识,我们从中学到了很多。
我们首先对宕机事件按表现方式而非导致的原因进行分类。一般来说,“运行环境”是排名第一的宕机类别,大约35%的事件属于这一类。运行环境可以看作是支持数据库服务器运行的系统和资源集合,包括操作系统、硬盘以及网络等。性能问题紧随其后,也是约占35%;然后是复制,占20%;最后剩下的10%包含各种类型的数据丢失或损坏,以及其他问题。
我们对事件按类型进行分类后,确定了导致这些事件的原因。以下是一些需要注意的地方:
- 在运行环境的问题中,最普遍的问题是磁盘空间耗尽。
- 在性能问题中,最普遍的宕机原因确实是运行很糟糕的SQL,但也不一定都是这个原因,比如也有很多问题是由于服务器Bug或错误的行为导致的。
- 糟糕的Schema和索引设计是第二大影响性能的问题。
- 复制问题通常由于主备数据不一致导致。
- 数据丢失问题通常由于DROP TABLE的误操作导致,并总是伴随着缺少可用备份的问题。
复制虽然常被人们用来改善可用时间,但却也可能导致宕机。这主要是由于不正确的使用导致的,即便如此,它也阐明了一个普遍的情况:许多高可用性策略可能会产生反作用,我们会在后面讨论这个话题。
现在我们已经知道了主要宕机类别,以及有什么需要注意,下面我们将专门介绍如何获得高可用性。
12.3 如何实现高可用性
#
可以通过同时进行以下两步来获得高可用性。首先,可以尝试避免导致宕机的原因来减少宕机时间。许多问题其实很容易避免,例如通过适当的配置、监控,以及规范或安全保障措施来避免人为错误。第二,尽量保证在发生宕机时能够快速恢复。最常见的策略是在系统中制造冗余,并且具备故障转移能力。这两个维度的高可用性可以通过两个相关的度量来确定:平均失效时间(MTBF)和平均恢复时间(MTTR)。一些组织会非常仔细地追踪这些度量值。
第二步——通过冗余快速恢复——很不幸,这里是最应该注意的地方,但预防措施的投资回报率会很高。接下来我们来探讨一些预防措施。
12.3.1 提升平均失效时间(MTBF)
#
其实只要尽职尽责地做好一些应做的事情,就可以避免很多宕机。在分类整理宕机事件并追查导致宕机的根源时,我们还发现,很多宕机本来是有一些方法可以避免的。我们发现大部分宕机事件都可以通过全面的常识性系统管理办法来避免。以下是从我们的白皮书中摘录的指导性建议,在白皮书中有我们详细的分析结果。
- 测试恢复工具和流程,包括从备份中恢复数据。
- 遵从最小权限原则。
- 保持系统干净、整洁。
- 使用好的命名和组织约定来避免产生混乱,例如服务器是用于开发还是用于生产环境。
- 谨慎安排升级数据库服务器。
- 在升级前,使用诸如Percona Toolkit中的pt-upgrade之类的工具仔细检查系统。
- 使用InnoDB并进行适当的配置,确保InnoDB是默认存储引擎。如果存储引擎被禁止,服务器就无法启动。
- 确认基本的服务器配置是正确的。
- 通过skip_name_resolve禁止DNS。
- 除非能证明有效,否则禁用查询缓存。
- 避免使用复杂的特性,例如复制过滤和触发器,除非确实需要。
- 监控重要的组件和功能,特别是像磁盘空间和RAID卷状态这样的关键项目,但也要避免误报,只有当确实发生问题时才发送告警。
- 尽量记录服务器的状态和性能指数,如果可能就尽量久地保存。
- 定期检查复制完整性。
- 将备库设置为只读,不要让复制自动启动。
- 定期进行查询语句审查。
- 归档并清理不需要的数据。
- 为文件系统保留一些空间。在GNU/Linux中,可以使用-m选项来为文件系统本身保留空间。还可以在LVM卷组中留下一些空闲空间。或者,更简单的方法,仅仅创建一个巨大的空文件,在文件系统快满时,直接将其删除。(2)
- 养成习惯,评估和管理系统的改变、状态以及性能信息。
我们发现对系统变更管理的缺失是所有导致宕机的事件中最普遍的原因。典型的错误包括粗心的升级导致升级失败并遭遇一些Bug,或是尚未测试就将Schema或查询语句的更改直接运行到线上,或者没有为一些失败的情况制定计划,例如达到了磁盘容量限制。另外一个导致问题的主要原因是缺少严格的评估,例如因为疏忽没有确认备份是否是可以恢复的。最后,可能没有正确地监控MySQL的相关信息。例如缓存命中率报警并不能说明出现问题,并且可能产生大量的误报,这会使监控系统被认为不太有用,于是一些人就会忽略报警。有时候监控系统失效了,甚至没人会注意到,直至你的老板质问你,“为什么Nagios没有告诉我们磁盘已经满了”。
12.3.2 降低平均恢复时间(MTTR)
#
之前提到,可以通过减少恢复时间来获得高可用性。事实上,一些人走得更远,只专注于减少恢复时间的某个方面:通过在系统中建立冗余来避免系统完全失效,并避免单点失效问题。
在降低恢复时间上进行投资非常重要,一个能够提供冗余和故障转移能力的系统架构,则是降低恢复时间的关键环节。但实现高可用性不单单是一个技术问题,还有许多个人和组织的因素。组织和个人在避免宕机和从宕机事件中恢复的成熟度和能力层次各不相同。
团队成员是最重要的高可用性资产,所以为恢复制定一个好的流程非常重要。拥有熟练技能、应变能力、训练有素的雇员,以及处理紧急事件的详细文档和经过仔细测试的流程,对从宕机中恢复有巨大的作用。但也不能完全依赖工具和系统,因为它们并不能理解实际情况的细微差别,有时候它们的行为在一般情况下是正确的,但在某些场景下却会是个灾难!
对宕机事件进行评估有助于提升组织学习能力,可以帮助避免未来发生相似的错误,但是不要对“事后反思”或“事后的调查分析”期待太高。后见之明被严重曲解,并且一味想找到导致问题的唯一根源,这可能会影响你的判断力(3)。许多流行的方法,例如“五个为什么”,可能会被过度使用,导致一些人将他们的精力集中在找到唯一的替罪羊。很难去回顾我们解决的问题当时所处的状况,也很难理解真正的原因,因为原因通常是多方面的。因此,尽管事后反思可能是有用的,但也应该对结论有所保留。即使是我们给出的建议,也是基于长期研究导致宕机事件的原因以及如何预防它们所得,并且只是我们的观点而已。
这里我们要反复提醒:所有的宕机事件都是由多方面的失效联合在一起导致的。因此,可以通过利用合适的方法确保单点的安全来避免。整个链条必须要打断,而不仅仅是单个环节。例如,那些向我们求助恢复数据的人不仅遭受数据丢失(存储失效,DBA误操作等),同时还缺少一个可用的备份。
...
2025年1月5日 08:42 周日第11章 可扩展的MySQL
本章将展示如何构建一个基于MySQL的应用,并且当规模变得越来越庞大时,还能保证快速、高效并且经济。
有些应用仅仅适用于一台或少数几台服务器,那么哪些可扩展性建议是和这些应用相关的呢?大多数人从不会维护超大规模的系统,并且通常也无法效仿在主流大公司所使用的策略。本章会涵盖这一系列的策略。我们已经建立或者协助建立了许多应用,包括从单台或少量服务器的应用到使用上千台服务器的应用。选择一个合适的策略能够大大地节约时间和金钱。
MySQL经常被批评很难进行扩展,有些情况下这种看法是正确的,但如果选择正确的架构并很好地实现,就能够非常好地扩展MySQL。但是扩展性并不是一个很好理解的主题,所以我们先来理清一些容易混淆的地方。
11.1 什么是可扩展性
#
人们常常把诸如“可扩展性”、“高可用性”以及“性能”等术语在一些非正式的场合用作同义词,但事实上它们是完全不同的。在第3章已经解释过,我们将性能定义为响应时间。我们也可以很精确地定义可扩展性,稍后将完整讨论。简要地说,可扩展性表明了当需要增加资源以执行更多工作时系统能够获得划算的等同提升(equal bang for the buck)的能力。缺乏扩展能力的系统在达到收益递减的转折点后,将无法进一步增长。
容量是一个和可扩展性相关的概念。系统容量表示在一定时间内能够完成的工作量(1),但容量必须是可以有效利用的。系统的最大吞吐量并不等同于容量。大多数基准测试能够衡量一个系统的最大吞吐量,但真实的系统一般不会使用到极限。如果达到最大吞吐量,则性能会下降,并且响应时间会变得不可接受地大且非常不稳定。我们将系统的真实容量定义为在保证可接受的性能的情况下能够达到的吞吐量。这就是为什么基准测试的结果通常不应该简化为一个单独的数字。
容量和可扩展性并不依赖于性能。以高速公路上的汽车来类比的话:
- 性能是汽车的时速。
- 容量是车道数乘以最大安全时速。
- 可扩展性就是在不减慢交通的情况下,能增加更多车和车道的程度。
在这个类比中,可扩展性依赖于多个条件:换道设计得是否合理、路上有多少车抛锚或者发生事故,汽车行驶速度是否不同或者是否频繁变换车道——但一般来说和汽车的引擎是否强大无关。这并不是说性能不重要,性能确实重要,只是需要指出,即使系统性能不是很高也可以具备可扩展性。
从较高层次看,可扩展性就是能够通过增加资源来提升容量的能力。
即使MySQL架构是可扩展的,但应用本身也可能无法扩展,如果很难增加容量,不管原因是什么,应用都是不可扩展的。之前我们从吞吐量方面来定义容量,但同样也需要从较高的层次来看待容量问题。从有利的角度来看,容量可以简单地认为是处理负载的能力,从不同的角度来考虑负载很有帮助。
数据量
应用所能累积的数据量是可扩展性最普遍的挑战,特别是对于现在的许多互联网应用而言,这些应用从不删除任何数据。例如社交网站,通常从不会删除老的消息或评论。
用户量
即使每个用户只有少量的数据,但在累计到一定数量的用户后,数据量也会开始不成比例地增长且速度快过用户数增长。更多的用户意味着要处理更多的事务,并且事务数可能和用户数不成比例。最后,大量用户(以及更多的数据)也意味着更多复杂的查询,特别是查询跟用户关系相关时(用户间的关联数可以用N×(N−1)来计算,这里N表示用户数)。
用户活跃度
不是所有的用户活跃度都相同,并且用户活跃度也不总是不变的。如果用户突然变得活跃,例如由于增加了一个吸引人的新特性,那么负载可能会明显提升。用户活跃度不仅仅指页面浏览数,即使同样的页面浏览数,如果网站的某个需要执行大量工作的部分变得流行,也可能导致更多的工作。另外,某些用户也会比其他用户更活跃:他们可能比一般人有更多的朋友、消息和照片。
相关数据集的大小
如果用户间存在关系,应用可能需要在整个相关联用户群体上执行查询和计算,这比处理一个一个的用户和用户数据要复杂得多。社交网站经常会遇到由那些人气很旺的用户组或朋友很多的用户所带来的挑战(2)。
11.1.1 正式的可扩展性定义
#
有必要探讨一下可扩展性在数学上的定义了,这有助于在更高层次的概念上清晰地理解可扩展性。如果没有这样的基础,就可能无法理解或精确地表达可扩展性。不过不用担心,这里不会涉及高等数学,即使不是数学天才,也能够很直观地理解它。
关键是之前我们使用的短语:“划算的等同提升(equal bang for the buck)”。另一种说法是,可扩展性是当增加资源以处理负载和增加容量时系统能够获得的投资产出率(ROI)。假设有一个只有一台服务器的系统,并且能够测量它的最大容量,如图11-1所示。
图11-1:一个只有一台服务器的系统
假设现在我们增加一台服务器,系统的能力加倍,如图11-2所示。
图11-2:一个线性扩展的系统能由两台服务器获得两倍容量
这就是线性扩展。我们增加了一倍的服务器,结果增加了一倍的容量。大部分系统并不是线性扩展的,而是如图11-3所示的扩展方式。
图11-3:一个非线性扩展的系统
大部分系统都只能以比线性扩展略低的扩展系数进行扩展。越高的扩展系数会导致越大的线性偏差。事实上,多数系统最终会达到一个最大吞吐量临界点,超过这个点后增加投入反而会带来负回报——继续增加更多工作负载,实际上会降低系统的吞吐量。(3)
这怎么可能呢?这些年产生了许多可扩展性模型,它们有着不同程度的良好表现和实用性。我们这里所讲的可扩展性模型是基于某些能够影响系统扩展的内在机制。这就是Neil J. Gunther博士提出的通用可扩展性定律(Universal Scalability Law,USL)。Gunther博士将这些详尽地写到了他的书中,包括Guerrilla Capacity Planning (Springer)。这里我们不会深入到背后的数学理论中,如果你对此感兴趣,他撰写的书籍以及由他的公司Performance Dynamics提供的训练课程可能是比较好的资源。(4)
简而言之,USL说的是线性扩展的偏差可通过两个因素来建立模型:无法并发执行的一部分工作,以及需要交互的另外一部分工作。为第一个因素建模就有了著名的Amdahl定律,它会导致吞吐量趋于平缓。如果部分任务无法并行,那么不管你如何分而治之,该任务至少需要串行部分的时间。
增加第二个因素——内部节点间或者进程间的通信——到Amdahl定律就得出了USL。这种通信的代价取决于通信信道的数量,而信道的数量将按照系统内工作者数量的二次方增长。因此最终开销比带来的收益增长得更快,这是产生扩展性倒退的原因。图11-4阐明了目前讨论到的三个概念:线性扩展、Amdahl扩展,以及USL扩展。大多数真实系统看起来更像USL曲线。
图11-4:线性扩展、AmdahI扩展以及USL扩展定律
USL可以应用于硬件和软件领域。对于硬件,横轴表示硬件的数量,例如服务器数量或CPU数量。每个硬件的工作量、数据大小以及查询的复杂度必须保持为常量(5)。对于软件,横轴表示并发度,例如用户数或线程数。每个并发的工作量必须保持为常量。
有一点很重要,USL并不能完美地描述真实系统,它只是一个简化模型。但这是一个很好的框架,可用于理解为什么系统增长无法带来等同的收益。它也揭示了一个构建高可扩展性系统的重要原则:在系统内尽量避免串行化和交互。
可以衡量一个系统并使用回归来确定串行和交互的量。你可以将它作为容量规划和性能预测评估的最优上限值。也可以检查系统是怎么偏离USL模型的,将其作为最差下限值以指出系统的哪一部分没有表现出它应有的性能。这两种情况下,USL给出了一个讨论可扩展性的参考。如果没有USL,那即使盯着系统看也无法知道期望的结果是什么。如果想深入了解这个主题,最好去看一下对应的书籍。Gunther博士已经写得很清楚,因此我们不会再深入讨论下去。
另外一个理解可扩展性问题的框架是约束理论,它解释了如何通过减少依赖事件和统计变化(statistical variation)来改进系统的吞吐量和性能。这在Eliyahu M. Goldratt所撰写的The Goal(North River)一书中有描述,其中有一个关于管理制造业设备的延伸的比喻。尽管这看起来和数据库服务器没有什么关联,但其中包含的法则和排队理论以及其他运筹学方面是一样的。
...
2025年1月5日 08:42 周日第10章 复制
MySQL内建的复制功能是构建基于MySQL的大规模、高性能应用的基础,这类应用使用所谓的“水平扩展”的架构。我们可以通过为服务器配置一个或多个备库(1)的方式来进行数据同步。复制功能不仅有利于构建高性能的应用,同时也是高可用性、可扩展性、灾难恢复、备份以及数据仓库等工作的基础。事实上,可扩展性和高可用性通常是相关联的话题,我们会在接下来的三章详细阐述。
本章将阐述所有与复制相关的内容,首先简要介绍复制如何工作,然后讨论基本的复制服务搭建,包括与复制相关的配置以及如何管理和优化复制服务器。虽然本书的主题是高性能,但对于复制来说,我们同样需要关注其准确性和可靠性,因此我们也会讲述复制在什么情况下会失败,以及如何使其更好地工作。
10.1 复制概述
#
复制解决的基本问题是让一台服务器的数据与其他服务器保持同步。一台主库的数据可以同步到多台备库上,备库本身也可以被配置成另外一台服务器的主库。主库和备库之间可以有多种不同的组合方式。
MySQL支持两种复制方式:基于行的复制和基于语句的复制。基于语句的复制(也称为逻辑复制)早在MySQL 3.23版本中就存在,而基于行的复制方式在5.1版本中才被加进来。这两种方式都是通过在主库上记录二进制日志(2)、在备库重放日志的方式来实现异步的数据复制。这意味着,在同一时间点备库上的数据可能与主库存在不一致,并且无法保证主备之间的延迟。一些大的语句可能导致备库产生几秒、几分钟甚至几个小时的延迟。
MySQL复制大部分是向后兼容的,新版本的服务器可以作为老版本服务器的备库,但反过来,将老版本作为新版本服务器的备库通常是不可行的,因为它可能无法解析新版本所采用的新的特性或语法,另外所使用的二进制文件的格式也可能不相同。例如,不能从MySQL 5.1复制到MySQL 4.0。在进行大的版本升级前,例如从4.1升级到5.0,或从5.1升级到5.5,最好先对复制的设置进行测试。但对于小版本号升级,如从5.1.51升级到5.1.58,则通常是兼容的。通过阅读每次版本更新的ChangeLog可以找到不同版本间做了什么修改。
复制通常不会增加主库的开销,主要是启用二进制日志带来的开销,但出于备份或及时从崩溃中恢复的目的,这点开销也是必要的。除此之外,每个备库也会对主库增加一些负载(例如网络I/O开销),尤其当备库请求从主库读取旧的二进制日志文件时,可能会造成更高的I/O开销。另外锁竞争也可能阻碍事务的提交。最后,如果是从一个高吞吐量(例如5000或更高的TPS)的主库上复制到多个备库,唤醒多个复制线程发送事件的开销将会累加。
通过复制可以将读操作指向备库来获得更好的读扩展,但对于写操作,除非设计得当,否则并不适合通过复制来扩展写操作。在一主库多备库的架构中,写操作会被执行多次,这时候整个系统的性能取决于写入最慢的那部分。
当使用一主库多备库的架构时,可能会造成一些浪费,因为本质上它会复制大量不必要的重复数据。例如,对于一台主库和10台备库,会有11份数据拷贝,并且这11台服务器的缓存中存储了大部分相同的数据。这和在服务器上有11路RAID 1类似。这不是一种经济的硬件使用方式,但这种复制架构却很常见,本章我们将讨论解决这个问题的方法。
10.1.1 复制解决的问题
#
下面是复制比较常见的用途:
数据分布
MySQL复制通常不会对带宽造成很大的压力,但在5.1版本引入的基于行的复制会比传统的基于语句的复制模式的带宽压力更大。你可以随意地停止或开始复制,并在不同的地理位置来分布数据备份,例如不同的数据中心。即使在不稳定的网络环境下,远程复制也可以工作。但如果为了保持很低的复制延迟,最好有一个稳定的、低延迟连接。
负载均衡
通过MySQL复制可以将读操作分布到多个服务器上,实现对读密集型应用的优化,并且实现很方便,通过简单的代码修改就能实现基本的负载均衡。对于小规模的应用,可以简单地对机器名做硬编码或使用DNS轮询(将一个机器名指向多个IP地址)。当然也可以使用更复杂的方法,例如网络负载均衡这一类的标准负载均衡解决方案,能够很好地将负载分配到不同的MySQL服务器上。Linux虚拟服务器(Linux Virtual Server,LVS)也能够很好地工作,第11章将详细地讨论负载均衡。
备份
对于备份来说,复制是一项很有意义的技术补充,但复制既不是备份也不能够取代备份。
高可用性和故障切换
复制能够帮助应用程序避免MySQL单点失败,一个包含复制的设计良好的故障切换系统能够显著地缩短宕机时间,我们将在第12章讨论故障切换。
MySQL升级测试
这种做法比较普遍,使用一个更高版本的MySQL作为备库,保证在升级全部实例前,查询能够在备库按照预期执行。
10.1.2 复制如何工作
#
在详细介绍如何设置复制之前,让我们先看看MySQL实际上是如何复制数据的。总的来说,复制有三个步骤:
- 在主库上把数据更改记录到二进制日志(Binary Log)中(这些记录被称为二进制日志事件)。
- 备库将主库上的日志复制到自己的中继日志(Relay Log)中。
- 备库读取中继日志中的事件,将其重放到备库数据之上。
以上只是概述,实际上每一步都很复杂,图10-1更详细地描述了复制的细节。
图10-1:MySQL复制如何工作
第一步是在主库上记录二进制日志(稍后介绍如何设置)。在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中。MySQL会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志。在记录二进制日志后,主库会告诉存储引擎可以提交事务了。
下一步,备库将主库的二进制日志复制到其本地的中继日志中。首先,备库会启动一个工作线程,称为I/O线程,I/O线程跟主库建立一个普通的客户端连接,然后在主库上启动一个特殊的二进制转储(binlog dump)线程(该线程没有对应的SQL命令),这个二进制转储线程会读取主库上二进制日志中的事件。它不会对事件进行轮询。如果该线程追赶上了主库,它将进入睡眠状态,直到主库发送信号量通知其有新的事件产生时才会被唤醒,备库I/O线程会将接收到的事件记录到中继日志中。
MySQL 4.0之前的复制与之后的版本相比改变很大,例如MySQL最初的复制功能没有使用中继日志,所以复制只用到了两个线程,而不是现在的三个线程。目前大部分人都是使用的最新版本,因此在本章我们不会去讨论关于老版本复制的更多细节。
备库的SQL线程执行最后一步,该线程从中继日志中读取事件并在备库执行,从而实现备库数据的更新。当SQL线程追赶上I/O线程时,中继日志通常已经在系统缓存中,所以中继日志的开销很低。SQL线程执行的事件也可以通过配置选项来决定是否写入其自己的二进制日志中,它对于我们稍后提到的场景非常有用。
图10-1显示了在备库有两个运行的线程,在主库上也有一个运行的线程:和其他普通连接一样,由备库发起的连接,在主库上同样拥有一个线程。
这种复制架构实现了获取事件和重放事件的解耦,允许这两个过程异步进行。也就是说I/O线程能够独立于SQL线程之外工作。但这种架构也限制了复制的过程,其中最重要的一点是在主库上并发运行的查询在备库只能串行化执行,因为只有一个SQL线程来重放中继日志中的事件。后面我们将会看到,这是很多工作负载的性能瓶颈所在。虽然有一些针对该问题的解决方案,但大多数用户仍然受制于单线程。
10.2 配置复制
#
为MySQL服务器配置复制非常简单。但由于场景不同,基本的步骤还是有所差异。最基本的场景是新安装的主库和备库,总的来说分为以下几步:
- 在每台(3)服务器上创建复制账号。
- 配置主库和备库。
- 通知备库连接到主库并从主库复制数据。
这里我们假定大部分配置采用默认值即可,在主库和备库都是全新安装并且拥有同样的数据(默认MySQL数据库)时这样的假设是合理的。接下来我们将展示如何一步步配置复制:假设有服务器server1(IP地址192.168.0.1)和服务器server2(IP地址192.168.0.2),我们将解释如何给一个已经运行的服务器配置备库,并探讨推荐的复制配置。
10.2.1 创建复制账号
#
MySQL会赋予一些特殊的权限给复制线程。在备库运行的I/O线程会建立一个到主库的TCP/IP连接,这意味着必须在主库创建一个用户,并赋予其合适的权限。备库I/O线程以该用户名连接到主库并读取其二进制日志。通过如下语句创建用户账号:
...