65.9K
CodeProject 正在变化。 阅读更多。
Home

编写可扩展的代码

2011年1月28日

CPOL

11分钟阅读

viewsIcon

35938

本文中,Rackspace 强调了编写代码时需要考虑的 5 个概念,并提供了一些技巧来帮助您实现这些概念。

Rackspace Hosting 提供两种文件存储产品——Cloud Files™ 和 Cloud Drive。尽管两者都提供基于云的文件存储,但它们是为不同的用途而设计的。您的目标是内容交付还是文件备份和同步?用例决定了哪种产品最适合您的需求。欲了解更多信息,请访问 http://www.rackspace.com/cloud/

编写可扩展的代码

网络庞大,而且每天都在变得更大。如果您正在编写一个将达到数百万最终用户的网络规模应用程序,您可能需要仔细考虑如何编写该应用程序,以便它在网络可能产生的苛刻工作负载下正常运行。我们的计算硬件越来越快,越来越便宜。这种渐进式发展已经改变了很多,以至于传统的软件开发概念不再适用于当今的网络规模应用程序环境。内存充足,CPU 是 64 位,带宽便宜,CPU 也是。存储比以往任何时候都便宜,但 I/O 容量和高速网络互连却不是。考虑到所有这些,我们生成的数据比以往任何时候都多,并且正在思考构建令人惊叹的交互式软件解决方案和数据分析系统的新颖而令人兴奋的方式,这也就不足为奇了。要在今天思考网络规模,您需要改变几年前有意义的思路。

在本文中,我想强调编写代码时需要考虑的 5 个概念,并提供一些技巧来帮助您实现这些概念。

概念

保持资源需求低

在当今快速的 CPU、充足的内存和高速网络的时代,这一点经常被忽视。有时,减少可伸缩性问题最简单的方法是使软件过程更高效。您越快地进出 CPU,传输的数据越少,占用的内存越少,您的应用程序运行速度就越快。每单位工作的处理时间越低越好。需要考虑的四个关键资源

  • I/O – 大多数应用程序首先用尽此项,尤其是在处理大量数据时。磁盘 I/O 最慢,但总线 I/O 也可能耗尽。
  • 网络 – 尽管带宽比以往任何时候都更经济实惠,但如果您严重依赖网络进行远程数据访问,仍然很容易耗尽带宽。
  • 内存 – 它比以往任何时候都更便宜、更快。CPU 现在拥有大量缓存,并且多个核心之间共享 L2 缓存!
  • CPU – 您可以负担很多,更多的核心和更多的服务器有很大帮助。

通过并行运行缓解瓶颈

将您的工作负载分成小块,并安排它们在独立的 CPU 核心、存储设备、网络甚至独立的服务器上并行处理。将处理的协调同步保持在最低限度。锁会扼杀并发性。

数据分散化

传统上,软件使用简单的集中式数据存储模型。扩展这需要数据库的垂直可伸缩性,这变得越来越昂贵和困难。集中式数据库很快成为 I/O 瓶颈。在集中式数据设计中频繁更新状态变化可能特别有问题。如果您将数据分布在多个不同的服务器上,并将部分数据放入每个服务器中,您可以将写入负载分散到众多系统上。使用像 Cassandra 这样的分布式数据存储系统可以对此有很大帮助,它为您处理所有数据分区,并且可以很容易地添加服务器以增加容量。

最终一致性

如果您的应用程序可以使用稍微过时的数据,那么它可以使用最终一致的数据存储系统。这些系统使用异步过程更新存储数据的远程副本。在某些情况下,某些用户在更新后可能会立即看到稍微过时的数据版本。仔细决定哪些数据需要 ACID 属性(原子性、一致性、隔离性、持久性)。如果 BASE(基本可用、软状态、最终一致)足够,那么您可以从利用数据异步复制的分布式数据存储系统获得卓越的可伸缩性和可用性。

横向扩展优于纵向扩展

向给定计算机系统添加资源(更多内存、更多或更快的 CPU、更快的网络接口)被称为纵向扩展。尽管纵向扩展相对容易且经济实惠,但您可以很快达到其极限。当您运行您能负担得起的最大最快的服务器时会发生什么?那么,您需要横向扩展。这意味着添加更多服务器,并在它们之间分配工作。对于一个横向扩展良好的系统,您可能会对许多较慢的系统感到非常满意,而不是一台快速服务器。在没有横向扩展计划的情况下编写软件可能会让您陷入困境,以至于您唯一的选择是投入更多硬件进行纵向扩展。如果您的一个问题适合横向划分,您可以通过显着的成本节省来进一步扩展。

  • 并发性!= 锁。横向工作允许您同时完成大量工作。尽量减少同步的并行工作量。过多的数据同步序列化会导致低并发性和低效的资源利用。
  • 每个连接一个线程 = 不好。您通常不希望线程数远多于 CPU 核心数。如果您的 CPU 密集型且线程数多于核心数,您完成的工作量将大大减少。如果您的线程是 I/O 密集型,那么线程数多于核心数可能是可以接受的,但也不要将您的 I/O 系统扩展得太薄,试图一次做太多事情。
  • 固定数量工作线程的线程池 = 好。拥有一个线程池要聪明得多。然后您可以拥有最佳数量的工作线程从队列中拉取工作,并显着提高吞吐量。

编写可扩展代码的有用技巧

  • 首先编写“压力”测试计划。列出您最坏的情况。把它写下来并贴在墙上提醒您。例如:“支持 10,000 个并发连接,响应时间 < 1 秒。”请务必量化在最坏使用场景下可接受的结果。在软件设计和实现的每个阶段都牢记这一点。定期提醒所有人。很容易被您的功能列表分心和分散注意力,而忘记您的架构性能和可伸缩性目标。把它白纸黑字地放在您面前。在编写一行代码之前提前编写测试计划。它有效!
  • 缓存,宝贝,缓存。找出您经常访问的数据,并将其缓存到内存中,以便重复高速访问。对于大多数用例,分布式内存缓存(如 memcached 集群)优于基于主机的缓存。
  • 压缩通过网络发送的数据。客户端和服务器之间的数据压缩和解压缩经常被忽略,而它却能让交互式应用程序更具响应性。它减少了数据传输时间,并提高了单位时间的连接处理能力。压缩和解压缩的 CPU 时间成本通常与您从中获得的速度效益相比微不足道。使用压缩网络传输的系统总体效率几乎总是高于发送未压缩数据。
  • 压缩存储在磁盘上的数据。试试看。真的。是的,存储很便宜,但 I/O 却不便宜。通过压缩存储的数据,您可以轻松有效地提高 I/O 吞吐量。
  • 使用工作队列时,考虑采用感官驱动的准入控制系统。我一再看到的一个错误是,一个系统对其在网络上的并发使用没有限制。例如,假设您的系统最大容量为一次完成 100 件事,并能在可接受的响应时间 T 内产生 100 单位的输出。如果您给它 101 件事要做,那么在时间 T 内的输出可能会减少到 50。如果您给它 102 件事要做,那么在时间 T 内的输出可能会减少到 40,依此类推。将系统推到其极限之外可能会导致它自身磨损,而实际上没有完成任何工作。我建议在队列变得太长无法处理时将工作排队并拒绝工作。人们出于显而易见的原因不愿设置可见的限制,但实际上,如果您在接近实际限制时控制接受新工作的速率,性能会更好。如果您拒绝工作,您的客户端可能会收到可见的错误消息或忙音信号。仔细考虑一下。是收到忙音信号更好,还是您的电话在句中神秘地挂断或声音糟糕?没错,忙音信号更好。不知何故,软件开发人员似乎没有努力将忙音信号功能放入他们的网络系统中。使用合理的准入控制系统和适当的拒绝程序,当队列变得太长时,可以解决这个问题。如果您的系统具有良好的横向扩展能力,您可以使用 API 调用来配置更多云服务器,以帮助您在队列长度过长时为队列提供服务。这样,您就可以潜在地扩展您的资源以跟踪您的需求,并完全避免拒绝工作。这种弹性资源配置方法不能替代准入控制,因为在某些时候,尤其是在错误情况下,需求仍然可能超出可用容量。考虑一下,如果您的系统自身出现无限循环,即服务器错误地充当自己的客户端,会发生什么。拥有准入控制可以在整个系统崩溃之前打破这个循环。
  • 围绕寻道设计。大多数 I/O 瓶颈是由寻道引起的。向磁盘存储系统读写数据时,避免导致磁盘寻道的操作。尽可能用顺序 I/O 模式替换随机 I/O 模式。
  • 保持每个连接的开销低。如果您每个连接需要大量内存,您将无法并发完成太多工作。假设您有 8 GB 内存,每个连接需要 100 MB 内存,您一次可以运行大约 80 个连接。如果您切换到 10 个工作线程池,每个 100 MB,并将每个连接的内存减少到 1 MB,您一次可能可以允许 7000 多个连接,并以与您只能支持 80 个并发连接时相同或更快的速率完成工作。
  • 不要保存超出实际需要状态。Web 应用程序开发人员通常在 HTTP 请求之间将数据保存在服务器端会话中。这种做法扩展性不好。它会大大增加每个连接的内存开销。将状态保存在客户端浏览器而不是 Web 应用程序中(使用 cookie,而不是会话)可以帮助解决此问题。如果您有信任顾虑,可以对其进行加密。只将您_真正_需要的值读入服务器端应用程序。
  • 频繁通信的应用程序应避免使用基于 XML 的解析文本协议。如果您的应用程序组件之间通过网络通信,或者您在客户端和服务器之间进行大量通信,请尽量减少使用文本解析协议。在网络通信中使用 XML 或 JSON 等文本数据格式非常诱人,因为您可以运行不同的架构等。事实证明,解析文本格式数据所需的服务器端资源通常非常占用 CPU,并显着减慢连接处理时间。如果可能,请保持轻量级。考虑使用简单的二进制协议,这样就不需要文本解析。

我喜欢尽可能地保持系统设计简单。为数百万用户提供服务使得这变得困难。根据我的经验,设计横向可扩展系统会以复杂性为代价。在承诺之前,请仔细考虑可伸缩性和效率的真正成本。有时,只需在更大更快的服务器上运行就可以解决问题,这很简单。添加压缩、加密、准入控制、线程池、最终一致性、分散数据、顺序 I/O、低每个连接内存开销、二进制网络协议和分布式缓存不适合业余爱好者。做所有这些对于大多数应用程序来说可能都是多余的。在开发应用程序时,只需记住这些概念,这样您就可以实现对您的应用程序有意义的内容。根据您的要求,您可能只需其中几项就可以构建一个令人惊叹的可扩展系统。

我们每天都在努力构建软件和服务,使部署可扩展应用程序变得越来越容易。

至此,我的“编写可扩展代码”文章就结束了。现在是时候以不同的方式思考如何设计和实现您的软件了,这样当一切完成后,您就能拥有一个高效的可扩展系统。

联系我们的狂热支持团队 -
http://www.rackspace.com/forms/contactsales.php

© . All rights reserved.