构建可伸缩的 PHP Web 应用(一)
一、介绍
“云”是新的技术术语。似乎每个人都在谈论“迁移到云”,但很少有人真正知道这意味着什么,更不知道如何利用它。随着每一次技术的转变,有些人认为新技术将解决他们所有的问题,甚至没有盘点那些问题是什么。许多技术都做出了承诺,其中一些甚至是真的,但是要实现它们,你必须以正确的方式使用技术。
这本书主要是为那些想开始将他们的应用迁移到云上,并想知道如何开始和他们可用的不同选项的开发人员准备的。其次,这本书是为那些想要精通云技术和想法的管理者准备的,以便更好地理解可用的选项以及不同的开发决策将如何影响它们。这本书的重点是 PHP,但是,即使 PHP 不是您打算使用的部署语言,这本书也会告诉您在任何语言或平台中构建云应用需要知道什么。
这本书将讨论几种不同的云选项,但将侧重于开发 Linode 和类似的云,原因我们将在第二章中看到。
请注意,这本书在特定的基础设施供应商之间做了许多比较。我不代表任何特定的公司,也不保证这些比较是万无一失的或永久的。然而,它们是我在写作时的经验和知识的结果,我已经尽力提供尽可能多的事实信息。尽管如此,尽管本书中讨论的一般考虑因素不太可能改变,但具体的供应商、产品以及它们如何符合标准可能会随着时间的推移而改变。
此外,虽然设置、编程和配置任务的一般大纲可能会在很长一段时间内保持不变,但随着产品和平台的变化,具体步骤和屏幕截图可能会与本书不同。尽管如此,这里给出的步骤应该仍然是一个可靠的指南,告诉你应该从不同的云供应商那里得到什么类型的东西。
1.1 先决条件
这本书的先决条件很少。即使你不熟悉我们正在使用的具体工具,这本书有足够的一步一步的指示,你应该能够相当容易地跟随它。
因为这是一本关于 web 应用的书,所以它假设您熟悉 HTML、CSS 的绝对基础知识,以及互联网如何工作的基础知识(即域名、IP 地址等)。).如果你不熟悉这些东西,你应该把这本书放下,拿起一本我之前的书,新程序员从这里开始。新程序员从这里开始并没有触及你需要知道的每一个话题,但是如果你理解了它的概念,你应该能够理解本书中的大多数例子。
本书中的代码是基于 PHP 的,但是代码足够简单,无论你熟悉什么语言,你都应该能够理解。之所以选择 PHP,是因为用 PHP 很容易编写简短易懂的 web 应用。例如,在我的工作中,我几乎总是用 Ruby on Rails 编程。然而,如果不熟悉整个 Ruby on Rails 系统,理解 Rails 应用几乎是不可能的。另一方面,PHP 没有 Rails 那么“神奇”,但这让读者更清楚发生了什么。对于 PHP,特别是在本书使用它的水平上,我相信任何有编程语言经验的人都能够理解正在发生的事情。
本书还假设您对数据库和 SQL 有基本的了解。这不是一个绝对的要求,但是数据库代码本身很大程度上没有得到解释。然而,SQL 代码对任何人来说应该是不言而喻的,即使是对 SQL 稍有了解的人。
最后,这本书使用 Linux 作为操作系统的选择。然而,即使你对 Linux 一无所知,这本书也给了你需要输入的每一个命令,所以你实际上不需要了解 Linux 就可以使用这本书。附录 A 有一个常用 Linux 命令的列表,如果你想了解更多,可以参考一下。
选择 Linux 的原因是因为它是一个非常容易在云环境中安装和使用的操作系统。虽然在云环境中使用 Windows 是可能的,但是 Linux 是为这种类型的应用从头开始构建的。命令行虽然看起来晦涩难懂,但实际上使服务器管理变得快速而轻松。
此外,因为 Linux、PHP 和 PostgreSQL(我们的数据库系统)都是自由软件,所以使用它们不需要考虑许可。你只需要安装你需要的东西,然后继续你的生活。你不需要担心你什么时候需要付钱给谁。您不需要担心您的使用是否与您的产品许可相匹配。你不需要担心有人审计你的业务来验证合规性。有了自由软件,软件只是一个工具——你可以安装它,然后忘记它。
原则上,我对专有软件没有问题,但是,因为它引入了大量额外的管理问题,如果可能的话,我会尽量避免。我并不反对人们为他们的工作获得报酬,但我确实对永无止境的、不必要的麻烦持原则性立场,当你过度依赖许可质量很差的专有软件时,你最终会遇到这种麻烦。
本书重点介绍 CentOS 7 Linux 发行版,因为 CentOS 往往是一个相当稳定和健壮的 Linux 发行版,受到大多数供应商的支持,许多其他 Linux 发行版都使用 CentOS 作为起点。
1.2 印刷惯例
这本书使用了一些你应该知道的简单的印刷惯例。当提到代码、键入的命令、URL 或任何其他用于键入的文本时,这本书使用的字体如下:type me here。与命令名相同的程序名(你可以输入使用)也是这样写的,用户名和文件名也是这样写的。
但是,您输入的许多内容需要用您自己的值替换,比如您部署的机器的 IP 地址。这些需要替换的东西都是全大写字母的单词给的。例如,要访问你自己的网站,你可以在浏览器中输入 http://YOUR.DOMAIN.HERE/、,其中YOUR.DOMAIN.HERE是指你自己网站的域名。文本中描述了这些的含义以及它们应该被替换为什么。
请注意,PHP 实际上有几个默认的变量名,我们将使用的都是大写的。所以$_GET、$_POST、$_FILES都是 PHP 中的实变量名,不应该被替换。
这本书描述了如何使用各种第三方服务。当描述各种网站上的菜单、按钮和用户界面元素的字段名称时,这些项目通常会放在双引号中。例如,要从大多数程序中打印出一个文件,你可以进入“文件”,然后点击“打印”
1.3 键入或下载代码
这本书围绕一个简单的留言簿应用,使用不同的应用架构构建和重建它。应用的全部代码都在这本书里。老实说,我认为自己把这本书里的所有代码打出来会对你有很大好处。没有太多,它会帮助你思考你在构建什么。然而,我知道当你只是试图处理这些例子时,这可能会变得很乏味,并且当你试图解决问题时,拥有完整的、可工作的代码是非常有帮助的。
因此,我们在本书中介绍的应用的所有变体的代码都可以从 www.github.com 下载。存储库中的每个分支都代表了对书中应用的不同修改。使用存储库,您需要做的唯一更改是本书正文中提到的特定于环境的更改,比如设置数据库服务器的 IP 地址。这些也在每个分支的README文件中。
您可以从以下网址下载代码
https://github.com/johnnyb/cloud-example-application
如果您自己键入代码,请密切注意空格在程序中的使用位置。代码完全按照应该键入的样子编写,除非文本在其他地方指明,否则在应该的地方换行。将空格和换行符放在错误的地方,或者将它们留在它们应该在的地方,肯定会弄乱代码。
二、什么是云
对于什么是“在云中”有一点混淆。一些人甚至错误地认为仅仅是在互联网上就是在利用云技术。
云技术和其他类型的互联网托管的本质区别在于,云服务至少提供了快速扩展应用的能力。过去,开发人员会将他们的 web 应用部署到他们在特定场所购买或租赁的固定服务器上。获得更多的设备是可能的,但这总是要花费相当多的时间和精力。通常,开发人员必须为服务器投入大量资金,购买、配置和部署服务器。这个过程可能需要几周甚至几个月。即使直接从托管公司购买,汇总报价和启动运行的过程也可能需要一周以上。
云的承诺是,不必经历新服务器的物理设置过程,可以即时或至少在几分钟或几小时内获得额外的容量,而不是几天、几周或几个月。对于某些解决方案,您甚至不必担心机器—云解决方案会根据您的需要自动将您的应用扩展到任意多的机器上。对于其他人来说,只需点击几下鼠标就可以请求、映像和启动一台新机器,这台机器是某台现有机器的副本,然后将它添加到您的 web 应用中。
无论如何,云的核心思想是即时、自动化的可伸缩性和灵活性。
许多云提供商甚至提供对多个数据中心的访问。是要一套美国的服务器,一套欧洲的服务器?没问题。只需点击几下,就能搞定。
2.1 基础设施即服务
云计算经常令人困惑,因为云计算有几种不同的类型,每种类型都有自己的优点和缺点。云服务类型之间的差异主要基于所提供的抽象级别。
本书将重点介绍的最基本类型的云服务被称为基础设施即服务,缩写为 IaaS。IaaS 意味着您可以购买和部署基础设施(服务器、负载平衡器、防火墙等)。)只需点击一下按钮。这是通过服务器虚拟化技术实现的。
服务器虚拟化允许 IaaS 服务公司部署单个非常大的服务器,并将其拆分为多个较小的服务器。每台服务器运行一个虚拟机管理程序程序,该程序允许公司快速自动地拆分服务器。一家公司可能会将一台拥有 16 个处理器和 64g RAM 的计算机分成 4 个虚拟机,每个虚拟机拥有 4 个处理器和 16g RAM。
这样做有三个好处。首先是空间。通过购买最大的机器,IaaS 公司为自己提供了每机架单元最大的计算能力,这在服务器机房中是一种有价值的商品。因此,通过购买一台大型机器并将其分成四台较小的机器,他们只使用了原本可以使用的空间的四分之一。
第二是每个 CPU 内核的成本。通过将如此多的 CPU 核心和如此多的内存打包到一台服务器中,它们的每 CPU 核心成本和每千兆字节内存成本都会下降。因此,通过购买更大的计算机,他们可以为购买较小的虚拟服务器的用户提供更低的每 CPU 核心成本。
然而,第三个优势也是最重要的—可管理性。为了支持虚拟化,每台机器的一小部分专用于管理程序,即管理服务器上其他虚拟机的小操作系统。因为这些机器是虚拟机,而不是真正的硬件设备,所以非常容易管理。操作员可以向管理程序发送命令,管理程序可以立即设置新的虚拟机,克隆新的引导磁盘,并在几分钟内启动新的虚拟服务器。
从历史上看,如果我想要一台新的服务器,我必须购买服务器,然后在控制台前安装相关的操作系统和系统软件,最后将服务器搬到机架上,插上电源并打开。我必须确保网络接通并配置好。我可能需要配置 BIOS 来允许键盘断开连接的操作。如果机器出了问题,我要做机器的备份,找一个新的物理机,把备份复制到新机器上,装上新机器,和旧机器物理换出来。
有了虚拟机,我可以让虚拟机管理程序处理所有这些事情。我所需要的只是运行虚拟机管理程序的足够多的额外服务器,这样当我需要一台新机器时,我就可以告诉虚拟机管理程序为我创建一台新的虚拟机,以及我想要使用的磁盘副本的位置。
更好的是,对于大多数 IaaS 平台,您甚至不需要担心虚拟机管理程序和容量。IaaS 供应商会为您完成所有这些工作。IaaS 供应商提供了一个点击式界面,允许您只需登录到 web 管理控制台就可以启动虚拟服务器。你告诉它你想要多大的机器(即 CPU 核心的数量和内存的大小),它就会为你分配一台服务器。你告诉它你想在上面安装什么(要么是一个基本操作系统,要么是一个现有机器的克隆),它就会把它复制到引导盘上,然后帮你启动。瞧啊。您已经有了一台新的服务器。
此外,对于大多数 IaaS 服务,如果存在物理硬件问题,该服务会为您解决。如果检测到严重的硬件问题,他们会简单地关闭您的服务器,将其迁移到新的服务器,重新启动,并向您发送电子邮件,让您知道发生了什么。如果问题不太严重,一些供应商会通知您,要求您在最方便的时候按下按钮执行迁移。
更好的是定价模式。大多数 IaaS 供应商都可以选择小时定价。你需要一台机器,但只是几个小时?有了 Linode,你可以以每小时不到 1.00 美元的价格获得一台 32 核 64g 的机器!
在第三章中,我们将看看建立云服务器所需的具体步骤。
2.2 平台即服务
扩展应用的另一个选项称为平台即服务,缩写为 PaaS。借助 PaaS,PaaS 定义了一个让你运行应用的平台,而不是给你裸机,让你随心所欲地运行。PaaS 供应商管理整个平台,您只需担心您的应用。
例如,Heroku 是一个流行的 PaaS 供应商。Heroku 处理所有的服务器管理和维护。你甚至不能登录他们的机器!您只需将代码推给他们,他们就会为您将代码部署到他们的机器上。其他常见的 PaaS 供应商包括 Google App Engine、Windows Azure、亚马逊的 Elastic Beanstalk 和 OpenShift。
使用 PaaS,您可以选择使用多少“工作人员”,系统将根据需要在多少台机器上分配工作(工作人员只是一个活动的流程,每个 PaaS 供应商都有自己的术语)。因此,PaaS 供应商负责平台(硬件、操作系统、安装的应用),您只需管理应用代码。
这在理论上听起来很棒——您再也不用管理服务器了!这一切都是在幕后为你自动完成的。然而,现实是,PaaS 平台并不像它们看起来那样透明,PaaS 供应商往往将它们的附加值定得相当高。
2.3 码头工人
Docker 是该领域一项新技术,引起了很多关注。Docker 应用介于 IaaS 和 PaaS 之间。Docker 应用是一个映像,本质上包含了运行应用所需的所有程序的完整安装。
它们可以像 PaaS 一样进行管理、部署和扩展,在 PaaS 中,您只需告诉服务您想要运行多少个,它就会负责将您的映像部署到正确的位置,但您拥有更大程度的控制,类似于 IaaS。
Docker 的一个有趣的部分是将不同的服务“链接”在一起的能力。本质上,您将 Docker 容器提供或需要的不同类型的服务命名,然后可以使用这些名称告诉您的服务如何找到彼此。然而,这通常只是在应用的初始设置阶段出现的问题,在此之后,您是手动还是使用高级工具将应用组件链接在一起就无关紧要了。
Docker 本身是技术,不是厂商。有许多供应商允许您部署 Docker 应用,包括负责 Docker 技术的 Docker Inc .
2.4 为什么选择 IaaS
出于多种原因,本书集中讨论 IaaS 云模型。首先是 IaaS 非常灵活。无论您想要运行什么类型的工作负载,无论您想要运行什么平台,IaaS 都会为您提供裸服务器。你如何对待他们取决于你自己。这也意味着您不会被特定供应商的系统所束缚。
第二,IaaS 的工作相当有预见性。虽然存在差异,但大多数 IaaS 供应商的运营方式是相当相似的。你选择一个盒子大小,说你想要什么,然后按下按钮。这些服务的质量、成本和灵活性有很大的差异,但它们都非常相似。
有了 PaaS,您的选择就更加有限了。首先,您受限于您的供应商提供的平台。如果你用 Ruby on Rails 编程,你只能使用 Ruby on Rails PaaS。虽然大多数 PaaS 供应商已经向相当多的不同应用服务器开放了他们的系统,但是仍然存在一些限制。第二,您必须编写与他们配置平台的方式相匹配的代码。这通常涉及很少或没有本地文件存储、要连接的某些特定类型的数据库、仅某些允许的平台选项或扩展,以及在如何配置服务器方面很少或没有灵活性。有时这很好,但有时这太严格了。
此外,PaaS 通常太不透明。访问服务器日志通常很困难,调试特定于服务器的问题几乎是不可能的。有时 PaaS 供应商有工具可以提供帮助,但是它们无法直接在有问题的机器上进行调试。这种不透明有时会导致毁灭性的结果。例如,如果您购买了一个 PaaS 数据库系统,但不知何故您的数据库被破坏了,那么您的选择就非常有限。你基本上要打电话给公司,乞求帮助。有些公司在这方面反应很积极,但这让我很紧张。
还要记住,为了让 PaaS 系统正常工作,他们必须不断升级他们的系统。事实上,这就是你付钱让他们做的事情。然而,不能保证他们明天进行的升级不会意外地破坏您的应用。也许升级是必要的,但这不是你能做的决定。
类似地,使用 PaaS 供应商意味着您必须按照他们的时间表升级。如果他们决定放弃他们的基础设施,您必须重写代码来解决这个问题。如果他们认为你使用的软件版本过时了,你必须重写你的代码来使用新版本。简而言之,PaaS 意味着你失去了对你的技术的控制,而 IaaS 意味着你拥有完全的控制权。虽然使用 PaaS 可以消除某些麻烦,但它们通常会被引入的麻烦所弥补。
最后,PaaS 通常非常昂贵。例如,对于 Heroku(一个常见的 Ruby on Rails PaaS 供应商)上的四个 CPU 内核,每月花费 100 美元。对于 Linode 上的 4 核机器,成本仅为每月 40 美元,并且 Linode 机器更快。我发现,对于同等性能,大多数 PaaS 供应商的收费是优秀 IaaS 供应商的三倍左右。PaaS 供应商为您做了更多,但是只有当您的开发人员队伍中没有任何系统管理经验时,这才是值得的,而且无论如何,您都要为跟上 PaaS 平台的步伐而付出代价。
如果您的组织知道如何设置和维护服务器(这本书为学习如何做提供了一个良好的开端),IaaS 是目前利用云技术最简单、最灵活、最快、最便宜的方式,并且它不会将您局限于特定的供应商。如果您决定真的想要使用类似 PaaS 的系统,只需知道有许多开源的 PaaS 系统可以在您的 IaaS 服务器上运行。
2.5 选择 IaaS 供应商
选择 IaaS 服务或供应商时有许多考虑因素。最重要的考虑是服务的可靠性。如果服务中断,即使有一个优秀的 web 应用也没有用。如果你不能得到你的数据,从长远来看,它不会为你的公司赚钱。因此,服务的可靠性应该会对您的决策产生重大影响。
这也延伸到他们解决票证的能力。每个服务都会在某个时候出现问题。如果一家公司不能及时响应或解决问题,那么您就不应该让生产系统占用它们。
下一个主要因素是性价比。许多早期的云基础设施公司非常重视云计算的灵活性,结果几乎每个云解决方案的成本都高得惊人。亚马逊网络服务(AWS)的云计算服务 EC2 就是一个很好的例子。如前所述,一台 8GB 内存的 Linode 4 核机每月 40 美元。EC2 上类似指定的机器(c5.xlarge机器)是 122 美元/月。从历史上看,即使在相同的规格下,EC2 也往往比 Linode 慢得多。在云计算的早期,EC2 是该领域仅有的大玩家之一,为了获得灵活性,他们让你付出了高昂的代价。就个人而言,按照 EC2 的要价,我更愿意走出云,做一个传统的托管设置,在那里租赁物理服务器。对我来说,这种灵活性不值 AWS 要求的价格。
另一方面,有些云服务的价格高得不可思议。也就是说,你可以从价格上认识到,他们不可能以这样的价格提供可靠的长期服务。要么公司倒闭,要么公司不得不提高价格,要么服务最终会被超额预订,降级到不可用的地步。一个很好的例子就是 CloudAtCost ( www.cloudatcost.com )。有了 CloudAtCost,你只需支付一次性费用,就可以永远保留服务器*。例如,花 70 美元,你可以得到 2 个 CPU 内核和 1GB 内存。但是那是每月的费用。那是一笔一次性的费用!正如我所说,这是一个令人难以置信的好价格。他们收取每年 9 美元的账户维护费,但不管你有多少台服务器,这个费用都是一样的(这可能是为了让他们可以关闭不再维护的服务器)。*
*这种服务可能对一些事情有好处。例如,如果您想要一个开发服务器,即使网络偶尔出现故障,或者技术人员不能快速响应,都没有关系。而且,如果有一天他们关了门,你也不会出局太多。但我肯定不会把我的生意押在这样的服务上。如果你想看看其他价格极其便宜的服务,请查看 www.lowendbox.com 。
最后一个因素是灵活性。能够点击设置一台新机器是一回事,但是如果您不能存储磁盘映像,并且每次启动一台机器时都必须重建一台机器,那该怎么办?这将是一个艰苦的过程,你将失去云计算的一个主要优势。这是 AWS 大放异彩的一个领域。AWS 不仅包括其云计算服务(EC2),还为云基础设施的几乎每个方面提供配置、控制和自动化。AWS 提供可扩展存储解决方案、视频转码服务、可扩展数据库、消息排队服务和搜索服务。这为您的集群提供了一个外部监控服务,当负载增加时,它会自动生成新的服务器来处理负载,并在网络负载减少时,从您的集群中删除服务器并关闭它们。换句话说,AWS 附带的额外服务几乎是无限的。
然而,到最后,AWS 的灵活性被 EC2 糟糕的性价比压倒了。然而,令人欣慰的是,AWS 更好的服务(如 S3 和 CloudFront)可以单独使用,即使你使用另一个提供商作为你的主要 IaaS 供应商。我们将在后面的章节中讨论如何做到这一点。
如果你还没有猜到,我对云供应商的偏好是 Linode ( www.linode.com )。他们的性价比是无与伦比的。原因有三。第一,设备较新。其次,他们只使用固态硬盘(即固态硬盘,没有旋转磁盘),通常比普通磁盘快一个数量级。这可能是 Linode 表现最重要的方面之一。第三,他们实施了控制措施来防止“吵闹的邻居”。
高噪音邻居是与您的机器在同一物理服务器上的虚拟机,但是它使用了计算机的所有 I/O 资源。在 IaaS 平台上,你不能选择(甚至不知道)谁在共享同一个物理硬件。因此,使用防止资源占用的服务非常重要。Linode 实施了许多控制措施来防止任何单个虚拟机过度使用资源。这不仅对你自己的虚拟服务器的性能有好处,而且也意味着你不必担心成为别人的坏邻居!
虽然 Linode 没有 AWS 的灵活性,但这种灵活性也是不容错过的。你想用 AWS 做的大部分重要的事情用 Linode 来做都是极其简单的,AWS 增加的特性让 AWS 的学习曲线变得陡峭而混乱。当有人花费额外的钱来获得 AWS 的灵活性时(例如,创建一个系统来自动引导新机器以响应负载),他们可能已经在 Linode 上花费了相同的钱来为他们的集群提供足够的容量,这样它就不会有问题了。
还有其他类似于 Linode 的服务——digital ocean 通常被认为是价格相当的类似服务。然而,我和 Linode 的经历非常积极,我觉得没有必要对它们都进行研究。Linode 提供了我需要的东西,有一个易于使用的界面,而且价格非常便宜。因此,本书将重点介绍在 Linode 上开发云应用。
选择供应商时,仔细阅读他们的服务条款也很重要,以确保您的预期用途是兼容的。不仅某些服务不允许某些工作负载(例如,某些服务禁止群发电子邮件营销),许多服务还限制了允许您扩展网络的速度。这主要是为了防止滥用,但提前发现也很重要。在任何情况下,您都可能希望接触潜在的云供应商,并确保您的计划使用符合他们的服务指南,然后再选择一家。
2.6 一些重要术语
在我们进一步讨论之前,我想澄清一下本书中使用的一些术语。借助 IaaS,您租用的基本上都是机器。它们是虚拟机器(即真实机器的分区),但尽管如此,它们本身也可以被认为是抽象的机器。在云计算行话中,它们通常被称为节点。由于它们向网络上的其他机器和/或用户提供服务,它们也被认为是服务器。因此,节点的另一个常用术语是 VPS,即虚拟专用服务器。在本书中,机器、节点和服务器这三个术语可以互换使用,尽管具体术语的选择通常是基于您目前的使用方式。
一个集群是一组协同工作的节点。因此,我们正在云中构建一个集群。在云中可扩展的服务被称为云集群,或者简称为云。
术语可扩展性是云计算中的一个重要术语。可伸缩性不仅仅是性能。性能是指效率或原始速度。可扩展性是指系统快速扩展其处理能力的能力。例如,一个每秒可以处理 2,000 个请求的程序可能被认为是高性能的,但是如果该程序的速度不能通过添加另一个节点来提高,那么它就是不可伸缩的。
另一方面,即使是低性能的程序也可能是可伸缩的。如果我的程序每秒只处理 1 个请求,但我添加的每个节点都会增加它可以处理的请求数,那么我的程序就是可伸缩的。如果我将这个低性能的程序扩展到 4000 个节点,它将能够处理比以前每秒最多处理 2000 个请求更多的请求。根据整个集群的吞吐量,整个集群(包含 4,000 个节点)现在可能被认为是高性能的,即使其各个部分并不是高性能的。
通常,可伸缩性取决于您并行化任务的能力。并行化是指任务彼此独立运行的能力,即使在其他机器上也是如此。这两个任务越需要相互协调,它们的并行性就越差。在为可伸缩性设计应用时,您需要特别注意系统的不同部分需要协调的方式,并致力于最小化或消除这些协调点的影响。
希望您构建的东西既有高性能(即使对于少量节点)又有可伸缩性(因此添加更多节点可以提高性能)。让一个应用运行良好是关于寻找慢代码并用快代码替换它,或者重写你的应用以不需要慢代码。使应用可扩展是关于寻找阻止并行化的瓶颈*,并用可扩展到多个节点的代码替换这些瓶颈,或者重新设计您的架构,以便瓶颈一开始就不会出现。*
*本书的重点是可伸缩性,尽管性能也在考虑之列。
跳过服务器机房
这里有一个有趣的故事,是我在处理服务器机房时不会错过的事情。在 20 世纪 90 年代末,我曾经在一家托管自己服务器的公司担任程序员/系统管理员。这些服务器不在常规的服务器机架上,而是放在金属线架子上。主要的面向外部的服务器在顶层架子上。
我们需要一些额外的驱动器空间,所以我在服务器上安装了一个外部驱动器包。当时,将外部存储连接到服务器的主要模式被称为 SCSI(读作“scuzzy”),它充满了问题。过了一段时间,服务器访问驱动器包开始出现很多问题,所以我去排除故障。
因为服务器在架子的顶层,所以我站在凳子上操作机器。我花了大约半个小时试图找出问题所在。意识到这发生在我们的主要外部服务器上,所以每一分钟都很重要,因为我们(非常活跃的)网站有问题。
因此,我设法把所有东西从新的驱动器包中移走,然后断开驱动器,把它们带回我的办公室,看看问题出在哪里。然而,我花了太多的时间站在凳子上摆弄电脑,我忘记了,你知道,我是站在凳子上。所以,我抓起驱动器包,走出了房间,或者说,我打算这样做。谢天谢地,我和车手们都没有受伤,但我的自尊心却受到了伤害。
尽管回忆这些故事很有趣,但我很高兴那些日子已经过去了。**
三、设置云服务器
与大多数云提供商一样,在 Linode 上设置云服务器极其简单。本章通过截图向您展示了启动和运行的基本过程。
3.1 创建虚拟服务器
如果你是一名网站开发人员或管理者,希望你可以不太麻烦地注册 Linode 服务。它需要预先准备一张信用卡,但是你会发现运行这本书里所有东西的费用可能比这本书本身要少,假设当你用完它们的时候你关闭了你的服务。
在继续之前,请立即在 Linode 创建您的帐户。
在您注册并登录之后,Linode 会将您带到您的仪表板,它看起来应该类似于图 3-1 。
图 3-1
Linux 仪表板
您目前没有进行任何设置,因此您的仪表板相当空。要开始,请单击“创建”按钮。Linode 将他们的虚拟服务器称为“节点”或“Linodes”,因此选择“Linode”来创建一台新机器。
然后,Linode 会问您几个问题,以帮助您配置要使用的节点。虽然有许多好的选择,但为了能够跟随这本书,请使用这里的一些:
-
在“选择发行版”下,选择“CentOS 7”
-
在“区域”下,您选择什么并不重要,但是您每次都必须选择相同的区域,以便您的服务器能够相互通信。这本书将使用“德克萨斯州达拉斯”的设施。
-
在“Linode 计划”中,最便宜的是“Nanode 1GB”计划,它足以满足我们的目的。在撰写本文时,这项计划每小时的运行成本不到 1 美分。
-
在“Linode Label”下,我们将称这台机器为
template_node。 -
您可以忽略“添加标签”部分。当您有很多机器时,标签对于将它们分组在一起很有用。
-
在“Root Password”下,为该机器添加一个密码。请确保密码是安全的,因为有很多黑客只是到处尝试不同的 root 帐户密码。每月有 50,000 次这样的黑客攻击并不罕见。
-
目前,你可以保持“可选附件”不变。我们将在本书的后面处理备份和私有 IP 地址。
完成所有这些设置后,点击“创建”按钮,Linode 将开始构建您的机器。Linode 会将你带到你的机器的仪表板上,除了别的以外,它还会有一个进度条和一个“活动提要”(见图 3-2 )。当进度条完成时,您现在是云中一个新服务器的骄傲的所有者了!
图 3-2
您的新节点的仪表板
选择服务器
Linode 根据(a)服务器将服务的工作负载类型和(b)服务器自带的内存大小对服务器进行分类。工作负载类型有“Nanode”(超小型和超廉价的实例)、“Standard”(平衡的 CPU/RAM)、“Dedicated CPU”和“High Memory”(也有“GPU”,但这些是针对与这里考虑的完全不同类型的云计算)。
在每种工作负载下,服务器都是根据它们附带的内存量来命名的。你猜对了,一个“Linode 4GB”配有 4GB 内存。它还将指定有多少个虚拟 CPU 和多少磁盘空间。通常,内存量的增长速度比内核数量的增长速度快,考虑到内存通常是比 CPU 能力更强的限制因素,这是有道理的。它们各自也有不同数量的磁盘空间,但是我认为磁盘空间是次要的考虑因素,因为第八章将展示如何建立一个具有无限可用磁盘空间的服务。
在选择生产服务器时,出于今后将变得更加清楚的原因,我通常为数据库选择相当大的规模(因为它们更难复制),为 web 服务器选择低到中等的规模(因为我可以通过添加更多的服务器来轻松增加容量)。
3.2 登录并四处查看
所以,你有一台机器,但是它在哪里,你如何访问它?
单击仪表板上的“网络”选项卡。它看起来应该类似于图 3-3 。在“访问”下,有一个标题为“SSH 访问”的区域这里有您需要在命令行中键入的命令,以便登录。使用您为节点创建的密码登录。
图 3-3
网络选项卡
命令行?那是什么?
命令行是访问计算机的老派方式。在漂亮的图形界面出现之前很久,人们通过打字与电脑互动。对于许多事情来说,*尤其是对于与系统管理相关的任务来说,*命令行仍然是管理系统的最佳方式。
如果您对命令行没有任何经验,请不要担心!这本书并不假设你有这方面的专业知识,并将引导你完成每一步。如果你只是想弄清楚如何进入命令行,这里是你在每个主要操作系统中要做的事情:
-
Windows 10 : Windows 实际上有两个命令行系统。旧的“命令提示符”(
cmd.exe)和新的 PowerShell。只需点击 Windows 图标并输入PowerShell即可开始。您将至少需要 2018 年 4 月的更新,以便在没有进一步设置的情况下运行此处的命令。 -
MacOS X :每台 Mac 都附带一个名为“终端”的应用。您可以使用 Spotlight search 找到它,或者您可以前往“应用”,然后前往“实用工具”,在那里找到它。我建议你把它添加到你的 Dock 中,因为你可能会经常用到它。
-
Linux :每个 Linux 发行版都安装了一个命令行程序,通常被命名为“终端”或“Bash 提示符”或类似的东西。
当您第一次启动命令行时,它会向您显示一些文本,后面跟着一个闪烁的光标。现在您可以开始输入命令了!
如果您是 Linux 新手,ssh是一个非常方便的工具,它允许远程、安全地连接到您的服务器命令行。也就是说,您可以ssh进入您的机器,它就像您登录到控制台一样工作。此外,连接是加密的,所以你不必担心有人窃听你或窃取你的密码。ssh默认安装在每个主要的操作系统上,所以你应该已经安装了。如果你用的是旧版本的 Windows,可能要下载一个单独的 ssh 应用,比如 PuTTY,可以从 www.putty.org 免费下载。
要登录到您的机器,只需打开一个命令行,键入“SSH 访问”部分下面列出的命令。它应该显示类似于ssh root@MY.IP.ADRESS.HERE的内容,其中MY.IP.ADDRESS.HERE是您的 Linode 的 IP 地址。因为ssh之前没有见过这台电脑,所以很可能会警告你主机的真实性无法建立,并询问你是否要继续连接。就回答yes。它只会在第一次询问您,因为ssh会记住远程计算机。然后,输入您在设置机器时设置的密码作为密码。图 3-4 显示了这可能的样子。
图 3-4
从命令行登录
您现在已登录到您的机器!
最后一行被称为“命令提示符”它提供了会话当前状态的基本信息。root是您的用户名。这是 Linux 上管理用户的名称。li1125-199(或@符号后的任何符号)是您机器的名称。最后,~告诉你你在什么目录下(对于新手程序员来说,“目录”是“文件夹”的旧说法)。~表示用户的“主”目录。
如果您不熟悉 Linux,了解以下几个命令会有所帮助:
-
pwd:表示“打印工作目录”这将告诉您当前正在哪个目录下工作。如果你第一次登录时这样做,它应该会显示/root。Linux 目录不是以驱动器号开始,而是以斜杠(/)开始作为顶级目录。/root是根用户的主目录。 -
mkdir:这代表“制作目录”这将在当前目录中创建一个新目录。 -
cd:这代表“改变目录”如果你给它一个目录名,它将转到那个目录。如果您键入不带任何参数的命令,它将带您到您的主目录。命令cd /将带您到根目录(注意根目录是顶级目录的术语,不是根用户的目录)。如果一个目录名以斜杠开头,cd命令会认为它是一个绝对路径,从根目录开始。如果目录名以波浪符号(~)开头,cd命令将解释相对于您的主目录的路径。否则,它会将路径解释为相对于当前目录。 -
这代表“列表”,给你一个当前目录下的文件列表。要查看文件权限,在命令中添加选项
-l。要查看隐藏文件,添加选项-a。因此,要查看的隐藏文件和文件权限,输入ls -l -a。 -
Nano 是你易于使用的文本编辑器。如果你将运行机器作为你工作的一部分,你也应该学习
vim,因为这对你来说会更有效率,但那是一项更困难的任务。Nano 易于使用,对于入门来说已经足够了。如果你想在当前目录下创建一个名为test.txt的文件,输入nano test.txt并开始输入。组合键 control-o 将保存(即输出)您的文件,control-x 将退出。 -
systemctl:这个命令处理在某些 Linux 发行版上启动和停止系统服务,包括 CentOS。本章稍后将向您介绍如何使用它。 -
logout:退出当前用户会话。您也可以通过键入exit或 control-d 来完成此操作。
我鼓励你花些时间来研究一下命令mkdir、cd、ls和nano。尝试创建一个新目录,进入该目录,并在那里创建一个新文件。然后,尝试注销,用ssh重新登录,找到你的文件,并查看它。这样做几次,直到您完全熟悉登录、注销、导航目录和编辑文件的过程。
在您熟悉了在主目录中创建文件和目录之后,您应该扩展到其他目录。您还不应该在那里编辑文件(您的主目录之外的文件对操作系统来说可能意味着一些重要的东西),但是四处看看没有坏处。
要开始四处查看,请转到根目录(cd /)并四处查看(ls)。你会看到许多目录,其中大部分都记录在 Linux 文件系统层次标准中(参见 www.pathname.com/fhs)。尽管文件系统层次结构标准是关于目录用途的一般信息的好地方,但它不再被严格遵守,所以不要对一些偏差感到惊讶。然而,简而言之,/etc包含服务器配置信息,/home包含除 root 之外的用户的主目录,/usr包含已安装的程序,/opt包含定制的程序和其他特定于服务器的项目,/var包含定期变化的信息(例如,日志文件、缓存、队列等)。).)
3.3 更新您的系统
系统启动并运行后,您应该做的第一件事是用最新的升级和安全软件包更新服务器。CentOS 使用yum来管理系统软件安装。yum简单安全地下载、安装、升级、删除和验证软件包。当你要求yum安装或更新一个包时,它足够聪明,可以在远程服务器上找到这个包,验证这个包的真实性,查找并安装这个包所依赖的任何其他软件,并跟踪所有已安装的包及其文件。yum只能由根用户运行,但到目前为止,这是我们唯一可用的用户。
当你有一个新的服务器时,你应该做的第一件事就是把它所有的安装包更新到最新的版本。幸运的是,使用yum这真的很容易。只需运行以下命令:
yum -y update
这通常会下载一个非常大的数量的包——这很好。CentOS 不断修复发行版中每一个软件的错误和解决安全问题,因此这些更新会变得很大。然而,CentOS 也非常小心地确保它包含的修订版不包含任何不兼容的升级。所以,通过运行yum update,你可以让自己跟上时代,并且你不太可能因为运行它而意外地打破任何东西。
3.4 运行 Web 服务器
默认情况下,Linode 附带的 Linux 发行版只安装绝对必要的软件。这实际上很棒,因为维护安全性的主要方法之一是只安装您绝对需要的东西,这将您面临的潜在漏洞的数量减到最少。但是,默认情况下,不会安装 web 服务器。
如果你在浏览器中输入你的网络服务器的 IP 地址,你的浏览器会回应说它不能连接到电脑。这是因为 web 服务器尚未运行。为了安装 web 服务器,我们将使用 CentOS 的yum包管理器。这本书涵盖了 Apache web 服务器,计算机称之为httpd,但是也有其他的可能性。
要安装httpd,请运行:
yum -y install httpd
运行yum的时候在什么目录并不重要。无论您从哪里运行它,它都会将软件包安装到正确的目录中。yum将列出您想要安装的软件包,以及运行它所需的其他软件包。
现在你的 web 服务器已经安装好了,但是它没有运行。要运行 web 服务器,只需输入:
systemctl start httpd
这个命令也不关心您在什么目录中。
现在您的 web 服务器正在运行,但是您可能仍然无法连接到它。这是因为 CentOS 在默认情况下会运行防火墙。因此,我们需要在防火墙上增加漏洞,以允许从外部访问 web 服务器。
为此,发出以下命令:
firewall-cmd --add-service http
firewall-cmd --add-service http –permanent
firewall-cmd --add-service https
firewall-cmd --add-service https --permanent
这些命令会将 HTTP 和 HTTPS 添加到远程用户可以连接的服务列表中。管理您的防火墙。添加服务(--add-service)允许访问该服务。在没有--permanent标志的情况下运行它会修改当前的防火墙。添加--permanent标志告诉防火墙在服务器重启时将该规则准备好。要立即启用该规则,并在服务器重新启动后仍启用它,您需要这两个命令。
您可以通过发出以下命令来查看允许的服务列表:
firewall-cmd --list-services
当这些命令已经运行时,你可以简单地在你的浏览器中找到你的服务器的 IP 地址,你应该得到一个如图 3-5 所示的测试屏幕。
图 3-5
Web 服务器的测试屏幕
这意味着您的 web 服务器已经启动并正常运行——祝贺您!
然而,还有一个问题需要考虑。即使服务器现在正在运行,如果你重新启动你的机器,它将不会在启动时运行。要确保该服务也在启动时运行,请发出以下命令:
systemctl enable httpd
要进行测试,请转到您的节点的仪表板,在页面的右上角,应该显示“正在运行”如果你点击它,它会显示一个“重启”按钮。单击该按钮重新启动计算机。当您的服务重新启动时,测试网页可能会在某个时候消失。但是,一旦重新启动的进度条完成,测试网页应该可以再次使用。
重新启动将使您注销,因为计算机不再运行。但是,您可以再次登录,您将回到 root 的主目录。
3.5 建立自己的网页
如果网站没有内容,我们得到的测试页面是自动生成的。要为网站创建内容,您只需将一些内容放在正确的位置。
进入/var(类型cd /var)并环顾四周(类型ls)。您看到的其中一个目录将是www.这是 web 服务器提供的数据(即网页)的默认目录。走进www(类型cd www)四处看看(类型ls)。目录是你放置 HTML 和 PHP 文件的地方。进入那个目录(键入cd html)。要验证您是否在正确的位置,键入pwd,它应该会告诉您您在/var/www/html中。
现在您在/var/www/html中,您将为 web 服务创建页面。使用nano index.html创建文件index.html,并在其中放些东西(如果你不知道键入什么,只需键入hello there或类似的东西)。使用 control-o 保存文件,使用 control-x 退出编辑器。一旦你创建了这个文件,你就可以进入你的 IP 地址,这个文件就会显示为默认页面。
3.6 安装 PHP 7
因为这是一本关于 web 应用开发的书,我们想做的不仅仅是网页。我们需要在服务器端启用脚本。因此,我们需要安装我们正在开发的应用框架,以及让它在 Apache 上运行的插件。本书重点介绍 PHP 7。不幸的是,CentOS 7 只有 PHP 5 可用。因此,我们将不得不从另一个库加载 PHP 7。
两个常用的软件包仓库是 EPEL(Enterprise Linux 的额外软件包)仓库和 Remi Collet 的仓库。很容易加载新的存储库供yum查找。每个存储库都有一个 URL,yum可以加载这个 URL,这样yum就可以在将来的安装命令中使用它。要启用这些存储库,只需键入以下内容(缩进的行应该与前一行放在同一行):
yum install -y
https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum install -y
https://rpms.remirepo.net/enterprise/remi-release-7.rpm
现在我们可以安装 PHP 7 了。为此,只需键入:
yum install -y php74
这将安装基本的 PHP 7 包及其依赖项,但没有其他的了。安装完成后,仍然在/var/www/html目录中,运行命令nano test.php并输入以下脚本:
完成此操作后,用 control-o 保存文件,用 control-x 退出。现在可以直接使用命令行运行该文件。键入命令:
php74 test.php
它将运行您的文件中的代码,并像代码所说的那样输出字符串This is a test。
现在,打开你的网络浏览器,进入 http://MY.IP.ADDRESS.HERE/test.php. 注意,它是而不是作为 PHP 脚本运行的。这是因为 Apache 和 PHP 没有连接在一起。
我们现在需要将 PHP 连接到 Apache。这是通过 FastCGI 进程管理器完成的(FastCGI 是允许 PHP 和 Apache 进行通信的协议)。您可以使用以下命令进行安装:
yum install -y php74-php-fpm
这是一个独立的过程,因此也必须启用和启动它。
systemctl enable php74-php-fpm
systemctl start php74-php-fpm
现在,我们必须配置 Apache 将.php文件连接到 PHP 7 解释器。使用nano创建一个名为/etc/httpd/conf.d/php.conf的文件,并放入该文本(缩进的行应该在与前一行相同的行)。
图 3-6
PHP 配置文件
这段代码告诉 Apache 将所有对以.php结尾的文件的请求转发(称为代理)到我们之前安装的 FastCGI 服务。最后一行告诉 Apache,它可以将index.php作为目录索引,这意味着如果有人删除了文件名,并且有一个index.php文件可用,它可以将该文件作为该目录中的默认页面。
现在,要让它正常工作,重启 Apache:
systemctl restart httpd
现在你应该可以用你的浏览器点击这个文件了,它应该是通过 PHP 7 处理的。万岁!
安装 PHP 时,您只安装了基础包。要查看您可以安装的所有扩展,请运行命令yum search php74。这将显示所有可用 PHP 包的列表,每个包都可以用yum install -y安装。
3.7 关闭 SELinux
SELinux 是 Linux 的一个安全增强特性。虽然从理论上讲,SELinux 可以做很多事情来最小化服务器上的安全风险,但实际上它对于实际应用来说太笨拙了。SELinux 对于构建良好的站点通常是不需要的,对于构建不良的站点也没有足够的保护。相反,它最终增加了一个很大的系统管理难题,但收效甚微。如果您运行 SELinux,最有可能的结果是,您将花费数天时间试图找出某些东西不工作的原因,结果却发现 SELinux 正在无缘无故地阻止一些基本操作。
要关闭 SELinux,编辑文件/etc/selinux/config,将SELINUX=enforcing改为SELINUX=permissive。
重启你的机器。当它重新启动时,登录并发出命令getenforce。上面应该写着Permissive。您现在可以开始工作了。
注意,如果不禁用 SELinux,本书中构建的应用将无法运行,因为 SELinux 会阻止应用连接到数据库。
3.8 为开发设置用户
到目前为止,我们已经使用了根用户(即超级用户)。虽然有许多事情需要 root 用户,但出于安全原因,通常情况下您应该尽可能少花时间作为 root 用户。因为根用户可以做任何事情,所以很容易违反安全措施,因为根用户要么破坏系统,要么意外地允许未经授权的实体访问您的系统。
因此,我们将创建一个非管理用户来完成我们的大部分任务。这个用户将被命名为fred。要创建用户,请键入以下内容:
useradd fred
这将 Fred 设置为系统上的用户,为他创建一个主目录,并给他一个用户和组号。现在,我们需要通过键入以下内容为 Fred 设置密码:
passwd fred
这将提示您输入密码,并通过要求您重复密码来确保您输入的密码是正确的。我们希望 Fred 能够添加和修改/var/www/html目录中的文件,因此我们需要授予他这些文件的所有权。我们使用chown命令来实现这一点:
chown -R fred /var/www/html
这告诉将/var/www/html目录及其下所有文件的所有者更改为fred。我们现在可以注销,或者我们可以打开一个新窗口,以fred的身份ssh回到机器中:
ssh fred@MY.IP.ADDRESS.HERE
这将把我们带到弗雷德的主目录。我们现在可以cd进入/var/www/html并像以前一样修改文件,因为它们现在归 Fred 所有。请注意,由于 root 是超级用户,所以即使文件归 Fred 所有,他仍然可以修改这些文件。
3.9 向服务器传输文件
现在,大多数人不喜欢直接在他们的服务器上编程。通常,他们希望在自己的机器上编写程序,然后传输到服务器。为此,您需要一个支持 SFTP 协议的工具。SFTP 基本上是基于 SSH 的 FTP。
跨 Windows、Macintosh 和 Linux 工作的最简单的 SFTP 解决方案是 FileZilla ( www.filezilla-project.org )。您可以使用普通(免费)或专业(付费)版本。要使用 FileZilla,在你安装之后,只要打开它,点击“文件”,然后点击“网站管理器”单击“新建站点”按钮。
填写类似于图 3-7 的屏幕。在“主机”框中,输入您的 Linode 服务器的 IP 地址。在“协议”框中,选择 SFTP。将“登录类型”设置为正常,将用户名设置为fred,将密码设置为您为 Fred 设置的任何密码。你也可以把网站的名字改成一个容易记住的名字(我把我的名字叫做“Linode 虚拟服务器”)。
图 3-7
设置 FileZilla 进行连接
完成后,点击“连接”按钮。
第一次连接时,可能会弹出一个对话框,提示“未知主机密钥”这很好——这和你第一次通过ssh连接时的想法是一样的。该软件以前从未见过服务器。单击标有“始终信任该主机”的框,然后单击“确定”一旦连接上,它将看起来类似于图 3-8 。
图 3-8
FileZilla 连接到你的 Linode 服务器
FileZilla 有两个窗格——左窗格是您的本地计算机,右窗格是远程计算机。它会告诉你在每一个目录中它正在查看哪个目录。您需要做的是将本地目录设置为您想要传输文件的位置,将远程目录设置为您想要放置这些文件的位置(大概是/var/www/html)。一旦设置了这些目录,下面的两个窗格就会列出实际的文件,您可以简单地来回拖放它们。
作为练习,在您的本地计算机上创建几个简单的 PHP 文件,并将它们传输到服务器,并验证您可以通过 web 浏览器看到它们。
编辑PHP.INI
对于某些应用,您可能需要修改php.ini文件。我们安装的 PHP 7 版本将php.ini文件放在目录/etc/opt/remi/php74/中。但是,只有 root 用户可以访问该文件。
如果您需要用 FileZilla 传输它,您将需要以 root 用户身份使用 root 用户的密码重新连接。也可以通过ssh登录,直接用nano修改。在任何情况下,修改该文件时都要小心。此外,在修改文件之后,一定要使用
systemctl restart php74-php-fpm
这似乎需要做大量的工作来设置。虽然有更快的开始方法,但这种方法有几个优点。首先,你现在比大多数人更熟悉所有这些部分是如何连接在一起的。第二,你有一个运行 PHP 7 的网络服务器,而不是某个十年前的版本。最后,您需要的一切都有了,没有什么是没有的,这将有助于您保持 web 服务器的安全。
其他要安装的工具
就个人而言,我喜欢用工具打包我的节点。我成为 Linux 用户的时间比我的许多读者还长,所以我非常熟悉大量的工具。幸运的是,它们都很容易安装(你必须以 root 用户身份登录才能安装)。
无论如何,以下是我经常使用的工具的安装命令:
yum install -y git
yum install -y screen
yum install -y telnet
yum install -y bind-utils
yum install -y traceroute
yum install -y nmap
yum install -y strace
yum install -y perl
本书不是对这些命令的介绍,但是它们值得研究。
四、创建简单的 Web 应用
在本章中,我们将创建一个极其简单的 web 应用,用于后续章节中的演示。这里的目标是让一个完整的端到端应用启动并运行。
我们将要开发的应用将只是一个留言簿,这样任何人都可以在留言簿上留言。
4.1 设置数据库服务
任何好的 web 应用都有一个数据库。我选择的数据库一直是 PostgreSQL ( www.postgresql.org )。有一种神话认为 PostgreSQL 很慢。这是有一定道理的——在上世纪 90 年代。
然而,从 PostgreSQL 7 开始,PostgreSQL 已经是一个顶级的表现者,并且它只是随着每个版本变得更好。此外,PostgreSQL 在复杂查询方面一直表现出色,今天依然如此。PostgreSQL 的目标是无限制编程。例如,在 PostgreSQL 文本列中,您可以在单行的一列中存储高达 4gb 的数据,并且仍然可以按它进行排序。在许多数据库中,您的大部分时间都花在使数据与数据库的首选架构相匹配上。我发现,使用 PostgreSQL,数据库通常已经为您自己的数据架构做好了准备。
虽然这不是一本关于 PostgreSQL 的书,但我们将讨论它的一些与节点集群相关的特性。
要安装 PostgreSQL,只需以 root 用户身份执行以下操作(本部分的所有操作都应以 root 用户身份执行):
yum install -y postgresql-server
这将安装 PostgreSQL 所需的所有软件包。要设置初始数据库,请键入以下内容:
postgresql-setup initdb
这将创建 PostgreSQL 运行所需的所有目录和文件。接下来,我们需要设置连接到 PostgreSQL 数据库的身份验证方法。PostgreSQL 将其数据和配置存储在目录/var/lib/pgsql/data中。控制数据库访问的文件是pg_hba.conf。
编辑该文件(键入nano /var/lib/pgsql/data/pg_hba.conf)以在顶部添加以下两行:
local all all trust
host all all all md5
第一行表示信任所有来自本地的连接(即,不通过网络)。因此,我们在命令行上直接处理数据库时不需要密码。第二行表示任何人都可以使用适当的密码通过网络连接到数据库。这有点不安全(我们不希望任何人都能连接到我们的数据库),除了默认情况下数据库只监听本地地址 127.0.0.1,所以现在无论如何你不能从外部连接到它。确保保存文件,然后退出编辑器。
请注意,即使我们有适当的限制(仅限本地连接、防火墙等)。),许多人会认为前面的配置过于暴露,不符合他们的喜好。这里的措施是为了平衡安全性和易学性。要了解更多关于保护 PostgreSQL 的信息,您应该阅读关于pg_hba.conf文件的 www.postgresql.org 上的文档。
现在是时候打开我们的数据库了。为此,请输入以下内容:
systemctl enable postgresql
systemctl start postgresql
您的数据库系统现在已经启动并运行。您已经创建了数据库系统,但是没有创建数据库本身。然而,首先我们需要一个数据库用户。createuser命令创建一个新的数据库用户:
createuser -U postgres -d -P gbuser
该命令以数据库管理员用户(-U postgres)的身份运行,创建一个名为gbuser的新用户,他可以创建数据库(-d),并提示您为这个新的数据库用户(-P)设置一个密码。当它提示您时,将密码设置为您想要的任何值,并记下它,以便以后使用。我们将在本书中需要的地方使用密码mypassword,但是请注意,这将是一个在生产中实际使用的可怕密码。当使用命令行时,您不需要使用密码,因为我们已经将它设置为trust,但是当从您的应用连接时,您将需要它。
要以此用户身份创建数据库,请键入:
createdb -U gbuser guestbookapp
这将创建一个新的数据库作为给定的数据库用户。现在,为了创建表,我们需要登录数据库。psql命令将为您提供一个到数据库的交互式 SQL 会话。要使用它,只需键入:
psql -U gbuser guestbookapp
命令提示符会切换到类似于guestbookapp=>的状态,这表示您在数据库中。要随时退出,可以键入\q。像许多系统管理命令一样,当您运行 PostgreSQL 命令时,它并不真正关心您在文件系统中的位置。它与运行在自己目录中的数据库服务通信。
现在我们已经连接到数据库,我们将使用以下命令创建一个表:
create table gb_entries(id serial primary key, name text, email text, message text, created_at timestamp,
has_img bool default false);
id字段是用类型serial创建的,这是 PostgreSQL 的自动编号机制。要查看您刚刚创建的表格,请键入\d gb_entries。完成后,进入\q退出数据库。
4.2 The 代码
在我们编写 PHP 代码之前,我们需要安装一些额外的 PHP 库,以便我们可以连接到我们的数据库。将它们安装在:
yum install -y php74-php-pgsql php74-php-pdo
systemctl restart php74-php-fpm
我们的应用很简单:
-
一个保存配置信息和常用功能的文件
-
一个显示留言簿条目列表的文件
-
一个显示个人留言簿条目的文件
-
一个文件用于输入新的留言簿条目
-
CSS 样式表
这本书假设你知道一点 PHP 和 SQL,但即使你不知道,代码应该足够简单,无论你熟悉什么语言。因此,文件将在此呈现,不做过多评论。你会在本章末尾找到包含代码的数字。
图 4-1 显示了其他文件包含的常用功能。它有两个获取数据库连接的函数——一个用于获取只读连接,一个用于获取读/写连接。在这一点上,它们都返回相同的连接(并且它们确实都是读/写的),但是随着我们进一步开发应用,我们将会看到如何通过分离只用于读的连接和用于读和写的连接来获得很多好处。这两个函数都简单地使用 PDO (PHP 数据对象)通过连接字符串获取到数据库的连接。请注意,这些连接字符串包含数据库的密码。确保将这些连接字符串的和上的密码更改为您之前为gbuser数据库用户输入的密码。
它还有getHeader()和getFooter()函数,所以我们不必写太多的 HTML。另外,h()被用作htmlspecialchars()的一个较短版本,这样我们可以有更安全的输出。
图 4-2 是列出数据库中所有条目的 PHP 脚本。这段代码只是创建一个数据库语句,执行它,并遍历结果。
图 4-3 显示了从数据库中获取单个条目。同样,只准备和执行一条 SQL 语句,结果显示在屏幕上。
图 4-4 简单地显示了一个将被用来创建一个新的留言簿条目的表单。该表单将其数据发布到图 4-5 中的程序中。该程序根据在中输入的值创建新记录。然后,在执行 SQL insert 语句后,它将用户重定向回清单屏幕。
最后,图 4-6 是一个静态 CSS 文件,为整个过程提供了少量的样式。正如在第一章中提到的,如果你不想自己输入所有这些文件,你可以从 GitHub 获得它们:
https://github.com/johnnyb/cloud-example-application
要在服务器上直接使用 git,您需要以 root 用户的身份安装它:
yum install -y git
输入所有程序后,将它们发送到新服务器上的/var/www/html文件夹中。然后,将你的浏览器导航到 http://MY.IP.ADDRESS.HERE/list.php ,看看你的程序是否工作!如果没有,您可以通过以 root 用户身份登录并使用以下命令查看日志文件来检查 PHP-FPM 的错误日志:
tail -200 /var/opt/remi/php74/log/php-fpm/error.log
这将给出 PHP 错误日志的最后 200 行。
发出以下命令可以访问另一个日志,它将为您提供有用的信息:
tail -200 /var/opt/remi/php74/log/php-fpm/www-error.log
修复所有错误,然后重试。最可能的错误是程序中键入了错误的内容,或者连接字符串中列出的密码与您为 PostgreSQL 用户设置的密码不匹配。
查找错误消息的另一个地方是 web 服务器的错误日志。您可以使用以下命令查看该日志的结尾:
tail -200 /etc/httpd/logs/error_log
如果需要,可以在附录 C 中找到其他故障排除步骤。如果一切顺利,您应该会看到一个屏幕,提示您创建一个新条目。点击这个链接会给你一个要填写的表格。填写完表格后,点击“提交”会将新的留言簿条目添加到列表中。然后,您可以单击单个条目来查看完整信息。如果您的应用不这样做,那么可能是输入了错误的内容。
应用的局限性
本书的目标是让你快速了解如何扩展你的应用。因此,其他重要的开发方面,如错误处理、日志记录、净化数据和安全加固,都没有涉及。目标是传达一个应用,它可以快速输入,易于完全理解,并且不需要很深的平台知识来跟随或修改。尽管如此,我们还是实现了一些基本的安全实践,比如使用bindValue对通过$_GET和$_POST发送的值进行适当的转义,并在将它们发送回用户时使用htmlspecialchars()对它们进行转义。
在 www.owasp.org 网站上可以找到关于安全编程的有用信息。
图 4-6
CSS 文件(guestbook.css)
图 4-5
创建留言簿条目(create.php)
图 4-4
新留言簿条目(new.php)
图 4-3
单个留言簿条目(single.php)
图 4-2
列出所有留言簿条目(list.php)
图 4-1
配置和常用功能(common.php)
五、设置基本云集群
此时,我们有了一个简单的云应用,它运行在一台服务器上。虽然我们可以将它放在我们只需点击就能构建的服务器上,这很好,但这并没有充分利用云。云的目标之一是创建一个应用集群——一组协同工作来解决问题的服务器,其中的计算能力可以根据需要进行扩展和收缩。
5.1 简单的双层架构
在本章中,我们将探索一个简单的两层架构。该架构将包括
-
数据库服务器
-
一组 web 服务器
-
管理 web 服务器之间流量的负载平衡器
图 5-1
一个简单的双层体系结构图
该架构的基本结构如图 5-1 所示。所有的连接都进入一个负载平衡器,它的工作是将连接转发到几个 web 服务器中的一个。负载平衡器不仅转发连接,而且还监视各个 web 服务器的健康状况,如果 web 服务器停止响应,它将停止发送 web 服务器连接。每个 web 服务器共享一个数据库服务器。
在开发云应用时,程序员不仅需要知道如何建立集群,还需要知道如何分析集群。如果你看一下图 5-1 中的图表,你可以看到所有的网络服务器都依赖于一个数据库服务器。这使得数据库服务器成为集群的限制因素。几乎每个集群,不管设计得多好,都有一些限制因素。目标是最小化它们对您的架构的影响。
因为这种应用体系结构受单个数据库节点的限制,所以它最适合用于中小型部署,在这种部署中,大部分处理是在 web 服务器上完成的,而不是在数据库上。示例应用因为简单,实际上在 web 服务器上只做很少的处理。尽管如此,本章将向您展示如何设置服务器以在这种配置中部署它。
5.2 复制节点
图 5-1 中描述的基本双层应用表明我们将需要几个服务器节点。我们可以通过使用 CentOS 的空白副本启动新机器,并单独配置每台机器来实现这一点。然而,由于我们已经花了时间来设置当前的服务器,使其按照我们希望的方式工作,我们应该利用我们花在配置所有东西上的时间。
Linode 提供了几种可以完成这项任务的服务,每种服务都有自己的优点和缺点。Linode 能够创建保存的映像,这些映像可用于直接创建新节点(您可以选择保存的映像,而不是选择操作系统)。Linode 还有一个克隆服务,如果源机器和目标机器都关闭,它允许您克隆现有的机器(这不是一个硬性规定,但是如果您试图克隆一个正在运行的机器,您可能会遇到一致性问题)。最后,您可以从机器的备份中进行克隆。
我更喜欢从备份中进行克隆,因为(a)无论如何你都应该定期备份你的服务器,(b)在你创建新的服务器时,你不必关闭机器什么也不做,以及(c)这迫使你在学习克隆服务器的同时使用备份系统并适应它(有一天你将需要从备份中恢复,所以在你需要它之前熟悉这个过程是很好的)。Linode 图像服务可以解决这个问题,但是对于实际生产应用来说,它有太多的限制。为了防止用户将图像服务用作备份服务,他们限制了图像的大小和数量,但我的大多数机器通常都比 Linode 图像允许的最小大小大。请注意,其他云服务(如 DigitalOcean)提供类似的服务,但有不同的限制。
如果我们想要启动一台与现有服务器完全相同的新服务器,那么我们只需备份当前服务器。我们需要做的是首先在我们的服务器节点上启用备份。要做到这一点,只需登录到 Linode,在列表中单击您的节点,然后在节点的仪表板上单击“Backups”选项卡。这将为您提供一个按钮,上面写着“启用该节点的备份”这增加了一个小的月费,但它肯定是值得的。单击该按钮,您的计算机将自动拥有可用的每周、每天和临时快照。屏幕现在看起来应该类似于图 5-2 。
图 5-2
Linode 备份管理屏幕
我们现在要做的是拍摄我们机器的快照。为您的备份输入一个名称(我将我的命名为“用于复制的快照”),然后单击“拍摄快照”按钮因为 Linode 服务器都在固态硬盘上,所以快照非常快,通常只需要 5-10 分钟。
其他备份设置
因为备份实际上会降低磁盘速度,所以 Linode 允许您指定备份窗口。您可以选择一天中要进行备份的时间,也可以选择一周中的某一天作为“每周备份”一天中的时间应该是系统使用率最低的时候,而一周中的某一天应该在任何主要批处理之前。例如,如果您在星期五进行批处理,将备份日期设置为星期四将确保您在主要处理发生之前有一个“之前”的快照。如果不进行大规模批处理,每周备份的日期并不重要。
备份开始后,我们可以进入下一个任务,即创建新的服务器。新服务器将成为我们的数据库服务器。
如我们在第三章中所做的那样,要创建机器,先进入“创建”,然后进入“Linode”。但是,在“映像”标题下,您需要选择“我的映像”,然后选择“备份”,而不是选择发行版这将显示具有可用备份的节点列表。单击您的节点,然后它将显示可用的备份,其中包括我们刚刚创建的备份。参见图 5-3 来看看这个应该是什么样子。
您可以根据自己的需要创建尽可能大的机器,但是为了便于练习,您最好使用最小的机器尺寸。这个节点需要和你的另一个节点在同一个数据中心(否则他们不能私下地、廉价地、快速地互相交谈),但是 Linode 会自动把一个从备份创建的新节点放到同一个数据中心。
对于 Linode 标签,让我们使用名称dbmaster,这样我们就知道这个节点将用于主数据库。拥有一堆没有名字(或者名字不好)的节点会很快变得难以管理,所以一定要确保总是给你的节点起描述性的名字。
图 5-3
从备份中引导新的节点
现在,单击“创建”来构建您的新节点。从备份创建时,一旦节点完全创建,您必须自己引导节点。在屏幕的右上角,应该显示“离线”点击它,然后选择“开机”来启动你的新机器。现在,您可以使用为初始机器设置的相同用户和密码ssh进入这台新机器。事实上,它还应该运行您创建的应用。它是另一台机器的精确副本,只是修改了网络设置。
恢复到更大的机器
如果您将备份恢复到较大的计算机,它可能不会利用您购买的所有磁盘空间。由于分区是直接复制的,旧分区的大小将与其在复制它的服务器上的大小相同,这可能小于您的可用空间。
要解决这个问题,首先关闭服务器电源。接下来,在节点的仪表板上,单击“高级”选项卡。您可以添加另一个磁盘来使用剩余的空间(这很难管理,所以我不推荐),或者调整主磁盘的大小来利用所有的空间。若要调整磁盘大小,请在“磁盘”下找到主磁盘它应该被命名为“CentOS 7 磁盘”(而不是标有“交换映像”的磁盘)之类的东西。点按主盘旁边的省略号(即...),然后选取“调整大小”然后,您可以将大小设置为它告诉您的最大值,然后单击“调整大小”按钮。
当它完成尺寸调整后,你可以重新启动你的机器,现在你已经准备好了。
5.3 设置您的专用网络
既然我们有两台机器,我们需要它们进行通信。他们可以通过他们的公共 IP 地址通信,但是这导致了几个问题。首先,如果你有不想公开的服务(比如你的数据库),如果你只有一个公开的 IP 地址,就更难阻止公众获取这些服务。此外,Linode 对你的公共 IP 地址上的流量收费,所以,如果你通过那个 IP 地址通信,Linode 会对你的内部流量收费。因此,拥有一个私有 IP 地址是很重要的,因为它能让计算机通过一个快速、自由、更安全的内部网络相互通信。
为了解决这些问题,Linode 允许你为你的服务器建立一个内部网络。一个帐户中的所有服务器共享一个内部网络(如果它们是为该网络设置的)。要将服务器添加到您的内部网络,您只需进入节点的“网络”选项卡,然后单击“添加专用 IPv4”它会给你一些关于私有地址的附加信息,你可以点击“分配”继续。这将为计算机分配一个专用 IP 地址。因为所有的 web 服务器都将与该服务器通信,所以您需要记下生成的私有 IP 地址。从现在开始,我们将把这个地址称为DB.MASTER.PRIVATE.IP,所以总是用您刚刚记下的服务器的私有 IP 地址来替换它。
注意,在一些云平台上(包括 Linode),私有 IP 地址并不是完全私有的。也就是说,同一数据中心的其他云客户可能也在该网络上。因此,虽然私有网络肯定比公共网络更安全,但是云上的私有网络并不能保证只有我们自己的计算机在连接。因此,在生产系统上,您仍然需要采取预防措施来防止不必要的访问,即使是在内部网络上。然而,Linode 确实会过滤每个节点的流量,因此您不必担心有人会窥探内部网络上的数据流量。
您需要重新启动节点才能完成该过程。
一旦启动完成,您仍然可以在 http://NEW.NODE.PUBLIC.IP/list.php, 看到应用,但是您将无法在私有 IP 地址上看到它,因为如上所述,它是私有的。
要查看服务器上的地址列表,请以 root 用户身份登录并发出以下命令:
ip addr show
这应该会打印出分配给该节点的所有 IP 地址。
私有 IP 地址
如果您不熟悉 IP 寻址,一些 IPv4 地址已被保留用于内部网络。这些地址包括
-
192.168.X.X -
172.16–31.X.X -
10.X.X.X
这些 IP 地址都不允许用于互联网上的公共通信。
因此,在设置您的内部网络时,Linode 将从这些地址池中选择 IP 地址来配置您的机器。
另一个臭名昭著的 IP 地址是127.0.0.1,它被称为环回地址,这是一个机器可以用来指代自己的 IP 地址(实际上,整个范围127.X.X.X都是为此保留的,但通常只使用127.0.0.1)。
5.4 处理来自其他服务器的数据库连接
这台机器上现在有了应用和数据库的完整副本。但是,它仍然被配置为单服务器系统。我们需要将其配置为机器集群的主数据库。这一部分应该以 root 用户的身份执行,它将展示您需要做些什么来实现这一点。
由于这台机器是template_node的克隆,这意味着所有的用户、程序和配置都被复制到这个节点上。因此,您可以使用之前设置的密码以 root 用户身份ssh进入机器。
要将它用作其他节点的数据库服务器,您需要使数据库能够侦听来自这些节点的连接。默认情况下,PostgreSQL 只监听本地主机接口上的连接。我们不希望 PostgreSQL 监听公共互联网上的连接。因此,我们希望配置 PostgreSQL,使其监听本地主机及其私有 IP 地址上的连接。
要做到这一点,作为根用户,用nano打开文件/var/lib/pgsql/data/postgresql.conf,并修改显示为listen_addresses的行。将该行改为:
listen_addresses = 'localhost,DB.MASTER.PRIVATE.IP'
确保用节点的实际私有 IP 地址替换DB.MASTER.PRIVATE.IP。如果行首有注释标记(#),一定要去掉;否则,该命令将不会被激活。用 control-o 保存文件(只要按回车键来验证文件名,如果它问)。然后,要退出,请使用 control-x。现在,使用以下命令重新启动 PostgreSQL:
systemctl restart postgresql
此外,您将希望打开防火墙,以便它可以接受 PostgreSQL 的远程连接。您可以通过以下方式实现这一点:
firewall-cmd --add-service postgresql
firewall-cmd --add-service postgresql --permanent
通常,我让 web 服务器在数据库服务器上运行,这样我就可以检查它。但是,如果您愿意,可以使用以下命令关闭 web 服务器:
systemctl stop httpd
systemctl disable httpd
5.5 设置 Web 服务器
现在我们已经配置好了数据库,是时候设置我们的 web 服务器了。
请注意,我们实际上不会使用template_node作为服务器。我喜欢在身边放一个小机器,简单地用作未来盒子的模板,尤其是 webnodes。这样,我可以拥有一台最新的、已备份的小型计算机,当我准备好创建新的“映像”时,我只需创建一个命名备份即可使用。请注意,这将覆盖以前的快照映像,但对于我的目的来说,这通常是可以的。如果您需要保留旧版本,只需为您想要维护的每个配置保留一个模板节点。
现在我们将配置template_node成为一个 web 服务器模板。我们只需要做三点改变:
-
关闭该计算机上的数据库。
-
更改 web 应用的代码以指向我们的新数据库。
-
在机器上启用专用 IP 地址,使其能够使用专用网络。
为了完成第一个任务,我们需要做的就是以 root 用户身份登录到template_node机器并运行:
systemctl stop postgresql
systemctl disable postgresql
为了完成第二个任务,我们只需要修改common.php文件。我建议在您的本地机器上修改它,并使用 SFTP 传输新文件。不过也可以在服务器上用nano直接修改。您所要做的就是更改连接字符串。在当前显示为host=localhost的地方,将其改为host=DB.MASTER.PRIVATE.IP,其中DB.MASTER.PRIVATE.IP是您的dbmaster节点的私有 IP 地址。您需要进行两次修改——一次在getReadOnlyConnection()函数中,一次在getReadWriteConnection()函数中。完成后,用 SFTP 将代码加载回服务器。
此时,代码将不起作用,因为它不能访问dbmaster机器。这是因为 PostgreSQL 是唯一的监听它的私有 IP 地址,而template_node还没有私有 IP 地址进行通信。因此,您需要给机器添加一个私有 IP 地址,这样它就可以通过它的私有 IP 地址连接到dbmaster。
使用第 5.3 节中概述的过程为机器创建一个私有 IP 地址(不要忘记之后重新启动!).
一旦你完成了这些步骤,你的template_node机器应该能够连接到dbmaster,所以测试一下。找到你的template_node服务器的 IP 地址,看看它是否还能工作。如果是的话,那么恭喜你,因为你刚刚实现了一个小的、两层的系统!
现在,正如我前面说过的,我们实际上不会使用template_node来服务请求。我们的目标是使用它,以便我们可以轻松地启动新的 web 服务器节点,根据需要扩展容量。
因此,既然template_node已经完全设置为一个 web 服务器,那么创建一个新的备份快照。这一步至关重要。每当您在template_node中进行更改时,您应该创建一个新的备份快照,这样您从它创建的新节点将会有您的新更改(尽管它根本不会影响现有的节点)。
我们现在将为我们的集群创建三个(或者任意多个)web 服务器节点。
以下是每个新 web 服务器节点的步骤:
-
使用第 5.2 节中的步骤创建一个新节点。确保(a)它是在与
dbmaster相同的数据中心创建的,并且(b)将节点的名称设置为webnode1(或者 2,或者 3)。 -
使用第 5.3 节中的步骤,为节点添加一个私有 IP 地址,使其可以连接到私有网络上的
dbmaster。 -
机器完成启动后,通过查看节点的公共 IP 地址(即
http://WEB.NODE.PUBLIC.IP/list.php).) )上的 web 应用,验证其功能是否完整
最后,您应该有三台机器,webnode1、webnode2和webnode3,每一台都可以作为您的 web 应用的前端。现在您只需要将它们连接在一起,这将在下一节中介绍。
5.6 设置负载平衡器
现在我们有三台前端机器,都连接到一个数据库。我们如何将它们连接在一起?我们可以这样做的一个方法是建立一个 DNS 循环方案。其工作方式是在 DNS 中为一个主机名设置多个记录。然后,浏览器自己会选择要连接到哪个主机。这样做的问题是,如果您的一台机器出现故障,就没有办法引导用户离开该 IP 地址。Linode 现在实际上支持这种故障转移,但是它的用法超出了本书的范围。
图 5-4
创建节点平衡器
负载平衡器是一个更好的解决方案。负载平衡器位于您的集群前面,为您获取连接,然后将这些连接转发给可用于处理它们的服务器。此外,如果其中一台服务器出现故障,负载平衡器会检测到这一情况,并将流量转移到其余服务器上。然后,当您的服务器恢复时,负载平衡器也会检测到这一点,并将流量移回服务器。
在 Linode 中设置负载平衡器很容易。Linode 将其负载平衡器称为“节点平衡器”要设置节点平衡器,请单击“创建”,然后选择“节点平衡器”菜单项。这将带您进入图 5-4 所示的屏幕。
就像其他事情一样,你需要
-
设置平衡器的名称(我们将使用“主平衡器”)。
-
将平衡器放在与节点相同的数据中心。
此外,您需要添加一些额外的配置,如图 5-5 所示。确保选择了以下选项:
-
“端口”应设置为“80”
-
“协议”应设置为“HTTP”
-
“算法”应设置为“循环法”
-
“会话粘性”应设置为“无”
-
“主动健康检查”应设置为“无”
-
“被动健康检查”应该打开。
之后,您需要向您的平衡器添加至少一个节点(可以在创建平衡器之后添加更多节点)。在“标签”字段中输入节点的名称(我们假设为webnode1)。然后,在“IP 地址”字段中,从下拉列表中为您的节点选择 IP 地址。确定端口设置为“80”
完成这些设置后,单击“创建”创建平衡器。
创建平衡器后,您可以通过进入“配置”,然后进入“端口 80”,然后进入底部的“添加节点”来添加其余的节点。添加您创建的任意数量的节点。完成后点击“保存”。
平衡器将节点添加到列表中有时需要几分钟时间。要检查状态,请返回到节点平衡器配置屏幕。每台服务器旁边都有一个“状态”字段。当状态为“启动”时,服务器成功连接到平衡器。
如果您想为另一个端口配置一个负载平衡器,您可以使用“添加另一个配置”按钮。
图 5-5
节点平衡器附加设置
其他节点平衡器选项
节点平衡器有许多选项可用。下面是一些重要的描述。
端口:这是节点平衡器应该转发的 TCP 端口。这通常是 80 (HTTP)或 443 (HTTPS)。在我们的例子中,我们将使用端口 80。
协议:这是您希望服务器处理转发请求的方式。如果协议设置为 TCP,那么平衡器所做的唯一的事情就是将连接转发给你。如果它被设置为 HTTP 或 HTTPS,那么服务器实际上会为您处理连接的某些部分。HTTP 是我们将在本书中使用的。HTTPS 为安全站点增加了额外的支持,因为负载平衡器将为您处理 SSL 连接,从而从服务器上移除了大量负载(您还将证书和密钥信息上传到负载平衡器进行处理)。在进行 HTTPS 时,您可能希望平衡器上的端口不同于机器上的端口。在 HTTPS 的情况下,您应该将平衡器设置为连接到服务器上的未加密端口 80。如果你想做由你的机器而不是平衡器处理的 HTTPS,你可以选择 TCP(而不是 HTTPS)作为协议。由于获取和安装证书的复杂性,对于本书中的示例,选择端口 80 和 HTTP 进行负载平衡。
算法:这是负载均衡器决定将其转发给哪个服务器的方式。循环调度是默认设置,应该可以正常工作。
会话粘性:这是一个给定的用户是否应该继续连接到相同的网络服务器,一旦它已经建立了初始连接。只有当你在你的网络服务器上存储会话信息时,这才是重要的*。想象一下,如果您的 web 服务器有重要的会话信息,但是下一个请求发送到不同的服务器!因此,此选项允许您配置客户端是否以及如何与服务器匹配。我们的应用没有会话信息,所以应该设置为“None”如果您的应用使用本地会话信息(或本地缓存,我们将在 6.1 节中看到),我会选择 HTTP Cookie 方法,因为它不会像“Table”方法那样妨碍您的负载测试。因为所有的请求都来自同一个 IP 地址,所以“Table”方法只是将整个负载测试指向一台服务器,而不是将其分散开来,看起来好像负载平衡没有给你任何帮助。*
主动健康检查:这允许您为负载平衡器指定一个 URL,以便检查 web 服务器的状态。您可以将平衡器配置为只检查 HTTP 状态,或者在响应正文中查找特定的字符串。
后端节点:权重:为该服务器设置节点平衡器的首选项。权重越高,服务器连接越多。
后端节点:模式:设置平衡器中节点的模式。“接受”用于正常操作。“拒绝”实际上是关闭该节点,这意味着平衡器不会再向该节点发送任何请求。但是,如果打开了会话粘性,您可能不希望直接从“接受”转到“拒绝”“Drain”告诉平衡器仅接受来自在该节点上有会话的客户端的连接。“备份”表示只有当所有其他节点都关闭时才接受连接。
将所有 webnodes 添加到节点平衡器后,现在可以通过查看节点平衡器本身的 IP 地址来查看集群,节点平衡器会将您的请求转发到集群中的一台机器。您可以通过单击主菜单中的“节点平衡器”找到您的节点平衡器的 IP 地址。IP 地址将列在列表中您的平衡器旁边。它也列在节点平衡器“摘要”屏幕的右侧。
5.7 测量可扩展性
我们开发的简单 web 应用并没有从这里介绍的架构中获益太多。我们的 web 应用只是一个简单的外壳,用于一些数据库查询。因此,简单地添加前端设备无助于解决我们的应用受数据库限制的问题。将数据库从 web 服务器中分离出来会给我们带来一些帮助,因为它允许数据库服务器只关注数据库连接。然而,在核心上,我们在这个应用中所做的一切只是围绕数据库查询的一个薄薄的包装。
但是我们如何衡量我们应用的可伸缩性呢?衡量应用可伸缩性的一个常见的简单工具是 ApacheBench。ApacheBench 是 Macintosh 和大多数 Linux 发行版的标准。对于我们的例子,我们实际上可以从我们的template_node服务器运行 ApacheBench 来测试集群的其余部分。
为此,以root或fred的身份登录到您的template_node服务器。要运行一个简单的 ApacheBench 会话,只需键入:
ab http://BALANCER.IP.ADDRESS.HERE/list.php
显然,用负载平衡器的 IP 地址替换BALANCER.IP.ADDRESS.HERE。
这将向负载平衡器发送一个请求,并记录处理该请求所花费的时间。输出将类似于图 5-6 。
由于这只是对单个请求进行了基准测试,因此其中没有太多有趣的信息。它说这个请求花费了 9.674 毫秒,推断出来,它估计我们每秒可以处理 103.37 个请求。
现在,ApacheBench 有几个选项可以让我们更充分地使用服务器。-n选项告诉 ApacheBench 要发出多少个请求(默认情况下我们发出了 1 个)。-c选项告诉 ApacheBench 要建立多少个并发(即同时)连接。如果没有-c,ApacheBench 将一次只运行一个请求。但是,如果您添加了-c 50,ApacheBench 将始终保持对 web 服务器的 50 个活动请求。
因此,为了练习集群,我做了以下工作:
ab -c 50 -n 1000 http://BALANCER.IP.ADDRESS.HERE/list.php
这总共发送 1,000 个请求,确保每次总是有 50 个活动的请求。结果如图 5-7 所示。
这表示有 50 个并发请求时,我们每个请求的平均时间下降到 5.136 毫秒,但是每个请求的平均时间增长到 256.822 毫秒。这实际上不是问题,因为每个请求的平均时间对于容量规划来说是最重要的。我们还被告知,按照这个速度,我们的服务器每秒可以处理 194.69 个请求。
图 5-7
1,000 个请求的 ApacheBench 结果
图 5-6
单个请求的 ApacheBench 输出示例
这听起来很棒,除了当您对单台机器进行测量时(您可以用您机器的 IP 地址之一替换 IP 地址),您会得到接近相同的结果。当并发请求数量激增时,负载均衡器可以处理稍高的压力,但总的来说,它们的结果基本相同。这是因为我们的应用几乎完全只是数据库的一个外壳。因此,这个数字反映了我们数据库服务器的最大容量。为了验证这一点,您可以暂时将数据库服务器的大小调整到更大的机器上。
要调整您的dbmaster服务器,进入dbmaster的仪表板,点击“resize”选项卡,并选择一个新的计划(我选择了 Linode 4GB)。现在点击“立即调整这个节点的大小”停机几分钟后,你的 Linode 会重新调整大小,它会保留它的 IP 地址!一旦它完成调整,它将启动,你现在将有一个调整后的服务器!
现在,您可以在这个配置上运行 ApacheBench,并看到调整数据库服务器的大小为您提供了比以前的配置更大的优势。在做了这个实验之后,我已经将dbmaster的大小调整回了一个毫微级 1GB,这样我们就可以更好地看到后面章节中描述的架构的效果。
如果应用不是如此依赖数据库,我们应该已经看到了这种架构的一些扩展优势。然而,即使存在数据库瓶颈,第六章也将着眼于架构的改进,这将提供显著更好的伸缩能力。这里的目标仅仅是看看架构是如何工作的,以及如何在 Linode 上设置它。
很高兴看到,仅仅因为您可以向系统添加节点,并不意味着它可以自动伸缩。
六、通过缓存提高可伸缩性
本章将介绍对前一章中描述的基本两层体系结构的一些调整。
6.1 了解缓存架构
在我们当前的应用中,数据库是主要的瓶颈。这意味着添加更多的 web 服务器不会显著增加云能够处理的负载。当您发现一个瓶颈时,最好花些时间考虑如何避免这个瓶颈。
在我们的例子中,我们的数据并不经常改变。即便如此,从留言簿中获取最新的数据也不是那么重要。如果有人需要等待几秒甚至几分钟才能看到最近的留言簿条目,那也不是世界末日。
当您有经常被访问的内容,但可以承受稍微陈旧(或非常陈旧)的内容时,您可以实现缓存来加快速度。缓存仅仅意味着拥有一个我们可以快速访问的结果的临时存储。数据库很慢,因为数据库主要关心数据完整性。您希望知道您的数据是安全的,存储在磁盘上,不会消失,并且可以通过任意查询进行访问。另一方面,缓存是短暂的——它们通常只是存储在内存中。他们的目标是不惜一切代价快速检索数据。例如,许多缓存如果填满了内存,就会开始丢弃数据。这没关系,因为如果缓存中不存在某个东西,我们可以去数据库重新获取数据。
简而言之,数据库关乎持久性和可靠性,而缓存关乎尽可能快地获取我想要的东西。缓存通常被实现为简单的键/值对,通常附有截止日期。也就是说,缓存存储的每一段数据都有一个指定的“键”例如,由于所有留言簿条目只有一个列表,我们可以为此使用一个缓存“键”。在应用中,这将被称为entrylist。但是,每个单独的留言簿条目可能最终会获得自己的缓存键(这样缓存就可以知道它是唯一的),这将包括条目的数据库 ID。缓存键通常只是普通的字符串,对于大多数缓存系统,内容可以是任何东西。缓存不做任何特殊的处理,它们只是在一个给定的键中存储你要求的任何东西,所以你必须小心不要用同一个键做两件不同的事情!
使用缓存的方式是,程序将首先确定对数据使用什么键。目标是能够很容易地从 URL 参数中推断出缓存键。程序首先检查缓存中是否有缓存键值。如果缓存包含该值,则使用该值。如果没有,程序就以“正常”的方式获取该值(即,通常从数据库中获取该值,可能还要进行一些额外的计算或操作),然后将该值保存到缓存中,并注明到期日期。然后,无论该值是来自缓存还是“正常”方式,该值都会在应用中使用。一旦过期(或者缓存中的值太满),缓存的值就会在下一次查询中消失,迫使程序以“正常”的方式获取一个新值。
图 6-1
具有本地缓存的双层架构
正如您所看到的,这种缓存与普通访问的方法确保了虽然缓存机制是首选,但非缓存机制也是可用的,并且该机制将填充缓存。
如上所述,数据库通常被认为是任何应用中速度较慢的部分,因为它必须非常小心地正确处理您的数据并永久保存这些数据。另一方面,缓存被认为是短暂的。如果我们只是想突然清空缓存,这不会影响页面的操作,因为它会返回到数据库并再次获取值。
如果一个站点负载很重,并且不断地请求相同的内容,即使缓存几秒钟也能大大提高速度,减少资源使用。我们之前在清单页面上对这个集群进行了负载测试,每秒钟可以处理大约 250 个请求。如果我们将该页面的结果缓存 10 秒钟,那么在这段时间内会减少 2,500 个数据库请求!
图 6-1 展示了我们如何修改我们的标准双层架构来添加一个缓存层。在这种架构中,每个单独的 web 服务器维护自己的结果缓存。这可能会导致 web 服务器之间的细微不一致,因为它们可能在不同的时间刷新了结果。但是,如果到期时间没有设置得太远,这些问题是最小的。此外,如果有问题,还记得我们在 5.6 节中讨论的负载平衡器“粘性”吗这将特定的用户与特定的服务器联系在一起,这意味着用户将始终在同一服务器上,这意味着他们将始终使用同一服务器缓存。此外,这使得缓存更有效,因为每个服务器只需缓存用户子集的数据。
图 6-2
具有全局缓存的双层架构
缓存什么以及缓存多长时间取决于应用。对于许多架构来说,有些部分可以缓存几分钟甚至几天,而有些部分只能缓存几秒钟或者根本不能缓存。缓存经常会导致意想不到的结果,所以在开发时最好包含一个可以用来完全关闭缓存的特性,以便查看缓存是否是问题所在。
如果保持所有服务器之间的缓存一致很重要,另一个可以考虑的架构是使用全局缓存,如图 6-2 所示。在这种体系结构中,不是每个 web 服务器维护自己的缓存,而是有一个它们都可以访问的共享缓存。这增加了一点网络延迟,因为所有的缓存调用都必须在网络上运行,但它增加了结果的一致性。
根据实现方式的不同,这种外部缓存也可能成为瓶颈。但是,有许多缓存服务器可以跨多个服务器,并在服务器之间平衡请求。尽管如此,这也增加了管理的复杂性。
几乎在所有情况下,我发现拥有一个单一的外部缓存服务很难设置和维护,几乎没有任何好处。在大多数情况下,将缓存放在每个 web 服务器上可以获得最大的可伸缩效率,即使这是以一点一致性为代价的,正如前面提到的,这可以通过使用负载平衡器的“粘性”来缓解
6.2 在应用中实现缓存
我们在这里要实现的缓存架构如图 6-1 所示,这既是因为它更容易实现,也是因为从长远来看它更容易管理。我们要做的是在template_node机器上执行配置更改,然后简单地关闭我们现有的webnodeX机器,并用从template_node复制的新机器替换它们。这比遍历每台服务器并进行更改要容易得多(也不容易出错)。
我们需要做的第一件事是安装缓存服务。我们将使用memcached作为我们的缓存服务,因为它易于运行、访问和管理。要安装并打开memcached,只需以 root 用户身份输入以下命令:
yum install -y memcached
systemctl enable memcached
systemctl start memcached
您还需要用于memcached的 PHP 扩展。这些必须被编译,所以我们需要安装更多的扩展(再次以 root 用户身份)。
图 6-3
Memcache 连接函数
yum install -y libmemcached
yum install -y php74-php-pecl-memcached
不要忘记memcached末尾的“d ”,因为还有另一个名为 just memcache的扩展,它不做我们想要的事情。注意,这也启用了 PHP 中的扩展。它为我们做到了这一点,但是如果你需要调整配置的 PHP 端,文件位于目录/etc/opt/remi/php74/php.d中。
现在我们需要重启我们的 PHP-FPM 进程来使用新的 PHP 扩展:
systemctl restart php74-php-fpm
接下来,我们需要修改我们的应用来创建到本地memcached服务的连接。因此,将图 6-3 中的代码添加到common.php中。代码中提到的11211号是memcached默认监听的端口。
您的代码使用缓存的方式如下:
-
创建一个缓存键来唯一标识缓存中的信息。
-
检查信息是否已经存在于缓存中。如果有,那就用吧。
-
如果信息不存在于缓存中,用慢的方式找到信息(例如,执行数据库查询)。
图 6-4
重写
list.php以使用缓存 -
获取信息,并使用具有未来过期时间的密钥将其存储在缓存中(在这种情况下,我们将过期时间设置为从设置时起 10 秒)。
接下来,我们将更新list.php函数来使用缓存。图 6-4 显示了如何重写list.php以使用缓存。这只是在$result中存储查询结果的脚本的顶部。实际的 HTML 输出代码保持不变。
6.3 重新映像集群
现在我们需要将它部署到我们的集群中。为此,我们需要首先对template_node进行另一次快照备份,然后对集群中的每台webnodeX服务器执行以下步骤:
-
转到节点平衡器,从配置中删除
webnodeX服务器。 -
关闭电源并删除
webnodeX服务器(可以从节点屏幕的“设置”选项卡中进行删除)。 -
基于我们的
template_node备份创建一个新的webnodeX服务器(使用相同的名称)(确保添加一个私有 IP)。如有必要,打开电源。 -
将新的
webnodeX服务器添加到节点平衡器配置中。
对于生产系统,这并不总是进行代码或系统更新的最佳方式。我们将在第十二章中介绍更多的方法。这种方法简单地删除所有的webnodeX服务器,并用新的替换它们。这是一个非常干净的机制,尽管您可能希望缓慢地执行它,以确保您的站点在重新映像服务器时保持运行。
6.4 测试我们的缓存架构
一旦您的新云集群启动并运行,就该测试新架构了,看看我们是否获得了任何性能提升。
对于这个设置,我在单独的服务器和平衡集群上运行 ApacheBench。由于不再依赖数据库来解决瓶颈问题,单个服务器每秒能够处理超过 800 个请求!
此外,由于数据库没有瓶颈,性能几乎可以线性扩展。线性缩放意味着您添加的每个框都可以为您带来相同的性能提升。在这种情况下,使用一台服务器,我们的性能是每秒 800 个请求,两台服务器每秒产生大约 1,500 个请求,而在三台服务器上,我们能够持续地每秒处理超过 2,300 个请求!
请注意,如果您没有看到类似的性能提升,请检查您的平衡器配置,以确保您没有在任何时候打开会话粘性,因为这会将您的 ApacheBench 会话限制在一台服务器上。此外,在“设置”选项卡上,确保“客户端连接节流”也没有打开。
图 6-5 显示了 ApacheBench 在完整集群上的输出。
因此,我们了解到,缓存不仅提高了应用的速度,还提高了应用的可伸缩性。因为我们只需在缓存过期时命中数据库,所以数据库的速度现在相对不重要。事实上,即使我们再次加速数据库(就像我们在第五章的结尾所做的那样),它对我们的总效率的影响也相对较小,因为它很少被使用。
不利的一面是,如果你实际使用这个应用,你会发现在你发布留言簿条目后,它不会立即显示在网站上。事实上,如果您快速重新加载页面,您可能会发现它会出现和消失,这取决于您所在的服务器何时刷新其缓存。
这可以通过多种方式来缓解。首先,您可以减少数据缓存的时间。在这个基准测试中,缓存 1 秒钟还是 10 秒钟没有什么区别。因此,只需将缓存过期行更改为以下内容(即,将过期时间设置为1秒而不是10)即可让应用快速刷新,而不会显著影响我们测试的这些大规模负载的性能:
$expiration_seconds = 1;
图 6-5
缓存配置的 ApacheBench 输出
更重要的是,如果您将会话绑定到特定的服务器,您实际上可以告诉服务器清除特定事件的单个键甚至整个缓存。因此,在create.php的末尾,我们可以添加下面几行来清除服务器上的列表缓存:
$cache = getCache();
$cache->delete("entrylist");
或者,如果一个应用足够复杂,需要删除大量的键,代码可以用$cache->flush();清除整个缓存。
无论如何,正如你所看到的,缓存架构会使你的应用变得稍微复杂和难以管理,但从通常戏剧性的性能和可伸缩性提升来看,它们通常是值得的。
缓存调试提示
缓存虽然非常有益,但也带来了一系列问题。为了更好地调试网页,最好总是有一组参数可以传递给应用,让它关闭缓存。例如,在我自己的许多应用中,在 URL 中传递一个no_cache=1会关闭缓存。当问题被报告时,这通常是我第一个去的地方。
以下是您可能遇到缓存问题的一些迹象,以及如何解决这些问题:
-
问题:您的应用正在输出旧的内容,即使数据库中有更新的内容。
诊断:缓存中有过时的内容。
解决方案:让您的内容更快过期,或者提供额外的缓存密钥信息,让缓存知道您何时需要新数据。
-
问题:你的应用在给定参数的情况下发出不适当的数据。
诊断:这种情况经常发生在你的缓存键不够具体的时候。例如,如果内容以错误的语言出现,这可能意味着您需要附加当前语言作为缓存键的一部分。
解决方案:给你的缓存键添加更多的参数,以确保你真正用一个唯一的键识别每一个唯一的内容,或者你可能决定这个内容太具体而不能被缓存。
-
问题:同一个页面每次重新加载都会吐出不同的内容。
诊断:缓存在每台服务器上有不同的内容,这取决于它被访问的时间。
解决方案:有几种方法可以解决这个问题。您可以(a)减少到期前的时间量,(b)增加负载平衡器上的“粘性”,以确保同一个人总是访问同一台服务器(从而访问同一缓存),(c)利用全局(或同步)缓存而不是本地缓存,以及(d)添加额外的缓存关键参数,以更好地协调用户从缓存中获取的数据。
-
问题:缓存并没有像你想象的那样加速你的应用。
诊断:要么你从未命中你的缓存,要么你没有缓存正确的东西。
解决方案:因为有很多方法会出错,所以有很多方法可以修复它。当每个用户访问不同的数据集时,通常会发生这种情况,因此不会从缓存中提取任何内容。这可以通过增加缓存大小和/或智能地将可能被访问的数据预加载到缓存中来解决。您可能还需要增加数据的到期时间。但是,您可能需要对数据进行大量的后处理,这比实际查询花费的时间要长。
七、数据库复制
有些东西根本无法缓存。即席报告、最新的更改以及访问模式分布在大量不相关页面上的站点都很难使用缓存进行优化。对于这样的工作负载,你可以部署一个更大的数据库服务器,但是最终这些也会受到限制。
因此,许多应用架构都包括数据库复制,其中有多个数据库服务器为请求提供服务。
7.1 数据库复制的类型
根据您的需要,有许多类型的数据库复制。复制的基本类型包括
-
故障转移复制:在这种配置中,复制的服务器不帮助加载,但是它们确保如果主数据库服务器停机,有一个具有最新信息的数据库可以接管。
-
主/副本复制:在这种配置中,主数据库是唯一具有读/写权限的数据库。副本服务器接收记录在主服务器上的数据(或稍后接收),但它们是主数据库的只读副本。所有更新都转到主数据库,但查询可以转到主服务器或任何副本服务器。这也称为“主/从”复制,其中副本数据库被视为“从数据库”
-
多主复制:在这种配置中,所有数据库都被视为同等的“主”数据库,可以在其中任何一个数据库上执行写操作。然后,对任何给定数据库的写入将与集群的其余部分同步。
本章将集中讨论主/副本复制,因为它最容易实现,实际问题最少,而且用最少的努力获得最多的结果。多主控复制很少使用,因为它很难设置、维护和保持高效,而且很少有数据库支持它。即使受到支持,多主控复制也经常会引入新的难以解决的问题,例如数据冲突(即,当冲突的数据被提交到两个不同的服务器上时)。因此,为了保持简单,本书将集中讨论主/副本配置。
图 7-1 显示了典型主/副本架构的概念视图。
7.2 复制 PostgreSQL 数据库
这些年来,PostgreSQL 的复制系统在功能和易用性方面都有了很大的进步。虽然使用起来并不困难,但需要一些解释才能理解。
内置的 PostgreSQL 复制系统使用一种称为日志流的技术进行复制。为了保证数据的一致性,PostgreSQL 创建了一个预写日志,简称 WAL。基本上,PostgreSQL 将要做的更改写入 WAL,然后实际执行更改。这意味着,如果数据库服务器在更新过程中关闭,它会记录正在做的事情,并在重新启动时简单地完成操作。
图 7-1
主/副本数据库架构图
有趣的是,这正是复制服务器也需要知道的信息。因此,要实现数据库复制,PostgreSQL 只需将 WAL 文件发送到复制服务器,复制服务器同样会实现更改。这种类型的复制被称为 WAL 流。
为了在我们的集群中实现这一点,我们需要配置我们的主数据库,以便接收复制连接。以 root 用户身份登录dbmaster,编辑文件/var/lib/pgsql/data/postgresql.conf,设置如下设置:
wal_level = hot_standby
wal_keep_segments = 32
max_wal_senders = 4
hot_standby = on
如果您使用的是 PostgreSQL 的更高版本(9.4 或更高版本),您还需要设置:
max_replication_slots = 4
但是,该设置将破坏 CentOS 7.2 附带的 PostgreSQL 版本,我们将在本书中使用该版本。
这些配置更改完成了几件事:
-
wal_level调整 PostgreSQL 的“预写日志”(即 WAL),以便它保留足够的细节来发送副本服务器需要的所有内容。 -
在使用后保留足够的数据,以便副本服务器在落后时仍能获得数据。我们将它设置为 32,这是一个相当保守的设置。这允许新的副本有大量时间来完全同步,并防止较小的网络故障和速度减慢导致服务器不同步。
-
max_wal_senders应该至少设置为副本服务器的数量加 2(并且,如果您决定安装较新的 PostgreSQL,您可能需要将max_replication_slots设置为相同的值)。 -
hot_standby = on允许副本服务器响应查询。
接下来,我们需要向 PostgreSQL 数据库添加一个 replicator 用户。键入psql -U postgres以访问数据库,然后键入以下内容以创建用于复制的用户replicator(全部在一行中):
CREATE ROLE replicator WITH REPLICATION
PASSWORD 'mypassword' LOGIN;
然后键入\q退出。
接下来,我们需要授予远程服务器打开到该服务器的复制连接的权限。在/var/lib/pgsql/data/pg_hba.conf中添加以下一行:
host replication replicator all md5
现在我们需要重新启动数据库,以便新设置生效:
systemctl restart postgresql
我们的服务器现在完全可以开始接受复制请求了。现在我们可以设置副本服务器了。
为了设置 PostgreSQL 副本服务器,我们需要做的第一件事是创建一个新的 Linode 节点,通过使用标准过程复制我们的模板节点来运行它。在本练习中,将新节点命名为dbreplica。您还需要为这台机器添加一个私有 IP 地址并记下来(在本书的其余部分,我们将把它称为DB.REPLICA.PRIVATE.IP)。
现在,启动dbreplica并以root的身份登录。
如果您按照说明操作,这台机器应该没有运行 PostgreSQL。但是,如果它正在运行,您可以使用systemctl stop postgresql将其关闭。一旦 PostgreSQL 关闭,我们需要清除现有的 PostgreSQL 安装。使用以下命令完成该操作:
rm -rf /var/lib/pgsql/data/*
现在,我们需要从主系统请求一个初始二进制备份,作为复制的起点。这是通过pg_basebackup命令完成的。要生成这个初始备份起点,首先切换到 postgres 用户,如下所示:
su - postgres
接下来,发出以下命令(全部在一行中):
pg_basebackup -x -U replicator -h DB.MASTER.PRIVATE.IP
-D /var/lib/pgsql/data
它会要求您输入密码,然后从 master 数据库复制整个 PostgreSQL 实例,包括配置文件。接下来,在这一步完成后,您需要调整配置文件。我们在第五章中建立的postgresql.conf文件有一个包含服务器私有 IP 地址的命令listen_addresses。不幸的是,因为它是从主数据库复制的,所以它目前拥有主数据库的私有 IP 地址。要解决这个问题,只需打开/var/lib/pgsql/data/postgresql.conf并将listen_addresses配置改为:
listen_addresses = 'localhost,DB.REPLICA.PRIVATE.IP'
成功结束后,您需要告诉 PostgreSQL 该服务器将被用作热备用。
这是通过告诉服务器在启动时进入“连续恢复模式”来实现的。为此,我们创建了包含以下内容的文件/var/lib/pgsql/data/recovery.conf(最后两行应该在同一行中键入):
standby_mode = 'on'
primary_conninfo = 'host=DB.MASTER.PRIVATE.IP port=5432
user=replicator password=mypassword'
一旦这一切就绪,您需要像这样退出回到 root 用户:
exit
现在您再次成为 root 用户,我们需要重新打开 PostgreSQL 数据库,并确保它在重新启动时会自动启动:
systemctl start postgresql
systemctl enable postgresql
我们还需要确保防火墙中有一个洞来接收数据库连接:
firewall-cmd --add-service postgresql
firewall-cmd --add-service postgresql --permanent
此时,您的系统已经启动并作为副本服务器运行了!
要验证这一点,请运行以下命令,该命令将列出所有正在运行的 PostgreSQL 进程:
ps afxw|grep postgres
列表中的一个进程应该在输出中包含单词recovering或 startup。这意味着数据库是启动的、活动的,并以主服务器的 WAL 日志为基础。
您还可以检查日志文件,这些文件位于目录/var/lib/pgsql/data/pg_log中。日志文件的结尾应该类似于“数据库系统已准备好接受只读连接”和“流式复制已成功连接到主数据库”
关于 Postgresql 复制的几点注意事项
PostgreSQL 实现称为“实例”,可以包含任意数量的数据库。如果您遵循本书中的说明,那么您的 PostgreSQL 实例只包含一个数据库(实际上是三个,因为 PostgreSQL 总是安装有一对模板数据库,template0和template1)。使用createdb命令行程序或者在运行psql时发出create database指令来创建一个新的数据库是非常容易的。
在任何情况下,请记住,由于 PostgreSQL WAL 文件是系统级功能(即 WAL 文件由整个数据库实例共享),PostgreSQL 流复制复制整个 PostgreSQL 实例,而不仅仅是单个数据库。
7.3 设置应用以利用主/副本复制
应用本身已经构建为利用只读副本。如果你记得,我们实际上有两个连接函数,getReadWriteConnection()和getReadOnlyConnection()。现在,它们都指向同一个服务器。为了使用我们新的只读副本,我们所要做的就是改变getReadOnlyConnection()函数中的连接信息,它会将所有只读连接转移到副本服务器上。
我们所要做的就是改变getReadOnlyConnection()函数中的host参数,使其指向DB.REPLICA.PRIVATE.IP。
一旦完成,我们只需要重新部署我们的代码。通常,我们可以通过部署到template_node然后从它重新创建webnode服务器,或者通过将它单独部署到每个服务器来实现。
7.4 添加更多 PostgreSQL 副本服务器
如果分离出单个副本服务器并不能提高您需要的性能,那么您实际上可以根据需要拥有多个副本服务器。为此,您可以复制模板节点并重复 7.2 节中的过程,或者直接复制副本服务器。
复制副本服务器不像复制 web 节点那样自动,但是可以节省一些步骤。为此,您需要首先在当前的dbreplica节点上启用备份,然后从dbreplica的备份创建新的复制副本实例。创建每个副本实例后,您需要ssh到新的副本服务器,并将/var/lib/pgsql/data/postgresql.conf中的listen_addresses值设置为它自己的私有 IP 地址,因为默认情况下它将被设置为dbreplica的值。这样做之后,您将需要用systemctl restart postgresql重启 PostgreSQL。
在创建了大量数据库副本后,需要修改代码以利用数据库。如果我们可以为副本数据库创建一个负载平衡器,然后将所有应用代码指向负载平衡器,那就太好了。不幸的是,Linode 目前没有能力创建内部负载平衡器(即只接受私有网络上的请求的负载平衡器),因此我们必须提供自己的负载分配机制。我们将在应用代码中模拟这个特性,在获取数据库连接时随机选择一个服务器。因为我们没有负载平衡器,所以我们必须在您的应用代码中对服务器列表进行硬编码,并且添加新的副本服务器也需要修改应用代码,并将该代码刷新到所有 web 服务器。
为了理解如何修改应用代码,假设我们现在有三台副本服务器,它们的私有 IP 地址分别是DB.REPLICA.PRIVATE.IP、DB.REPLICA2.PRIVATE.IP和DB.REPLICA3.PRIVATE.IP。为了让我们的只读连接在这些之间循环,我们将根据图 7-2 重写我们的getReadOnlyConnection()函数。
一旦这段代码在我们的集群中就位,我们所有的只读请求将会在多个副本服务器之间进行负载平衡,如图 7-3 所示。这意味着我们唯一的瓶颈是读写数据库请求。无论如何,大多数应用都是由只读请求控制的,因此读写请求出现瓶颈通常不成问题。
图 7-3
多副本数据库配置图
图 7-2
连接到一组副本服务器
7.5 跨数据中心复制
对于非常大的应用,有时您需要比单个数据中心更好的地理覆盖范围。此外,超关键应用可能需要拥有多个数据中心所带来的可靠性,这样,如果一个数据中心出现故障,应用至少可以继续部分运行。
这种设置的创建有点复杂,所以本书不会像其他体系结构那样给出一步一步的方法。但是,要实现此功能,您需要执行以下基本步骤:
-
更改
dbmaster,使其listen_address设置为*。因为它将接收来自其他数据中心的请求,所以它也必须监听公共 IP 地址。 -
防火墙
dbmaster防止不受欢迎的第三方访问。 -
使用 SSL 加密到 PostgreSQL 的连接(参见后面如何启用它)。
-
在新的数据中心部署一个“主副本”,它将是
dbmaster的副本,但也将是网络上其他副本的主副本(如果需要的话)。这仍然是只读的,但它是流向其他副本的副本。 -
将您的 web 应用的副本部署到新的数据中心,该数据中心具有用于读写连接的公共 IP 地址
dbmaster,以及用于只读连接的本地副本的私有 IP 地址列表。
在这个过程的最后,您的集群架构应该看起来如图 7-4 所示。
随着您的部署变得越来越复杂,您对自动化部署处理的需求也在增加。有关自动部署的更多信息,请参见第十二章。
图 7-4
多站点架构图
如果您需要对本地设置中的数据库进行读写访问,那么配置会变得更加困难,因为您现在必须管理系统之间如何进行同步,以及如果系统之间的网络连接中断会发生什么。在这种情况下,您通常将本地(非共享)和全局(共享)数据分离到单独的数据库实例中。对于非共享数据,您需要在每个位置都有一个主数据库。对于共享数据,您将按照类似于图 7-4 的方式进行设置。
在 Postgresql 上启用加密
要在 PostgreSQL 上启用加密,您需要以 root 用户身份转到目录/var/lib/pgsql/data。在那里,您需要编辑postgresql.conf并添加以下行:
ssl = on
接下来,您需要生成一个 SSL 密钥和自签名证书。在同一个目录中,发出以下命令(全部在一行中):
openssl req -nodes -new -x509 -keyout server.key
-out server.crt
这将要求您填写一份简短的表格,该表格将被编码到您的证书中。然后,您需要对生成的文件设置权限:
chown postgres:postgres server.key server.crt
chmod 600 server.key
如果此时重启数据库,服务器将允许 SSL 连接,但不要求SSL 连接。要要求 SSL 连接,您可以将pg_hba.conf中的任意或所有host行改为hostssl。
完成所有更改后,您可以使用以下命令重新启动数据库:
systemctl restart postgresql
7.6 数据分片
数据库可伸缩性的另一个选项是数据分片。数据库分片在概念上相对简单——它只是意味着您以这样一种方式对数据进行分区,即不是所有的数据都依赖于同一个数据库系统。
例如,您可以共享您的数据,以便姓名以“A”和“B”开头的所有客户都在一个数据库中,姓名以“C”和“D”开头的所有客户都在另一个数据库中,依此类推。只要系统能够容易地确定它需要查询哪个数据库,您就可以用您喜欢的任何方法对数据进行分区。主要的一点是,数据本身被分散到不同的数据库系统,而不是所有的东西都由同一个数据库系统管理。
在这样的系统中,通常要么是应用负责分片,要么是有一个中间层负责将连接切换到适当的数据库。一些更新的工具已经被开发出来,比如pg_shard,它的目标是作为一个数据库插件无缝运行。
在任何情况下,分片都会带来一系列数据管理和数据完整性问题。数据库的一个目的是确保数据的一致性。从本质上说,分片消除了数据库为实现规模而提供的许多保护。因此,重要的是要确定你知道你为什么想要切分和你想要如何切分,以最小化风险。
例如,如果您将客户划分到不同的数据库,那么您也应该划分相关的记录,这样客户就可以由一个数据库提供服务,他们的记录也可以一起管理。想象一下,如果您必须从备份中恢复记录,但是一些相关的记录在不同的数据库系统上!
分片需要大量的工作,需要大量的规划,并且严重依赖于您的使用和工作负载的细节,因此很难笼统地谈论它。尽管如此,如果您正在寻找更多的方法来扩展您的数据库,分片绝对是一个选择。分片还可以与其他方法结合使用,如主副本复制或多主复制,但同样,它需要很多额外的爱、关心和管理才能使其正常工作。
如果您的登录组不共享任何数据,分片会更容易。例如,假设你建立了一个电子邮件营销系统。不同的客户很少在这样的系统上共享任何数据。所以,把他们的记录完全分开是没有问题的。
您可能会运行几个完全独立的云,每个云拥有不同的客户群。来自客户 A、B、C 和 D 的登录将被转移到云 1,来自客户 E、F、G 和 H 的登录将被转移到云 2,依此类推。如果这些云都是完全独立管理的,这样的设置还可以实现一定程度的灾难缓解,因为您不太可能同时让所有数据中心完全停机。
八、使用 CDN
到目前为止,我们对云应用的可伸缩性工作的重点是动态内容的服务(即从数据库查询中提取的内容)。然而,对于许多网站来说,动态内容实际上是系统中最小的部分。事实上,在任何网站上,大多数请求甚至不是针对动态内容,而是针对静态内容——图像、样式表和 JavaScript。因此,在考虑如何在云中扩展您的应用时,重要的是不要忘记扩展您的静态资产。
您可以通过创建更多的前端 web 服务器来扩展您的静态资产,因为它们通常是服务静态资产的服务器。然而,这可能会变得更昂贵(您必须保持更多服务器在线)并且更难管理(更多节点意味着更多管理)。
扩展静态资产实际上比扩展动态资产容易得多,因为您不必担心当它们改变时会发生什么。因此,有些服务是专门为扩展静态资产而构建的。扩展静态资产交付的服务被称为“内容交付网络”,也称为 CDN。
8.1 CDN 是如何工作的?
大多数 cdn 的工作方式是,它们有一个全球分布式内容服务器网络。假设您有一个 10MB 的图像,您希望由 CDN 提供服务。通常,如果该用户向您的服务器请求图像,那么您的服务器的处理时间和带宽将会被发送 10MB 的图像所限制。如果用户住在大洋的另一边,这是一个额外的问题,因为你的服务器会浪费大量资源来管理一个又慢又吵的连接。
CDN 允许你做的是将你的用户重定向到图片在 CDN 上的 URL。CDN 第一次看到 URL 时,通常对图像一无所知,但它有规则告诉它如何在您的服务上找到原始图像。从那时起,在 CDN 第一次获取图像后,每当有人请求该图像时,CDN 将设法将图像交付给用户,而完全不通过您的服务器。
此外,大多数 cdn 的服务器位于许多不同的物理位置。这些位置被称为“存在点”(pop),那里的服务器通常被称为“边缘服务器”(正因为如此,cdn 通常被称为“边缘缓存”)。在各种 pop 中使用边缘服务器不仅允许 CDN 通过大量服务器提供可扩展性,边缘服务器还允许 CDN 提供与用户物理距离很近的服务器。如果用户可以从附近的服务器获得大量数据,而不是漂洋过海去检索数据,这将大大提高用户的体验。
CDN 为服务静态资产提供了本质上无限的规模——对于任何知名的 CDN,您都不必担心它们的网络溢出。只要资产没有变化,CDN 将完全能够处理对该资产的任意数量的请求。
通常,CDN 的主要成本是带宽。然而,许多 cdn 的带宽成本比云服务器低。现在,有了 Linode,你就必须有一个非常活跃的网站来超越你的带宽。尽管如此,如果你这样做了,如果带宽是由 CDN 提供的话,你需要支付的费用会稍微少一些。在任何情况下,即使您的服务器有足够的带宽,cdn 提高可扩展性的好处通常也是值得的,因为您不需要连续运行服务器来处理可能出现或可能不出现的流量。你只需要为你实际使用的带宽付费。
8.2 设置简单的 CDN
幸运的是,大多数 cdn 非常容易建立。在本书中,我们将使用亚马逊的 CloudFront 作为 CDN,尽管存在许多其他选项(CloudFlare、StackPath、CDN77 和 Fastly,仅举几例)。cdn 的好处在于,由于它们提供的服务相当透明,很容易混合和匹配 cdn 的服务提供商。
CloudFront 的工作方式非常简单:
-
您在主站点上托管所有静态内容。这是你内容的“官方”存储库。
-
您在 CloudFront 上创建一个主机来提供您的内容(通常会有一个名称,比如
xyzabc.cloudfront.net)。 -
你告诉 CloudFront 服务器你的主站点的 URL。
-
每次从站点链接到静态内容时,都是使用 CloudFront URL 链接,而不是链接到自己服务器上的内容。例如,如果你的图片在
http://mysite.example.com/mydirectory/myimage.png,链接到它,你可以使用 URLhttp://xyzabc.cloudfront.net/mydirectory/myimage.png.CloudFront 将从你的配置中知道如何找到myimage.png,并将它缓存并提供给你的用户。
CloudFront 第一次接收到对图像的请求时,它会到您的站点获取图像。接下来,任何未来的请求都将由 CloudFront 使用请求图像的用户附近的服务器直接提供服务。
让我们看看如何使用 Amazon AWS 和 CloudFront 来实现这一点。你需要做的第一件事是在 http://aws.amazon.com注册 AWS。我想你可以自己完成注册过程。
AWS 有大量可用的服务,因此仪表盘不会列出所有服务,而是让您搜索一个。在搜索栏中输入“CloudFront ”,它将允许您进入 CloudFront 仪表板。
因为这是你第一次使用 CloudFront,它会给你一个按钮,上面写着“创建发行版”用 CloudFront 的术语来说,发行版就是一个 CDN 复制器服务。
点击“创建发行版”后,CloudFront 会询问您的交付方式,并让您选择“网络”或“RTMP”RTMP 是用于传输大量视频内容的协议。然而,由于我们只是分发像图像和样式表这样的基本资产,我们将选择从基本的 Web 交付开始。
然后亚马逊会询问你的销售细节,如图 8-1 所示。实际上,在这些选项下面有许多附加选项,但是唯一必需的字段是“原始域名”,这是您希望 CloudFront 从中获取资产的站点的 DNS 主机名。这个不能是一个 IP 地址,但必须是某种 DNS 主机名。如果您还没有为您的应用设置 DNS 主机名,您可以使用 Linode 自动生成的主机名。如果你进入 Linode 的节点平衡器屏幕,点击你的平衡器,它会给你一个(很长,可能分成两行)内部生成的平衡器主机名(类似于nb-BALANCER-PUBLIC-IP-ADDRESS.dallas.nodebalancer.linode.com)。填写“原始域名”后,向下滚动到页面底部。
图 8-2
CloudFront 发行版列表
图 8-1
创建 CloudFront 发行版
在页面底部,有一个“创建分发”按钮。点击此按钮后,您将进入一个显示新发行版的发行版列表,类似于图 8-2 。这个屏幕最重要的部分是“域名”,它向您展示了如何访问您新创建的发行版(您可能需要调整字段的大小,以便看到完整的名称)。它还会给你一个状态,从“进行中”到“已部署”需要 5 分钟到 1 小时的时间一旦部署完毕,您就拥有了一个正在运行的 CDN!
8.3 使用您的 CDN
一旦状态更改为“已部署”,该域名现在将完全反映您的原始站点。然而,它只是你网站的一个静态版本。如果你的网站发生变化,除非你要求,否则 CloudFront 不会更新它的资产。
假设 CloudFront 给你的域名是xyzabc.cloudfront.net。这意味着如果你去 http://xyzabc.cloudfront.net/list.php ,它会显示你的留言簿列表。但是,如果您随后去修改您的留言簿列表,这些更改将不会反映在您的 CDN 上,它会将所有内容视为静态资产。这就是为什么在大多数情况下,cdn 只提供静态资产——图像、样式表、JavaScript 等等。
因此,让我们修改应用,通过 CDN 只提供我们的样式表,而不是通过 CDN 访问整个站点。我们要做的就是修改common.php的一行。在getHeader()函数中,我们只需将<link>标签改为:
<link rel="stylesheet" href="http://xyzabc.cloudfront.net/guestbook.css" />
一定要用你的发行版的域名替换xyzabc.cloudfront.net!一旦在所有服务器上部署完毕,您的样式表现在将由 CDN 提供服务。
图 8-3
从 CDN 中删除内容
这意味着您的服务器几乎不会再提供样式表了。偶尔,CDN 可能会使其缓存的一些内容过期,但这取决于 CDN。CDN 将为自己优化存储多少数据,存储多长时间,以及重新请求原始文件的频率。对于更高级的应用,您可以配置 Apache 来提供一个Expires: HTTP 头或一个Cache-Control: HTTP 头,以指定 CDN 应该保存您的数据的最长时间。
然而,假设您部署了一个新版本的应用,它实际上有一个更新的样式表。这意味着 CDN 为您提供的版本现在已经过时了——它的缓存中可能仍然有旧版本。这根本不是问题,只是意味着你需要手动告诉 CDN“作废”你的内容,这样它才会再次请求。
要使 CloudFront CDN 上的内容无效,首先要单击 CloudFront 发行版的 ID。这将把您带到一个信息页面,描述在您的发行版上设置的所有选项。在最右边,有一个名为“无效”的标签单击此选项卡,然后单击“创建失效”这将把你带到一个类似于图 8-3 的屏幕。在“对象路径”字段中,只需清除那里的任何内容,只需键入/*即可使所有内容无效。虽然 CloudFront 允许细粒度的访问来使 CDN 无效并从 CDN 中删除特定的项目,但我发现,在大多数情况下,简单地使整个事情无效更容易、更干净。点击“无效”按钮使其无效。
失效可能需要几分钟才能扩散到 CloudFront 的所有服务器,但很快所有的服务器都会为您的新内容提供服务。当失效的“状态”变为“已完成”时,您将知道它何时完成
8.4 用 CDN 缓存整个站点
除了缓存单个内容片段,如样式表、JavaScript 和图像之外,现代 cdn 实际上还允许您缓存整个网站,为您提供跨互联网的即时可伸缩性。虽然这对于像我们这样的 web 应用不适用,但如果你有一个基本的网站,这可以让你的网站立即扩展到无限数量的用户,几乎不需要任何成本或额外的配置。
让我们来看看如何用 CloudFront 实现这一点。为了让 CDN 直接为您的网站服务,您需要将您的主站点的 DNS 指向 CDN 的服务器。
你可能认为你可以将你的网站的 A 记录设置为 CDN。然而,cdn 通常不会给你其主机的 IP 地址。其原因是 CDN 有多个 IP 地址用于 CDN(每个 PoP 一个),它们通常依赖 DNs 查找来决定将哪个 IP 地址分配给哪个客户端(即,它将为客户端分配一个在地理上靠近它们的 IP 地址)。这意味着我们不能仅仅在 DNS 中指定 CDN 的 IP 地址来将网站指向它。
相反,cdn 通常通过 DNS CNAME 记录来处理这类事情。CNAME 是一个“规范名称”——它告诉浏览器对 DNS 查询使用不同的名称,并将该名称的结果用于我们自己的 DNS 查找。如果您将www设置为 CloudFront 主机的 CNAME,那么 CloudFront 仍然可以使用自己的 DNS 机制为每个客户端分配正确的 IP 地址。
然而,这产生了另一个问题。CDN 需要知道您可能通过哪个主机名访问它。也就是说,如果您将www.example.com命名为abcxyz.cloudfront.com,CloudFront 需要知道当它的一台机器接收到对www.example.com,的请求时,它应该提供与abcxyz.cloudfront.com相关的缓存。
这是通过 AWS 中的分发设置完成的。这些设置位于发行版的“常规”选项卡下。点击“编辑”并寻找一个名为“备用域名(CNAMEs)”的字段您可以在这里输入您为该发行版命名的任何主机名(每行一个或用逗号分隔)。
然而,这带来了一个问题,因为 CDN 需要一个地方来检索您的数据,而您只是将您的 DNS 指向 CDN,而不是您自己的服务器!要解决这个问题,您只需设置一个内部 DNS 名称,CDN 从这个名称中提取(您可以将这个名称称为www2.example.com或 www-internal.example.com ),然后将www的 DNS 设置为指向 CDN(注意,我们使用“内部”并不是指只对我们可见,因为它在公共互联网上与主站点一样多,而是表示它不是我们将最终用户导向的目的地)。
这样做,CDN 允许无限增长的访问者访问你的网站。
处理裸域名
从技术角度来说,您不能为根级域名创建 CNAME。你可以为www.example.com,做 CNAME,但不能为example.com做。虽然您的 DNS 服务器可能允许这样做,但这是违反规范的,可能会给不希望这样做的客户端带来各种奇怪的问题。因此,您需要确保有一种机制将您的裸域名(即example.com)重定向到您的 CNAMEd 主机(即www.example.com)。
为了解决这个问题,许多 DNS 提供商提供重定向服务,自动将对裸域名的所有请求重定向到www主机。如果你的 DNS 提供商不提供这项服务,wwwizer.com的好心人会免费为你提供这项服务。本质上,如果您将空白域的 A 记录指向174.129.25.170,它将自动重定向到前面有www的同一个域。
有关该服务的更多信息,请访问 http://wwwizer.com/naked-domain-redirect。还要记住,和任何过于廉价的服务一样,你应该采取适当的谨慎措施。让他们重定向你的流量可以简化事情,但让一个你没有合同的第三方为你重定向你的流量也会有问题。
8.5 将 CloudFront 放在整个应用的前面
使用我们的应用,内容是动态的。这使得在它前面放置一个像 CloudFront 这样的 CDN 变得困难(但是,正如我们将看到的,并非不可能)。使用 CloudFront 带来了一个问题——我们如何防止人们查看陈旧的内容?
事实上,大多数 cdn 可以像我们处理本地缓存一样被处理。我们可以简单地为我们的内容设置一个最大到期日期。
在 CloudFront 上,如果你查看你的发行版,你会看到一个“行为”标签。点击这个,你会看到一个单一的,默认的行为。这些行为允许您在服务器的不同内容路径上设置不同的设置。对于这个例子,我们只需要编辑已经存在的那个。选择当前行为,然后点按“编辑”
如果向下滚动,您将看到一组 TTL(生存时间)值,以秒为单位。要更改这些值,首先将“对象缓存”设置从“使用原始缓存头”更改为“自定义”如果您不希望内容超过 5 秒,请将“最大 TTL”设置为 5。这意味着在重新查询内容之前,缓存将只保留内容 5 秒钟。因此,内容可能有点陈旧,但不会太陈旧。
这就引出了另一个问题 CDN 将如何处理向服务器发送表单?这也可以通过行为设置来处理。在“允许的 HTTP 方法”下,选择包含所有 HTTP 方法的选项。CDN 将只缓存 GET 和 HEAD 请求,但会将所有其他请求直接转发到您的服务器。
此外,默认情况下,CloudFront 不会将 cookies、HTTP 头或查询字符串转发给服务器。这是为了减少它必须发出的源请求的数量,以及它必须缓存的对象的数量。然而,在我们的应用中,您看到的内容是基于查询字符串的。因此,您需要将“查询字符串转发和缓存”设置为“全部转发,基于全部缓存”,以使应用正常工作。
所有这些设置完成后,单击“是,编辑”保存行为更改。
现在,您可以转到您的 CloudFront 发行版的 URL,并像使用常规网站一样使用它。您也可以按照 8.4 节中的说明,让用户通过您的网站自己的主机名访问它。
在您疯狂地使用这样的应用设置之前,请记住所有的缓存设置都是一种平衡。如果您的缓存使用了太多的键(即路径、头、查询字符串、cookies),那么您的大部分内容将被传递到服务器(即没有被缓存),并且您不会获得性能提升。事实上,这只会增加开销和成本,因为你必须支付高速缓存的带宽和服务器的带宽。但是,如果缓存设置过于宽松,用户将会看到过时的数据,或者更糟糕的是,看到其他人的数据!例如,如果您的内容是基于 cookie 进行个性化的,但是您没有使用 cookie 作为缓存键的一部分,那么如果 Jim Bob 请求了一个页面并将其放入缓存,当 Jane Doe 请求相同的页面时,她将获得 Jim Bob 的页面!
那么,拥有高度个性化 web 应用的人如何更好地利用 CDN 呢?答案就是把你的应用翻个底朝天。
8.6 彻底改变你的应用
我们在上一节中遇到的问题是向用户交付高度定制的页面,同时仍然充分利用缓存。拥有定制页面通常意味着它们是不可缓存的。这种困境可以通过为您的网站采用“由内向外”的页面架构来解决。
历史上,web 应用是通过让服务器生成页面来构建的。当用户导航到一个 URL 时,服务器获取一个模板,将其与用户数据结合,并添加页面内容以生成最终的 HTML 页面。然后,这个 HTML 页面作为一个整体返回给用户。
这种架构运行良好有多种原因:
-
大多数 web 应用框架都是围绕这个范例构建的。
-
这种架构非常符合 HTML 的结构方式。
-
这种架构很容易构思和构建。
-
这种架构需要更少的规划来实现。
-
这种架构几乎不需要前期开发。
-
网络的大部分历史都植根于这种架构。
-
这种架构的优化是最后执行的(即过早优化是万恶之源)。
然而,这种架构的问题在于,对于大多数用例来说,它在高流量的情况下效率非常低。
通过多年来 Ajax 技术的发展,构建应用的另一种方法是将页面翻过来。也就是说,在典型的架构中,服务器生成包含动态内容的页面。在新的方式中,服务器生成一个基本上静态的页面,但是对于任何动态内容,将回调到服务器。
例如,想象一下,有一个列出你的产品的页面。在典型的 web 框架中,您的服务器将执行以下任务:
-
用户的浏览器向服务器请求特定的页面。
-
服务器端开始 web 应用流程引擎(PHP,Ruby on Rails 等。).
-
应用访问数据库以获取产品列表。
-
应用为产品列表生成 HTML。
-
应用访问数据库以获取用户的状态信息(即登录信息、购物车信息、其他状态信息等)。).
-
该应用生成 HTML 页眉和页脚,其中包含用户的状态信息以及他们在页面导航中的位置。
-
应用将生成的片段拼接成整个 HTML 页面。
-
服务器将最终的 HTML 页面返回给用户。
-
用户的浏览器呈现页面。
正如你所看到的,这是一个相当复杂的过程,在整个页面被呈现在服务器上之前,用户不会得到任何反馈。这导致服务器需要等待很长时间才能完成。
然而,如果我们把它翻过来,我们可以实现一个更好的优化策略。我们要做的是将页面作为静态页面提供,然后让用户自己的浏览器负责获取基于用户的内容并将页面拼接在一起。
新的序列如下所示:
-
用户的浏览器从 CDN 请求页面。
-
假设它已经被缓存,用户附近的 CDN 服务器立即用页面内容(即产品列表)进行响应。
-
用户的浏览器加载内容并立即显示,在任何特定于用户的数据旁边有一个加载微调器。
-
该页面执行 JavaScript,向服务器发出请求,请求用户的状态信息。
-
服务器发回用户的 JSON 编码的状态信息。
-
该页面呈现网页的其余组件。
在这个序列中,服务器的负担大大减轻。它不再需要查询产品列表、生成页眉和页脚,或者将页面缝合在一起。所有这些都发生在用户和 CDN 之间的快速交易中。服务器只对用户的会话状态负责(即使这样也常常可以本地化到用户自己的浏览器!).这允许大量的动态、交互式内容,而对服务器的负担很小。
这个概念可以进一步扩展,甚至可以在事后生成产品列表。API 请求本身也可以被缓存,使用与主 web 页面相同或不同的设置。
如您所见,如果应用的架构合理,CDNs 为提高网站速度提供了一个非常灵活和强大的工具。
这种方法的主要缺点是实施时需要大量的计划和预见。这种方法需要决定一个 Ajax 和动态 HTML 框架,设计一个好的应用架构,开发 API 和 API 认证,并规划包含所有部分的页面布局。在旧的架构上,你通常可以随意地制作页面,它们仍然可以工作。当使用由内而外的架构时,您必须从计划开始,这需要相当多的前期工作。本书并不试图展示由内向外的页面架构的代码,正是因为它需要大量的代码来实现。
在任何情况下,无论您的应用架构是什么,大多数应用都可以从某种 CDN 解决方案中受益。
九、为无限磁盘空间使用 S3
对于一个真正可伸缩的站点,另一个经常需要的方面是无限的磁盘存储。管理服务器的大规模存储确实是一项艰巨的任务。决定多少冗余、多少可访问性、每台服务器有多少磁盘,以及如何管理磁盘以确保您提前知道磁盘是否出现故障是一项艰巨的任务。即使对于小规模的站点,管理文件也是困难的。
令人欣慰的是,使用文件存储服务将允许你把这些任务外包给第三方,可能比你自己做要便宜得多。文件存储服务的黄金标准是亚马逊的 S3 服务。S3 代表简单存储服务。这个缩写在很大程度上是正确的——对于简单的情况,S3 很容易设置,但是对于更复杂的任务,它也有相当大的灵活性。
S3 以极低的每千兆字节成本为你提供无限的空间。它将随着您的需要而扩展,并防止您遇到因本地存储文件而带来的所有文件管理难题。
其他云存储服务也存在,许多具有更好的定价结构。一些比较常见的包括 Backblaze B2,数字海洋的空间,和 Rackspace 的云文件。这里我们使用 S3,因为它拥有最广泛的采用和集成。
9.1 入门
S3 是亚马逊 AWS 工具套件的一部分。因此,您可以使用您在第八章中创建的相同登录来访问 AWS。
一旦登录 AWS(网址为 http://aws.amazon.com ),就可以在“存储”标题下访问 S3。当你点击 S3,你会得到一个类似图 9-1 的屏幕。
图 9-1
最初的 S3 屏幕
该屏幕的主按钮是“创建存储桶”按钮。S3 将所有文件组织成他们所谓的“桶”存储桶有点像命名硬盘。那是你储存所有文件的地方。桶名必须是唯一的,不仅对你的帐户,而且实际上在整个亚马逊。因此,对于 Amazon buckets,您不应该依赖任何特定的命名约定,这些约定假定您可以预测将来会有什么名称。相反,最好在您的应用中配置存储桶名称,这样更容易管理。
当您单击“创建存储桶”时,它会要求您输入存储桶名称和区域。AWS 将除了 CloudFront 之外的几乎所有服务组织成区域。就我们的目的而言,该地区本身并没有太大的不同。但是,如果您有特定的原因需要在特定的物理位置放置一个铲斗,AWS 允许您选择铲斗的放置位置。单击“创建”按钮创建您的存储桶。
一旦您成功地创建了您的 bucket,您应该得到一个 bucket 列表(只有您的一个 bucket)。如果你点击你的桶,你可以浏览你的空桶。要了解 S3,只需从硬盘上传一个文件到桶中。点击“上传”按钮,然后你可以从你的电脑拖放文件到你的桶。点击“上传”让他们上传。
9.2 的文件夹
如果您在 S3 控制台上四处看看,您会注意到您能够在您的存储桶中创建文件夹/目录。然而,在 S3,文件夹实际上是不存在的。实际上,S3 桶有一个完全扁平的结构,只有文件名和文件(从技术上讲,文件名称为“键”,文件本身称为“对象”)。但是,文件名可以包含斜杠字符。AWS 用户界面然后使用斜杠字符向您显示文件,就像它们在文件夹中一样。当你在 S3“创建一个文件夹”时,它实际上是创建一个文件夹名的空文件,文件名以斜杠结尾。简而言之,S3 控制台让你看起来像有文件夹和子文件夹,但实际上它只是一大堆文件,其中一些文件的名称中有斜线,S3 控制台用它来分隔成假文件夹,以便更容易查看。
9.3 获取凭据
在我们将 S3 帐户连接到服务器之前,我们需要创建一组安全凭据。为此,AWS 使用了一个名为 IAM 的系统,即“身份和访问管理”。IAM 允许您创建具有受限权限的用户,这样,如果您的安全密钥遭到破坏,就不会让攻击者完全控制您的环境。与其他服务一样,IAM 可以通过在其服务列表下搜索 IAM 来找到。当你第一次加载 IAM 屏幕时,它看起来如图 9-2 所示。
图 9-2
IAM 初始仪表板
IAM 主要使用“组”来管理权限,组本质上是权限的容器。因此,我们将从创建一个组开始。首先单击左侧面板上的“Groups ”,这将显示一个空的组列表。然后点按“创建新群组”这将打开一个屏幕,要求输入该组的名称。我们将使用名称guestbook-access(名称并不重要,我们稍后只需引用该名称)。单击“下一步”继续。
接下来,您将把策略附加到该组。策略是复杂的权限组。谢天谢地,AWS 有非常有用的预定义策略。出于我们的目的,我们只需要名为“AmazonS3FullAccess”的策略。您可以在“过滤器”框中搜索它,然后在找到时选择它。图 9-3 显示了它的样子。
图 9-3
将策略附加到组
最后,它会要求您审核并最终确定您的小组。单击“创建组”完成。
现在,您可以将用户添加到组中。在屏幕左侧,单击“用户”链接。这将把您带到一个空的 IAM 用户列表。要开始,请单击“添加用户”按钮。
在下一个屏幕中,将要求输入用户名和访问类型,如图 9-4 所示。我们称用户为application-user,尽管实际的名字并不重要。在“访问类型”下,选择“编程访问”这意味着创建的用户将不能登录,但只能使用 API。
图 9-4
创建 IAM 用户
在下一个屏幕中,它将要求您将用户添加到一个组。只需选择您之前创建的组(我们称之为我们的组guestbook-access)。下一个屏幕允许您为该用户设置标记。我们不需要,所以您可以跳过此屏幕继续。最后,它会要求您检查您的信息。此时,您可以单击“创建用户”,它将为您创建用户。
创建用户后,您现在可以下载他们的凭据。屏幕如图 9-5 所示。它列出了用户和两个特殊字段:“访问密钥 ID”和“秘密访问密钥”这两个字段实质上充当该用户的 API 的可重置用户名(“访问密钥 ID”)和密码(“秘密访问密钥”)。您可以下载凭据,也可以从屏幕上的字段中复制凭据。
图 9-5
正在检索 IAM 凭据
接下来,我们将把实际的访问密钥 ID 称为MYACCESSKEYID,把实际的秘密访问密钥称为MYSECRETACCESSKEY。
如果您后来丢失了这些凭据,您将无法再次获得它们。但是,您可以返回到用户的记录中,创建一组新的凭证。如果以后服务器的安全性受到威胁,您可以停用旧的凭据,并为同一用户颁发新的凭据。
9.4 通过命令行访问 S3
AWS 有一个命令行工具,不仅允许访问 S3,还允许访问它们的各种可伸缩性 API。要安装它,请在模板节点上以 root 用户身份发出以下命令:
yum install -y awscli
AWS 命令行有两种主要方式来指定您的访问密钥 ID 和秘密访问密钥。您可以通过环境变量或配置文件来实现。环境变量更灵活,所以我们将走这条路。
如果您不熟悉命令行,环境变量是在命令行会话中设置的变量。不仅如此,您调用的命令还可以访问您的所有环境变量。此外,您设置的任何环境变量在您注销时都会消失,因此它们需要在您每次登录时重置(如果您希望它们在登录时自动设置,您可以将设置它们的命令放在一个名为.bash_profile的文件中)。
aws命令为您的凭证使用的环境变量是AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY。要设置这些变量,请在您的终端中输入以下命令(用您的实际按键替换MYACCESSKEYID和MYSECRETACCESSKEY):
export AWS_ACCESS_KEY_ID=MYACCESSKEYID
export AWS_SECRET_ACCESS_KEY=MYSECRETACCESSKEY
现在你可以使用aws命令来操作你的 S3 桶。要获得您的存储桶列表,发出以下命令:
aws s3 ls s3://
它应该列出您在第 9.1 节中创建的存储桶。
要列出该 bucket 的内容,发出以下命令(用 bucket 的实际名称替换BUCKET):
aws s3 ls s3://BUCKET/
为了理解命令的工作方式,aws是我们正在使用的主命令,s3告诉我们要使用哪组子命令,ls就像 Linux 上的ls命令(它列出了内容),s3://BUCKET/是我们想要查看的位置。
aws命令也给出了操作文件的其他常用命令。代替ls,我们可以使用cp将文件复制进和复制出桶。如果您有一个名为test.txt的文件,您可以使用以下命令将它复制到您的 bucket 中:
aws s3 cp test.txt s3://BUCKET/
要将一个文件从你的桶复制到你的节点,只需切换s3://BUCKET/和test.txt的位置。
此外,您可以为您的文件创建临时访问 URL。这些 URL 是签名的 URL。这意味着 AWS 知道一个授权的人生成了这个 URL,并且 AWS 将在指定的时间内信任这个 URL 作为访问该文件的有效手段。
这允许您直接将人们引导到 AWS 站点来检索他们需要的数据,而不是必须将数据传输到您的服务器,然后自己发送。它节省了处理能力、带宽和响应时间。
然而,为了做到这一点,我们需要知道这个桶在哪个区域。您在创建 bucket 时指定了一个区域,但是 AWS 并不总是显示该区域的“计算机化”版本,这是命令行所需要的。发出以下命令来查找您的 bucket 的区域(用您的 bucket 名称替换BUCKET):
aws s3api get-bucket-location --bucket BUCKET
这将返回一个 JSON 编码的值。这个键叫做LocationConstraint,这个值是这个桶所在区域的名称。该地区的一些常见值是类似于us-east-1、us-east-2、ca-central-1、eu-west-2等字符串。我们将使用 REGION 来表示您所在的地区。
要获取访问文件的 URL,请使用以下命令:
aws s3 presign s3://BUCKET/FILE --region REGION
这将生成一个 URL,您可以将其复制并粘贴到您的浏览器中。瞧,文件将会出现!但是,这个网址只能用 3600 秒(1 小时)。如果您希望 URL 在不同的时间内有效,您可以使用--expires-in标志来告诉它。因此,如果您希望 URL 在 20 秒后过期,您只需在命令中添加--expires-in 20。
内联指定环境变量
在我们开始编码之前,我想快速补充一下,让您知道设置环境变量的另一种方法。您可以设置一个环境变量,使其仅对一个应用有效,方法是在给定命令本身之前指定要在同一行上设置的环境变量。
例如,如果我要运行命令example-command,并且我想将环境变量MYVAR设置为myval,我可以这样运行命令:
MYVAR=myval example-command
这将设置环境变量,但仅用于运行命令。在运行命令时,您实际上可以设置任意多的环境变量,它们只需用空格隔开。例如,要设置两个值,我们可以这样做:
MYVAR1=val1 MYVAR2=val2 example-command
这就是我们在将要创建的应用中设置凭据的方式。出于配置目的,实际上最好将凭证(和其他配置信息)放在代码之外,并通过服务器上的环境变量来设置它们。然而,这需要比本书更深入的、特定于服务器的配置细节。
9.5 将您的应用连接到 S3
现在我们知道了如何从我们的服务器与 S3 对话,我们现在将把我们的留言簿应用连接到 S3,这样用户就可以上传他们的消息和图片。这实际上相当简单。我们要做的就是
-
创建一些通用函数来获取 AWS 配置信息。
-
允许我们的表单有一个图像字段。
-
创建留言簿条目时检查图像上传。
-
把图像传送到 S3。
-
查看留言簿条目时,为图像创建一个签名的 S3 URL。
第一步是在common.php中创建一些助手函数,用于处理 AWS 凭证和配置。图 9-6 显示了其功能。记住用您自己的值替换 BUCKET、ACCESSKEY 和 SECRETKEY。
图 9-6
AWS 配置对common.php的补充
函数getAWSCredentials()将返回一个字符串形式的凭证,该字符串可以在前置到命令字符串时设置环境变量。
下一步是在new.php中的表单上放置一个图像字段。这包括两个部分。我们必须做的第一件事是修改<form>标签,以便它允许文件上传。为此,将属性enctype="multipart/form-data"添加到<form>标签中。否则,文件输入标签实际上不会上传文件。
接下来,我们需要添加一个文件上传字段。就在提交按钮之前,添加以下几行:
<label>Image (JPEG)</label>
<input type="file" name="imagefile" />
现在,您的表单被配置为可以上传文件。接下来,我们将配置create.php来接受文件上传。
图 9-7
修改create.php以检测文件上传
我们必须改变两个部分。首先,用图 9-7 中的代码替换线$has_img = false;。这段代码检测一个文件是否被上传,如果是,确保数据库被更新以反映这一点。其次,将图 9-8 中的代码添加到显示$stmt->execute();的行之后。这是把文件传送给 S3 的代码。
图 9-8
修改 create.php 向 S3 传输文件
现在,我们只需要提供一种方法来查看图片,如果你点击留言簿条目。为此,我们需要修改single.php。只需将图 9-9 中的代码添加到显示getFooter()的行之前。
图 9-9
修改single.php以显示图像
这段代码将为上传请求一个签名的 URL,然后将它放在一个图像标签中以供查看。
注意,对于一个真实的应用,我们希望验证上传的文件确实是一个 JPEG 文件。否则,任何人都可以上传任何东西,黑客可以很容易地滥用该系统作为一个免费的文件共享网站,或用于其他邪恶的目的。此外,您可能希望有一个管理功能来验证上传的图像是适当的。否则,有人会轻易地把你的留言簿变成色情分享网站。这些超出了我们简单的示例应用的范围,但它们是值得记住的好东西。
还要记住,你最终不仅要为存储空间付费,还要为进出 S3 的所有流量支付带宽使用费。未能保持警惕可能会付出高昂的代价。
现在您需要在template_node上测试您的新代码,然后,当它工作时,通过重新映像服务器将它部署到您的云集群,如 6.3 节所述。
现在试试你的应用——它现在有了亚马逊 S3 无限的文件存储空间!
注意,还有一个用于 PHP 的 AWS 库,它具有 S3 功能。这里,我们使用命令行,因为我们已经在前一节中学习了该工具。关于 AWS PHP 库的信息可以在 https://docs.aws.amazon.com/sdk-for-php/ 获得。
关于 S3 签名到期的说明
在处理签名 URL 时要记住的一件事是要警惕它们如何与缓存交互。在这种情况下,single.php没有被缓存,所以没有真正的担心。但是,如果是的话,确保 URL 的过期时间比呈现的代码在缓存中的时间长得多是很重要的。
例如,如果 URL 只在 30 秒内有效,但缓存持续了一个小时,那么,在第一个 30 秒结束后,在这一小时的剩余时间里,用户将获得他们无法使用的 URL。当问题开始出现时(或者最好是在问题出现之前),请记住这一点。
S3 文件权限
除了已签名的 URL 之外,还可以通过授予对文件的公共访问权限来允许访问 S3 文件。这是可行的,但不是共享文件的最佳方法。问题是,如果你只是提供一个公开共享的网址供人们访问,那么这个网址就可以被分享,人们就可以出于自己的目的使用你的 S3 资源,完全绕过你的网络应用。这意味着你可能会花钱成为别人的文件服务器。
即使您没有立即在应用中实现对文件访问的谨慎控制,强制每个人使用由您的应用控制的签名 URL 来访问 S3 对象(如我们在此所示)也意味着当您准备好实现访问控制时,一切都准备好了。至少,它可以防止在互联网上公开分享 S3 网址,这些网址使用你的 AWS 帐户资源下载大文件。
如果您真的喜欢这样做而不是签名的 URL,您可以通过首先在 bucket 本身上启用公共访问,然后设置每个人都可读的单个文件来管理它。这也意味着您需要对新上传的文件设置权限。您可以在aws s3 cp命令中这样做,方法是在命令末尾添加以下标志(URL 应该与该行的其余部分在同一行,等号后面没有空格):
--grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers
但是,这只有在 bucket 本身被配置为允许公共访问时才有效。