云计算生产环境架构性能调优和迁移套路总结(以 AWS 为例)

向作者提问
埃森哲咨询经理。专注于DevOps、持续交付,微服务以及全功能产品开发团队的设计、实践、落地和推广。
查看本场Chat

云计算生产环境架构性能调优和迁移套路总结

在我过去作为 DevOps 咨询师的 4 年里,不少项目都是帮客户把应用迁移到云计算平台上。作为云计算平台的翘楚, AWS 以其卓越的技术能力和集成能力为广大海外客户所喜爱。相比其它供应商而言,AWS 价格算是较高的,但从稳定性、自动化和社区支持来说,AWS 是最佳选择。

本文通过一个我最近完成的案例来对我这几年来进行云计算生产环境调优和应用迁移的套路进行总结。

案例背景

案例是一个泰国网站的生产环境(请脑补一句 “ 萨瓦迪卡 ”,为了叙述方便,下文中均以 “ 萨瓦迪卡 ” 指代这个网站。)“萨瓦迪卡”是一个 采用 Wordpress + MySQL搭建的应用。这个遗留系统已经工作了五年。客户已经把在其它 VPS 上平移到 AWS 上。平移(lift and shift)是说原样复制,而迁移(migration)还要进行改造。而客户唯一发挥 AWS 优势的一点就是用了一个配置很高的 EC2 虚拟机 —— m4.4xlarge。这样一台配置的虚拟机有 16 个虚拟 CPU,64 GiB 的内存,以及 2000 Mbps 的网络带宽,最高 3000 IOPS 的 200GiB 的块存储设备(也就是硬盘)。

知识点: GiB 是用二进制计算的,GB 是用十进制计算的。1 GiB 是 2的30 次方,而1 GB 是10 的 9 次方,1 GiB 略大于 1GB。 而且,AWS 的 FreeTier 免费计划是按 GB 计算的哦!

除了基本的网络和虚拟机以外,“ 萨瓦迪卡 ” 的所有东西都放在一台虚拟机上。没错,是所有东西——Web 服务器,反向代理,数据库,上传的文件——都放在一台虚拟机上。唯一个一个负载均衡用来承载 HTTPS 证书,没有使用集群,没有高可用,没有数据库/应用分离,没有防火墙,没有 WAF,没有 APM,没有 CDN 而且,没有持续交付流水线,所有部署都要 ssh 到机器上进行操作。如图所示:

enter image description here

“ 萨瓦迪卡 ” 的生产环境可以被认为是一个裸奔的肉鸡。我曾经一度它已经被轮番入侵很久了,只是还没有被发现而已。而且,“ 萨瓦迪卡 ” 生产环境的唯一一台服务器的内存率使用经常超过 95%,我很担心它的状况,任何一个小的 DoS,都不需要 DDoS,就可以让它整站宕机了。

我于是把我的担忧汇报给了客户,客户也意识到了问题。在我发现问题之前的一个月就启动了 “ 萨瓦迪卡 ” 的翻新(Revamp)项目,让这个应用保持原样(Keep it as is),直到 6 个月后新项目上线,替换掉当前应用。

然而,没想到我一语成谶。一天,“ 萨瓦迪卡 ” 被删库了!

删库?别慌!

作为一个运维工程师,最悲催的事情就是 “ 人在家中坐,锅从天上来 ”。这天是世界杯的某一场小组赛,而我刚吃完晚饭正在洗碗。突然被客户的 P1 告警(P1 - Priority 1,最高级别告警)惊吓到,得知 “ 萨瓦迪卡 ” 被删库了。

判断的依据是:

  1. “ 萨瓦迪卡 ” 主页打开是 Wordpress 的初始化安装页面。证明应用是正常的,数据不在了。
  2. 在服务器上用 MySQL 客户端登录数据库,找不到“萨瓦迪卡”的数据库。

还好客户每天有全量数据备份,于是客户快速从全量备份恢复了数据库,只是缺少了从备份点到故障点的业务数据。全量数据库的备份文件有 10 GiB,这么大的表如果采用 mysqldump 会因为锁表而导致 10 分钟左右的停机时间(别问我怎么知道的)。

问题分析

在恢复应用的同时,我们也开始进行了分析的工作。首先,我们怀疑是被攻击了。于是通过 AWS 的 CloudTrail(一种审计工具,用来记录登录 AWS 用户的操作)和 主机上的命令历史(history 命令)和登录日志进行分析,结果一无所获。其次,我开始检查 MySQL 的日志(/var/lib/mysql/*.err),在日志上发现如下片段:

InnoDB: Log scan progressed past the checkpoint lsn 126908965949
180622 17:21:09  InnoDB: Database was not shut down normally!
InnoDB: Starting crash recovery.

<此处省略很多行>

180622 17:21:32 InnoDB: Initializing buffer pool, size = 3.0G
InnoDB: mmap(3296722944 bytes) failed; errno 12
180622 17:21:32 InnoDB: Completed initialization of buffer pool
180622 17:21:32 InnoDB: Fatal error: cannot allocate memory for the buffer pool
180622 17:21:32 [ERROR] Plugin 'InnoDB' init function returned error.
180622 17:21:32 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.

通过分析,我们发现 mysql 发现自己有问题的时候尝试恢复数据库,但因为虚拟机可用内存不足而加载存储引擎失败,导致找不到数据库。因此,解决方案有以下三种:

  1. 采用工具进行对 mysql 服务器参数进行调优。
  2. 扩大内存,换个配置更高的虚拟机。
  3. 将应用和数据库部署在不同的虚拟机实例或者 RDS (关系数据库服务)上。

而三种有各自的问题:

对于方案1,数据库调优需要频繁重启。对于生产环境来说,必须在低流量的时段(一般是夜间)进行。而且所花时间未知且效果很难保证。由于资源有上限,且进程相互影响,很难发现问题。所以风险较高,价值有限。

对于方案2,需要对虚拟机进行不停机镜像复制,因此会导致部分数据丢失,而且数据同步恢复困难大。而且,不知道需要多少资源的虚拟才足够。问题同方案1,只不过由于资源更多,下次出现同样问题的时间更晚罢了。这个方案的风险虽然比第一种小,但用空间换时间的价值仍然有限,不晓得能撑到什么时候。而且,可能会带来一定的资源浪费。

方案3是风险最小,价值最大的方案。它将数据作为核心资源并托管至高可用服务上,有效了隔离了风险,保护了数据的可用性。但唯一的缺点就是对于需要的资源和性能是未知的。因此,在实施这个方案之前,我们需要进行性能测量。

你可能会想,只需要增加一些基础设施监控和 APM (Application Performance Monitoring,应用性能监控)就可以得到相应的数据了。然而,在生产环境的性能度量没那么简单。

首先,我们要保证生产环境的业务连续性。

APM 也是一种应用程序,也会占用资源,你如何确定安装和运行 APM 的过程不会造成生产环境停机?其次,如果一定会造成停机,那么会停机多久?当这些问题都是未知的情况下,鲁莽的行为只能增加更多的不确定性风险。

因此,在迁移之前,我们要模拟生产环境进行度量并进行分析。

设计性能度量

性能度量是一个从 “ 未知 ” 到 “ 已知 ” 的过程。

首先,你需要明确所要度量的问题。

你可以和你的小组一起商定需要解决的问题。在上面这个案例里,我们所需要回答的问题包括:

  1. 正常运行应用程序需要多少内存?
  2. 正常运行数据库需要多少内存?
  3. 进行哪些操作会导致停机时间?停机时间会持续多久?
  4. 资源使用对性能的影响有多少?
  5. 性能拐点在哪里?

当然,对于 CPU,网络和存储,你也可以设计以上的问题。

然后,找到数据基线(Benchmark)

由于资源的使用是和用户访问数量息息相关的,你还需要知道资源使用的 “ 均值,峰值,边际值。”

均值:是资源使用基线,也就是最小值。

峰值:是资源使用的警告线,如果过去发生过这么高。

边际值:是指每单位的用户请求所消耗的资源。

一般来说,这些数据都可以从云计算提供商的非侵入式监控服务获得,它的数据收集不会影响资源的性能。例如 AWS 的 CloudWatch 。我们可以根据过去 6 个月或者 3个月的时间来估计均值和峰值。

但由于未来是不确定的,因此过去 6 个月或者 3 个月的数据是建立在 “ 未来访问量不会突变 ” 的假设基础上的。

例如,如果有类似于 “ 6 · 18 ” 或者 “ 双十一 ” 的流量高峰,则日常的数据参考意义不大。

如果缺乏这样的手段,就要通过复制生产环境来度量了。

复制生产环境

复制生产环境的一点原则就是 “ 尽量减少不同 ”

尽可能的按照生产环境的配置来构建你的沙盒环境以得到更接近真实的数据。很多云提供商都提供镜像(Image)或者快照(Snapshot)的功能用来复制当前有状态的资源。有时候二者是同一个意思。如果有区别,二者的区别在于以下几点:

  1. 镜像是全量,快照是增量。
  2. 镜像的构建需要停机,而快照不需要。
  3. 镜像生成时间长,快照生成时间短。
  4. 镜像不能指定时间点部分还原,快照可以根据时间点部分还原。

无论是哪一种,我们都要选择一个对生产环境影响最小的方案。在 AWS 中,我们可以根据当前的虚拟机实例构建虚拟机镜像 AMI (Amazon Machine Images)。它提供两种方式:一种是不重启(no reboot),这种方式的缺点是会造成构建镜像时间点以后的数据丢失。另外一种是在构建之前重启实例,这样不会导致数据丢失。

对于上述的案例来说,生产数据的完整性并不会影响我们的度量,因此,无需重启实例。

但如果你要度量重启实例会带来多少数据丢失,则需要重启实例。

此外,为了保证你不会误操作,我建议你在非生产环境的云计算账号下重建应用。

如果你一定要在同一个账户中进行复制,请确保你做好了生产环境资源隔离。

设计测试场景

当你在测试环境下复制了生产环境,你就有了一个安全的沙箱来进行测试了。当我们开始进行性能测试的时候,我们要通过 “ 整体 ” 的测试来计算对 “ 局部 ” 的影响。并找到。以 “ 萨瓦迪卡 ” 为例,我们通过 AWS 上的数据得到了 “ 萨瓦迪卡 ” 生产环境的平均响应时间:0.2 ~ 0.4 秒,RPM(Requests Per Minute 每分钟请求)大概在 4500 左右。

因此我们设计了如下测试场景:

  1. 空闲使用率:0 请求的时候,资源使用率。

  2. 1 个,10 个,20 个 并发请求的时候,资源使用率和响应时间,用于计算边际资源使用率。

  3. RPM 和生产环境 RPM 均值相等的情况下,资源使用率和响应时间。

  4. 2 倍 ,4 倍, 10 倍 生产环境 RPM 均值的情况下,资源使用率和响应时间。

  5. 模拟生产环境的 RPM 增长速度(逐步增加请求到相应值,例如 5 分钟增长到 2000 RPM)进行测试。

  6. 模拟生产环境极限 RPM 增长速度(一次增加请求到对应值,例如 5 秒钟增长到 2000 RPM)进行测试。

    根据以上的测试场景,我们可以构建资源使用率和响应时间之间的关系。

如果你有 CDN 或者 URL 访问分析数据,可以它来构建你的测试案例。如果什么没有,例如 “ 萨瓦迪卡 ” 这种情况,你就可以使用主页的 URL 来进行测试。常用的工具有 Selenium, Jmeter 和 Gatling。你可以用 Selenium 录制一个用户访问的脚本,来模拟用户访问。你也可以通过 Jmeter 或 Gatling 来增加并发进行负载测试,后者能提供更加有用的信息。

如果你无法模拟足够多的真实用户数据,把以上的工具生成的脚本或配置放到 flood.io 上运行,得到更好的参考报告,如下图所示:

enter image description here

如果你需要度量某些操作的停机时间,你可以在进行负载测试的时候进行操作。也可以使用我写的小工具 wade (Web Application Downtime Estimation)来测试。关于 wade 的故事可以参考 一怒之下,我写了一个开源流量测试工具

通过模拟 “ 萨瓦迪卡 ” 的访问数据,我得到了以下数据:

  1. 当 Web 服务器(Apache)重启完,仅有健康检查访问的情况下,系统占用 367 MiB 内存。
  2. 数据库占用 10 GiB 左右内存,也就是说,给 Web 应用剩下的内存有 53 GiB 左右。
  3. 分别度量了 1个用户,10 个用户,20 个用户并发访问下的内存使用情况。平均每处理一个请求,最多需要消耗 133 MiB 内存。
  4. 也就是说,剩下的内存最大能服务 400 个左右的用户的并发访问。如果超过 400 个用户,系统会因为资源不足而宕机。
  5. 升级虚拟机 Linux 中的软件包和安全补丁会带来 5 秒钟左右的停机。
  6. APM (NewRelic)的安装会占用 63 MiB 左右内存且无停机时间。

编写性能度量报告

当我们完成了性能度量的时候,就要编写一份性能度量报告。性能度量报告包含以下 6 个部分:

  1. 背景:主要回答为什么(Why)要做这一次性能度量。
  2. 关键问题:通过性能度量期望知道哪些问题(What)。
  3. 测试设计:主要介绍度量方法(How),以及度量方法中的注意事项。
  4. 测试条件:由于是模拟测试,要强调与真实值的匹配情况,哪些部分重要,哪些部分不重要。
  5. 测试数据结果:采用工具得出的真实数据,要有源可查,最好是截图。
  6. 结论:根据数据的计算解答第 2 步 提出的关键问题。
  7. 建议:根据度量数据得出的下一步优化建议。

至此,我们完成了对生产环境性能的分析。接下来,就要为性能设计架构迁移方案了。

设计云计算平台迁移计划和方案

将应用程序迁移到云计算平台上主要的目的是把自行构建的高风险高成本应用以及组件替换为云计算平台上的高可靠性低成本组件/服务。

应用架构的迁移有两种方案:

一种是整体一次性迁移,即重新实现一个架构并完成部署,然后通过金丝雀发布或者蓝绿发布切换。这种方式的好处是简单,直接,有效,一开始就能按照最佳实践构建应用架构。而且对于现有系统来说影响不大。但如果方案没设计好,容易造成高级别的风险,所以应当进行大量的测试以确保可靠性。

另一种是持续部分迁移,每次引入一点风险,保证风险可控,但缺点就是优化步骤较多。虽然持续部分迁移步骤多,但是总体时间并不一定会比整体迁移更高。

注意:由于自动化基础设施和架构设计会带来一些副作用,特别是配置间的耦合。因此,对于生产环境的直接优化要慎用自动化。如果一定要用,请务必在测试环境上做好测试。但如果你能做到自动化并且有完好的测试,不如直接做整体一次性迁移方案得了。

一般说来,一个完整的云平台迁移方案会分为以下三大阶段:

第一阶段:构建高可用架构以实施水平扩展,从而保证了应用的稳定运行。

第二阶段:引入 APM 并根据 APM 数据进行定向优化,采用云计算的服务来优化应用的资源使用。

第三阶段:构建应用端的持续部署,构建 DevOps 的工作模式。

这五个阶段是大的顺序,而每个大的阶段里又会相互掺杂一些其它阶段的内容。

但无论什么样的迁移方案,一定要通过度量进行风险/收益比排序,最先完成代价最小,收益最大的内容。

第一阶段:构建高可用架构

我们之前说过,一个应用架构的第一追求就是业务的连续性和抗风险能力。一个高可用的架构能够在你的应用面对压力的时候从容不迫。因为如果资源满负荷运转,新的请求会因为没有可用资源而导致排队。这是常见的停机或者性能降低的原因。这就是 AFK 扩展矩阵常说的 X 轴扩展:通过复制自己扩展资源从而达到降低排队等待的时间。 此外,水平扩展出来的机器同样也是一个预留资源,能够提高应用的可用性。应用架构不仅仅是应用程序的事情,也包含着资源的分配,二者是相辅相成的。

一般会经历如下几步:

第一步,有状态和无状态分离

第二步,牲畜化(Cattlize)应用实例

第三步,自动化水平扩展(AutoScaling)

第一步:有状态和无状态分离

状态分离的目标是把有状态的组件和无状态的组件分开,以便在做复制的时候降低不一致性。最简单的判定办法是:如果复制当前的虚拟资源,并通过负载均衡随机分配请求访问,哪些部分会造成不一致。

常见的有状态内容比如数据库,上传的文件。所以,我们要把它们独立出来。在 “ 萨瓦迪卡 ” 的例子中,我们首先把数据库独立了出来。如下图所示:

enter image description here

在这个过程中,我们采用 RDS 而不是另外一个 EC2 上构建一套 MySQL 来完成数据库的分离。最主要的原因就是 RDS 提供了更好的可用性和数据库维护支持,例如自动备份,更多的监控指标,更自动的数据库迁移和维护窗口等。我们采用 Aurora 引擎的 MySQL 模式,这可以将数据库做成一个集群并让另外一个只读分片,降低数据库的负担。

在分离数据库的时候,要注意以下几点:

  1. 数据库分离的性能基线就是在同样的负载测试下,不能够比没分离之前更差。
  2. 数据库的网络建立在一个私有的子网中,除了应用子网内的 IP 不能访问数据库,从而提高安全性。
  3. 构建一个私有域名来访问数据库,这样可以固定应用的内部配置,减少对配置的修改。同时也给外部切换数据库主备等留下了更灵活的空间。
  4. 注意对原有数据库 MySQL 配置信息的复制,这会导致很大程度上的性能差异。
  5. 对于数据较大的数据库启动而言,会有一个几分钟的热身(Warm up)时间,这会导致性能下降。所以,做切换的时候提前启动数据库以做好准备。
  6. 不要用默认的 root 账户作为应用的访问账户。
  7. 由于 RDS 可以在不影响数据完整性和一致性的情况下降低使用配置,在最开始的时候采用较高的配置。随着优化的不断进行,可以采用维护时间窗口(Maintenance Time Window)在低流量时段对 RDS 实例的配置进行降级,以节约成本。

完成了数据库的隔离,我们就可以依法炮制文件的隔离了。最简单有效的方案是把文件存储在对象存储服务中。AWS S3 就是这样一种服务。避免自己构建共享文件系统或者共享存储设备。

文件相较于数据库来说,占用的内存资源和 CPU 资源较少,大部分的处理为 IO 处理,只要网络和设备的 IOPS 足够。一般不会出现大的问题。

为了降低文件隔离带来的问题,在迁移文件的时候尽量保证文件目录结构不变,只改变文件访问根(root)的位置。对于文件来说,可以通过多种方式:

  1. 如果有对应的文件存储位置修改功能,可以通过修改全局文件存储位置实现。
  2. 如果有反向代理,可以通过修改反向代理的配置来通过重定向实现。
  3. 对时间敏感的文件读写,可以根据日期和时间建立文件夹。
  4. 如果有七层的负载均衡或者 CDN 可以通过路径匹配来实现、

在 “ 萨瓦迪卡 ” 的例子里,我们通过 CDN 来实现了文件的隔离。将文件存储在 AWS S3 上,并且用 CloudFront 作为 CDN。用路径匹配的形式让请求通过访问 S3 而不是虚拟机实例来降低虚拟机的 IO 请求,再加上 CDN 的缓存,这就可以大大减少虚拟机实例的负担,也提升了用户的体验。最终的架构如下图所示:

enter image description here

在采用 CDN 的时候请注意以下几点:

  1. 最开始的时候取消默认缓存设置。因为对于未知的应用来说,各个访问点的内容更新频率并不清楚。这个阶段主要是为了收集应用的访问数据。

  2. 灵活利用缓存的过期时间(Expire time)强制过期(Invalidate)功能,来控制新旧内容的读写。

  3. 注意 DNS 的 TTL 时间,并可以通过设置多级域名和 Failover 功能进行 0 停机切换或者蓝绿部署。例如当前总入口域名直接访问 ELB,可以增加一个 Failover 备份访问点访问 CDN,然后通过 CDN 访问 ELB。如果没有特别的 WAF 配置。

  4. 出问题了多检查 HTTP 请求头和响应头的信息,一般都内藏玄机。

完成了应用的状态隔离,我们就可以开始进行水平扩展了。

第二步:牲畜化(Cattlize)应用实例

在 “ 萨瓦迪卡 ” 的例子里,它的整个架构就是一个宠物式(Pets)的架构:独一无二不可复制。但是带来的问题就是当宠物式架构出了问题之后,没有相对应的替代方案。而牲畜式(Cattles)的架构,就是可以进行复制的容错架构,其中任何一个出问题了,都有相应的备胎(alternatives),正所谓“有备无患”。

所以,可以认为,高可用架构的本质就是把宠物变成牲畜的问题。

如果你的应用是以函数式的方式进行编写的,那么它本身就自带水平扩展功能。函数本身是没有状态的,它只是对数据的加工。这样的应用仅仅是把用户手上的数据,经过一系列的转化,存储在了服务端。

如果你的应用不是以函数式的方式进行编写的,你也不想修改应用,除了做状态隔离以外,你需要将应用程序实例转变为可复制的模式:

首先,你最好有一个备份的网络可用域。可以理解为两个机房,当一个机房出问题了,你还有另外一个机房。

其次,你要通过虚拟机镜像来对应用实例进行复制。

最后,你要采取蓝绿部署或者金丝雀发布的形式来控制用户的访问以达到 0 停机的目的。

所以,我们在 “ 萨瓦迪卡 ” 上做了如下的规划:

  1. 构建另外一个可用区的网络。
  2. 通过虚拟机镜像复制虚拟机实例。
  3. 通过负载均衡来分配应用的访问。
  4. 通过 RDS 集群节点做统一的访问入口,负载均衡和高可用由 RDS 自己管理。

于是,我们就形成了如下图所示的最终方案:

enter image description here

特别需要注意的是,在构建虚拟机镜像的时候,你需要对虚拟机的操作系统进行升级并安装 APM 代理程序(APM agent),在升级的时候,为了避免不必要的停机时间,我们采用了如下流程:

  1. 构建一个当前虚拟机的镜像。
  2. 采用新镜像启动一个虚拟机实例,这个实例的配置需要和现有虚拟机实例一致。
  3. 新的虚拟机实例启动后并不加入到负载均衡里。
  4. 在新启动的虚拟机实例一台上进行更新和升级。
  5. 把升级后的虚拟机加到负载均衡里。
  6. 检查新加入负载均衡的虚拟机正常工作后,在线升级老的虚拟机。
  7. 完成之后,构建新的虚拟机镜像,并作为可复制镜像。

完成了以上的步骤之后,我们就不需要晚上在低访问量的时候进行操作了,白天可以通过创建新的虚拟机实例来完成相应的测试,并通过移入移出负载均衡的方式进行发布。

接下来,就要让这个架构可自动化伸缩了。

第三步,自动化水平扩展(AutoScaling)

当我们完成了前面两步,可以基本认为满足了高可用的条件。但是,由于是静态的人为操作,仍然需要人工值守来解决突发状况,这显然不是一个长久之计,我们要使架构可以处理突发状况,这也是云计算平台的优势所在。

首先,我们需要规划冗余资源。

“ 冗余值 ” 是为了应对超过过去峰值的资源使用率,而设计的容量,它是最大值减去 “ 保留使用率 ”(Reserve Utilization)所剩下的值。以我的经验,在没有特别事件出现的情况下。

“ 保留使用率 ” 一般取 “ 均值的 3 倍 ”,或者峰值的 2 倍中较高的那个值。

举个例子:如果我的内存使用平均值在 2 GiB,峰值在 4 GiB。那么我的保留使用率是 8 GiB。

冗余值的目的不是为了应对突发状况,而是给突发状况保留一些相应时间。当应用有趋于不正常的趋势的时候,我们可以由足够的时间来为下一个特殊阶段进行处理。

然后,构造监控告警。

我们也可以用以上几个值来设置资源监控告警来通知我们,告警方案一般为 “ 两线三区 ” :两线分别为告警线和人工干预线,三区分别为:绿区(正常),黄线(警告),红线(严重)。而每个线的设计方法也不同,一般有以下几种:

  1. 按照最大资源的 60% 和 80% 设计告警线和人工干预线。60%以下为绿区,60% - 80% 为黄区,80% 以上为红区。剩下的为冗余。
  2. 按照边际值的增幅的平方和立方设计告警线和人工干预线。边际值平方以下增速为绿区,边际值立方以下,平方以上为黄区,边际值立方以上为红区。
  3. 按照资源使用均值的 3 倍,或者峰值的 2 倍的较低值和较高值设计告警线和人工干预线。

在 “ 萨瓦迪卡 ” 里,我们采用了第一种方式进行了设置。因为这个应用并不会有突发的访问状况,所以我们采用了均值。

最后,制定和测试自动伸缩策略

有了以上的数据之后,我们就可以制定自动伸缩策略了。一般是在监控处于 “ 黄区 ” 的时候开始出发自动伸缩策略。而由于自动伸缩会有一定的 “ 热身时间 ” (Warm-up Time),这个时间如果资源被用完,就会导致宕机。所以,我们需要更快的响应。因此,制定自动伸缩策略的时候要采用 “ 快增慢减 ” 原则:即以两倍到三倍的速度增加以满足资源消耗,并以但倍速的方式进行减少,不怕浪费,就怕影响用户感知。

此外,请一定要通过前面所讲的性能测试方案来测试自动伸缩策略,以确保策略是可用的。

我以前碰到过一个例子:客户想当然的制定了自动伸缩策略,但从未测试过。导致了一次自动伸缩失效而引起的停机。

这个时候,应用迁移到云上的第一步工作就完成了。我们通过使用云计算平台的可靠性特性,首先先保证了应用的稳定运行。接下来,我们要用云计算平台的优势来逐步优化云平台上的应用。

第二阶段:引入 APM 并根据 APM 数据进行定向优化

对于一个黑盒应用,我们需要了解应用的内部性能状况,除了自己编写相应的代码以外,就是用 APM 平台了。APM 平台往往会提供一个低侵入的方案来获取应用和操作系统的性能数据。一般会采用代理(Agent)的形式运行,并且通过网络对外传输数据,某种意义上说这是一种不安全的方式,但现代大多数 APM 工具都提供了加密的方式来传输和压缩数据。NewRelic 就是这个行业的佼佼者,它不光提供了低入侵的方案,还提供了更多的分析界面来帮助我们找到应用的性能问题。效果如下所示:

enter image description here 在 “ 萨瓦迪卡 ” 里,我们就用了 NewRelic 来作为 APM 方案,它帮我们发现并度量了很多问题,例如缓慢的查询,低性能的插件,不稳定的资源使用等。无论是应用本身还是架构上的问题,以指导我们更好的进行性能调优,并通过数据的对比来判断效果。此外,我们可以结合 CDN 的统计数据来看哪些 URL 和资源最常被访问,从而制定出更有效的性能优化手段。

而一般的性能优化,会采用以下四种基本的方式。但无论是哪一作用方式,一定要根据业务数据来设计缓存,要了解对应访问点的数据更新频率和性能要求。

增加缓存

缓存往往是提升性能的第一选择,主要原理是采用少量速度更高且较贵的资源替代部分速度较慢的资源,形成 “ 短路访问 ” :本来需要经过四个环节才能获取的数据通过两个环节就可以获取到了。而缓存一般是根据 LRU 算法(Least Recently Used,即最近最久未使用)来实现的。

CDN 可以被看做是一种缓存,它通过网络延迟和路由帮用户找到访问更加快速的边缘服务器来加速。边缘服务器上往往根据 LRU 算法存储了 源服务器(Origin Server)的一部分拷贝。

另外一种就是内存数据库或者 Key-Value 存储,例如 Redis 或者 Memory Cache 这种方案是把数据通过一定的格式索引(最简单的方式就是 HashMap)并存储到内存里来替代访问。然而,这些服务的能力有限,并不能提供太多的复杂的操作。

动静分离

由于 Web 应用大多是读写,而不同的设备和内容的访问速度是不同的。对于低写入,高读取的资源。我们可以把它静态化。例如 Hexo 和 Jekyll 这样的工具,就可以把 Markdown 生成静态的 HTML 文件。然后通过 S3 或者反向代理为用户提供内容。相较于 Wordpress 或者很多动态应用,这样的内容不会占用 CPU 和内存,仅仅占用文件系统 IO。占用资源低,也较少会出错。此外,静态的内容也更容易被缓存。

唯一的问题就是区分应用架构中的静态部分和动态部分,并通过版本控制来对内容进行管理。必要的时候要采用 缓存的失效时间或者 HTTP 头的缓存控制来进行更新。

读写分离

如果把应用程序看成一个大的 I/O 系统或者 读/写系统。我们要明白数据是如何写入并如何读出的,特别是针对于数据库而言, 某些的操作都会带来一定的锁或者事务来解决临界资源的互斥访问问题,这种操作一定程度上也会影响性能。所以,我们如果能把数据库的读写分开,会提升应用的部分访问性能。

例如在 AWS 中,Aurora 数据引擎可以为数据库创建集群和只读分片来做读写分离。

另外一种情况下就是用搜索引擎(例如 ElasticSearch)来替代数据库查询,性能会高很多。只是要注意数据的更新频率和索引时间。

异步访问

在大型企业级应用中,通常会应用事务(Transaction)保证业务的完整性和一致性,代价则是系统资源的事务内占用。如果有很多的事务占用着资源,则会造成时间上的资源使用浪费。更加现代的做法是把一个较长的事务拆分成多个较短的事务,通过异步的方式进行 “ 两步提交 ” 来保证最终一致性。优点是释放了很多资源,缺点则是需要更多的确认操作来保证最终一致性。

通过以上的四种方式,我们还可以为不同业务组合成不同的分离方案。并通过 AFK 扩展三角的 Y 轴按能力将应用的关注点进行拆分,采用不同的技术栈和服务来实现并优化性能。例如变成微服务的架构的应用。或者当数据库达到瓶颈之后通过 Z 轴进行拆表拆库在分摊数据库的负载的情况下保证一致性。

第三阶段:构建应用端的持续部署

以上只是进行了基础设施方面的改造,应用的性能和稳定性得到了一定程度提升。然而,我们仍然采用部分人工的操作来进行应用和基础设施的变更。以 “ 萨瓦迪卡 ” 为例,如果我们需要更新应用,为了减少停机时间,则需要执行下面的步骤:

  1. 为当前生产环境虚拟机创建镜像。
  2. 采用新创建的新镜像创建虚拟机。
  3. 在新创建的虚拟机上进行更新。
  4. 更新后进行测试,测试完毕后创建新的更新镜像。
  5. 采用更新后的镜像进行自动水平扩展。
  6. 替换负载均衡里的新老虚拟机。
  7. 终止已经下线的虚拟机实例。

然而,这些操作会带来人为因素的风险,可能会带来一些数据丢失的情况。而且,浪费了人工去做云计算环境的部署,前后时间可长达 4 个小时。

通过之前的两个阶段,我们已经把一个非分布式的应用变成了简单的分布式应用。而面对分布式应用的架构复杂性,人力处理肯定是低效的。我们需要采用自动化的方式完成应用生命周期和基础设施生命周期的完整管理。持续部署流水线就是这样一种实践,通过把流程固化成自动化的脚本和操作避免了人工干预的风险,从而构建出了一个发布软件的可靠流程。

在实践中,我们往往采用持续集成服务器(例如 Jenkins),搭配云计算平台的资源描述和编排服务(例如 CloudFormation)和一些脚本和模板管理工具来完成这一系列操作。在这之前,我们需要对应用程序的不同部分进行封装。

<