如何使我的应用程序很好地扩展?

时间:2020-03-05 18:46:56  来源:igfitidea点击:

通常,什么样的设计决策可以帮助应用程序很好地扩展?

(注意:刚刚了解了Big O符号,我希望在这里收集更多的编程原理。我试图通过在下面回答我自己的问题来解释Big O符号,但是我希望社区改善这个问题并答案。)

到目前为止的回应
1)定义缩放比例。我们是否需要扩展虚拟环境中的大量用户,流量,对象?
2)查看算法。他们所做的工作量是否会与实际工作量(即要循环浏览的项目数,用户数等)成线性比例?
3)看一下硬件。我们设计的应用程序是否可以在无法跟上的情况下在多台计算机上运行?

次要思想
1)不要过早优化,首先要进行测试。瓶颈可能会发生在无法预见的地方。
2)也许扩展的需求不会超过摩尔定律,也许升级硬件会比重构便宜。

解决方案

回答

Jeff和Joel讨论了Stack Overflow Podcast#19中的扩展。

回答

好吧,这个名为High Scalibility的博客包含有关此主题的很多信息。一些有用的东西。

回答

通常,最有效的方法是精心设计,将比例缩放作为其中的一部分。

确定伸缩对项目实际上意味着什么。用户数量是否无限,是否能够处理网站上的斜线游戏?它是开发周期吗?

使用它来专注于开发工作

回答

我唯一要说的是编写应用程序,以便可以从一开始就将其部署在群集上。高于此的任何条件都为时过早。第一项工作应该是吸引足够的用户来解决扩展问题。

首先,以尽可能简单的方式构建代码,然后再对系统进行概要分析,并仅在存在明显的性能问题时进行优化。

剖析代码的数字通常是违反直觉的;瓶颈往往位于我们认为不会很慢的模块中。在优化方面,数据为王。如果优化我们认为很慢的零件,则通常会优化错误的东西。

回答

一个好主意是确定每个添加任务要创建多少工作。这可能取决于算法的结构。

例如,假设我们在一个城市中有一些虚拟汽车。我们随时都希望每辆汽车都有一张地图,显示所有汽车的位置。

一种解决方法是:

for each car {
       determine my position;  
       for each car {  
         add my position to this car's map;  
       }
    }

这似乎很简单:查看第一辆车的位置,并将其添加到其他每辆车的地图中。然后查看第二辆车的位置,并将其添加到其他每辆车的地图中。等等。

但是存在可伸缩性问题。当有2辆汽车时,此策略需要执行4个"添加我的位置"步骤;当有3辆车时,它需要9步。对于每个"位置更新",我们都必须循环浏览整个汽车列表,每辆汽车都需要更新其位置。

忽略每辆汽车还必须执行多少其他操作(例如,计算单个汽车的位置可能需要固定数量的步骤),对于N辆汽车,需要N2次"汽车访问"才能运行此算法。当我们有5辆车和25步时,这没问题。但是,当我们添加汽车时,我们将看到系统停滞不前。 100辆汽车将走10,000步,而101辆汽车将走10,201步!

更好的方法是取消for循环的嵌套。

for each car {  
      add my position to a list;  
    }  
    for each car {    
      give me an updated copy of the master list;  
    }

使用此策略,步骤数是N的倍数,而不是N2的倍数。因此,100辆汽车将承担1辆汽车的工作量的100倍,而不是10,000倍。

这个概念有时用"大O表示法"表示,所需的步骤数是" N的大O"或者" N2的大O"。

请注意,此概念仅与可伸缩性有关,并未针对每辆汽车优化步数。这里我们不在乎每辆车要走5步还是50步,主要是N辆车要走(X * N)步,而不是(X * N2)。

回答

FWIW,大多数系统将通过忽略此问题来最有效地扩展,直到问题解决为止。而不是支付开发商。

就是说,最重要的地方是数据层。这是应用程序最难扩展的部分,因为它通常需要权威性,而集群商业数据库非常昂贵,开放源代码版本通常很难获得正确的答案。

如果我们认为应用程序很可能需要扩展,那么在开发的早期阶段就着眼于内存缓存或者map reduce这样的系统可能是明智的。

回答

好的,因此我们已经找到了使用"大O表示法"的关键点。如果我们不注意的话,那肯定可以在后部咬住我们。还有其他一些维度,有些人看不到"大O"形眼镜(但是如果我们仔细看,它们确实是)。

该维度的一个简单示例是数据库联接。例如,在构造左内部联接时存在"最佳实践",这将有助于使sql更有效地执行。如果我们分解了关系演算,甚至查看了解释计划(Oracle),我们都可以轻松地查看按哪个顺序使用了哪些索引,以及是否发生了表扫描或者嵌套操作。

概要分析的概念也很关键。我们必须在架构的所有移动部分中以正确的粒度进行全面的检测,以识别和修复任何低效率的问题。例如,假设我们正在构建一个3层,多线程,基于MVC2 Web的应用程序,该应用程序将自由使用AJAX和客户端处理以及应用程序与数据库之间的OR映射器。一个简单的线性单一请求/响应流如下所示:

browser -> web server -> app server -> DB -> app server -> XSLT -> web server -> browser JS engine execution & rendering

我们应该有一些方法可以测量每个不同区域的性能(响应时间,以"每单位时间的填充量"衡量的吞吐量等),而不仅仅是盒级和操作系统级别(CPU,内存,磁盘I / O,等),但特定于每个层的服务。因此,在Web服务器上,我们需要知道所用Web服务器的所有计数器。在应用程序层中,我们需要具有可见性以及对正在使用的任何虚拟机(jvm,clr等)的可见性。大多数OR映射器都显示在虚拟机内部,因此,如果在该层对我们可见,则请确保我们注意所有细节。在数据库内部,我们需要了解正在执行的所有内容以及适合数据库风格的所有特定调整参数。如果我们有很多钱,那么BMC Patrol(对于大多数人来说)是一个不错的选择(带有适当的知识模块(KM))。在便宜的一端,我们当然可以自己动手,但是里程会根据专业知识深度而有所不同。

假设一切都是同步的(无需等待任何基于队列的操作),则存在大量的性能和/或者可伸缩性问题的机会。但是由于文章是关于可伸缩性的,所以我们将忽略浏览器,除了将调用Web服务器的另一个请求/响应的任何远程XHR调用。

因此,考虑到这个问题领域,我们可以做出哪些决定来帮助实现可伸缩性?

  • 连接处理。这也绑定到会话管理和身份验证。在不影响安全性的前提下,它必须尽可能干净轻巧。度量标准是每单位时间的最大连接数。
  • 每个层的会话故障转移。必要与否?我们假设在某种负载平衡机制下,每一层将是水平的盒子集群。负载平衡通常非常轻巧,但是会话故障转移的某些实现可能比预期的要重。同样,是否使用粘性会话运行可能会在体系结构中更深地影响选择。我们还必须决定是否将Web服务器绑定到特定的应用程序服务器。在.NET远程处理世界中,将它们捆绑在一起可能更容易。如果我们使用Microsoft堆栈,则执行2层(跳过远程处理)可能更具可伸缩性,但是我们必须进行实质性的安全权衡。在Java方面,我总是至少看到3层。没有其他理由。
  • 对象层次结构。在应用程序内部,我们需要尽可能清洁,重量最轻的对象结构。仅在需要时才提供所需的数据。恶意删除任何不必要的或者多余的数据。
  • 或者映射器效率低下。对象设计和关系设计之间存在阻抗不匹配。 RDBMS中的多对多构造与对象层次结构(person.address与location.resident)直接冲突。数据结构越复杂,OR映射器的效率就越低。在某些时候,我们可能不得不一次性放弃诱饵,并采取更多...呃...原始数据访问方法(存储过程+数据访问层),以便从特定性能中挤出更多性能或者可伸缩性丑陋的模块。了解所涉及的成本,并做出明智的决定。
  • XSL转换。 XML是一种很棒的,标准化的数据传输机制,但是,它可以成为性能出色的狗!根据我们随身携带的数据量以及选择的解析器以及结构的复杂程度,我们可以使用XSLT轻松地将自己绘制到一个非常黑暗的角落。是的,从理论上讲,这是处理表示层的一种绝妙的方法,但是在现实世界中,如果我们不特别注意这一点,可能会遇到灾难性的性能问题。我已经看到系统仅在XSLT中就消耗了超过30%的事务时间。如果我们想在不购买额外包装的情况下将用户群扩大4倍,那就不太好了。
  • 我们是否可以摆脱可扩展性困境的出路?绝对地。我看过发生的次数超过了我想承认的次数。摩尔定律(如我们已经提到的)今天仍然有效。以防万一,请多准备一些现金。
  • 缓存是减少引擎负担的好工具(提高速度和吞吐量是一个方便的副作用)。尽管在缓存过时时使内存无效和使缓存无效方面的复杂性方面要付出一定的代价。我的决定是开始完全清理,然后仅在我们认为对自己有用的地方才慢慢添加缓存。太多次低估了复杂性,而最初作为解决性能问题的方法却导致了功能问题。同样,回到数据使用注释。如果我们每分钟要创建千兆字节的对象,则无论是否缓存都无所谓。我们很快就会将内存占用量最大化,垃圾回收将毁掉一天。因此,我想得出的主要结论是要确保我们完全了解虚拟机内部的情况(对象创建,销毁,GC等),以便做出最佳决策。

非常抱歉。刚滚动而忘了抬头。希望其中的某些内容能影响询问精神,并且不要过于初级。