提高数据密集型应用程序性能的技巧
在大规模应用程序中,数据流的重要性很容易被忽视,但是这可能会导致很严重的性能泄漏。在 Shantanu Bhattacharya 撰写的这一篇文章中,我们将探索可能影响具有多个服务器的 n 层应用程序性能的数据流的各个方面。您还会看到在大规模应用程序的设计与架构方面提高性能的一些技巧。
开发者论坛中对数据处理的讨论通常都围绕 applet 和 servlet 展开,而讨论焦点往往是一些显而易见的性能问题,例如观感、安全性和加载时间。但从一台机器传输到另一台机器的实际数据量(通常称为数据流)的问题却很少被讨论到。实际上,数据流是一个非常重要的课题,但却很少被提起,其重要性也没有得到充分的认识;对于大规模的数据密集型应用程序来说更是如此。
本文将介绍数据流在具有多个服务器的 n 层应用程序中是如何对性能产生影响的。我们将使用一个数据流模型来展示一些数据可能延缓或阻塞应用程序处理的接合点,并解释如何解决常见的验证、安全性和数据访问问题。您还会看到一些更高级的设计和架构决策,它们可以在很大程度上提高应用程序的性能。另外,我们将对数据的集中存储和分散存储进行评测,这在当前的 n 层系统环境中是一个关系重大、但尚未得到充分考虑的因素。
数据流在任何阶段都可能会延缓甚至破坏应用程序的运行,因此技巧就是预见问题,在问题出现前就将其解决掉。我将使用一个数据流模型来描述最常见的数据流瓶颈,以及避免此类瓶颈的一些技巧。图 1 展示了通过一个具有多个服务器的典型大规模 n 层应用程序的数据流。
图 1. 大规模应用程序中的数据流
下面让我们来看一下数据在哪些地方容易造成程序的延缓,以及您可采取的对策。
- 1. 从客户机到 Web 服务器
- 对于数据流来说,这是一个必不可少的步骤;但是在有些情况下,很多通过这个点的数据流都是不必要的。例如,在服务器端(而不是在客户端)进行大量简单验证的应用就会造成系统速度变慢。理想情况下,我们希望只有在数据由客户机成功进行验证之后才移动到服务器上。尽管在某些情况中验证实际上并不是在客户端发生的,但是如果我们对应用程序进行重组,即可解决这个问题。例如,数据验证通常都被视为业务逻辑,因此也会作为服务器端的一项功能来考虑。实际上,数据验证通常是 特定于数据的,因此应在客户端执行。
- 2. Web 服务器到应用服务器
- 如果您希望为客户机处理数据的呈现规则,请在服务器上进行。通常,Web 服务器的用途只是将数据传递到应用层;为扫清大量性能障碍,我们可以改为在 Web 服务器上处理数据。在呈现数据的情况中,无论如何都要为应用层来处理这些数据。尽管在应用层上处理数据从代码的角度来看要更加简单,但这也会造成传递的数据远远超过您的需要。多传输 10 个字节看起来不是什么大问题,但是一旦经过一百万次传输(在大型应用程序中很常见),所传输的不必要的数据就会多达 10 MB。这还没有考虑数据所使用的报头和报尾呢!
- 3. 应用服务器到数据库服务器
- 请在数据到达数据库服务器之前完成所有数据的处理。这可确保数据库服务器只需为快速简单地访问数据而对数据进行重组即可。它还能确保只有必要的数据会到达数据库服务器。我们应根据这些考虑事项来确定用于在应用层进行处理的服务器个数。应用层中每增加一台服务器,不仅会增加硬件成本,而且还会增加数据传输的负载。
- 4. 从数据库服务器检索的数据
- 按照检索过程中需要连接数最少的方法来存储数据,这意味着数据必须在适当的地方进行规范化(normalization)和反规范化(denormalization)。稍后我们将更详细讨论 数据的规范化和反规范化 问题。
- 5. 从应用层返回 Web 服务器
- 在检索过程中,只有一个应用服务器应在利用之前接触数据流,即使整个系统超过 3 层或应用层有多种类型的服务器也是如此。注意存储和处理数据所使用的路径都不需要与检索数据的路径相同。根据正在编写的应用程序考虑一下要检索的数据类型,这也是非常值得的。例如,在一个基于 Web 的交易站点上,我们很可能在客户下订单之后就立即检索这些订单(比如用户希望修改或取消的订单)。另一方面,如果从下订单到发货之间经过了很长时间,我们也可能需要访问很多数据来检查订单的状态。这些考虑事项都可以引导您以一些原本不会采取的方式优化代码。 #p#page_title#e#
- 6. 在客户机上显示检索到的数据
- 所有为数据存储而进行的处理也必须应用于数据检索。因此,我们有必要同时编写编码和解码例程,从而尽可能有效地使数据检索与处理相结合。
一旦您理解了数据流有可能在哪些特定点造成性能的大幅度降低,能够针对此类瓶颈编码,那么也就有了一个很好的开端。接下来,我们需要运用一种更高级的方法,从而避免出现可能导致系统性能下降的设计和架构错误。考虑一下大规模应用程序中可能对性能造成影响的一些功能,以及如何设计才能获得更好的性能。
如前所述,仅为了进行数据验证就将数据从客户机移动到服务器上是一个巨大的错误。将数据从客户机发送到服务器上,然后由于验证失败就拒绝用户请求,这只会增加所传输的不必要数据的总量。最好的选择是重组应用程序,使所有的验证都在客户端进行。如果不能这样做 —— 也就是说,如果您必须在服务器上验证数据 —— 至少可以使用诸如 ActiveX® 或 Java™ applet 之类的代码传输机制来进行这种操作。代码传输只会发生一次,但每次在另外一端遇到验证问题时都要传输数据。
如果您所处理的数据依赖于已为验证而存储的数据,还要保持那些已存储的数据在客户端可用。例如,若需要保存一个用户所提交值的列表,以便与新值比较,那么在客户机而不是服务器上存储这些数据会比较经济。多个用户所输入的数据量会很快超过您为验证而必须传输的数据量。
对于这条规则,确实有例外情况,例如在数据库列中进行惟一性测试时。由于无法在客户端完成这一任务,因此在服务器上进行验证是有必要的。然而在大部分情况中,在客户端完成数据验证是最佳实践。
需要确保安全性的数据通常会比不需保护的数据耗费更多的投影(Projections)操作。尽管看似违背直觉,但不安全的数据所经历的操作往往与安全数据完全相同,这只是因为这两类数据都被存储到了相同的位置。您可通过在数据库中独立存储安全数据和不安全数据来提高应用程序的整体性能。
将两种类型的数据独立存储无效果的惟一情况就是:安全数据量与不安全数据量相比非常少,例如,有些应用程序中惟一的安全机制就是用户密码。此时可以将这两类数据保存在一起,不过仍然需要确保对安全数据所进行的操作不会发生在不安全数据上。
访问频率是我们在确定存储标准时需要考虑的一个重要因素。例如,考虑一个医院信息系统。在这个系统中,对一位患者统计信息的访问频率要远远低于其姓名和 ID 号。因此,将统计信息与姓名和 ID 数据分开存储是很有意义的。如果将这两类数据存储在一起,那么每次访问患者的 ID 和姓名时,数据库都必须执行一些操作来过滤掉关于统计信息的数据。
在这个例子中,在一个表中还是在一个数据库中存储所有这些信息并不重要。如果是在一个表中,那么您必须执行一次投影操作来过滤数据。如果是在数据库中,若用于脱机处理的数据(例如数据仓库)与用于联机处理的数据存储在一起,就会降低系统速度。如果频繁访问的数据也会用于脱机处理,那么这种问题就会更加突出;此时,应将这些数据存储在另一个数据库中。
即便在所有的数据都依赖于联机事务处理时,您也可以考虑为某些数据使用另外一个数据库。我们可以考虑为某些数据使用一个不同的数据库,这样这个数据库中所有的数据都是用于在线事务处理的了。如果应用程序不会同时访问两部分数据,而且这两部分数据都可能会增长到很大(达到数 GB 或 TB),那么这是一种很好的策略。这时将数据划分到两个不同的数据库中会很有帮助。
很多时候您都能够根据应用程序的当前状态来预测其后续状态,并且能保证一定的准确度。例如,在贸易软件中,如果用户查询某个订单,那么他很可能将要查看这份订单的具体细节。对于绝大多数应用程序类型,我们都可以观察到类似的模式。观察这些模式,然后即可优化应用程序,方法是:根据您的预测算法,提前对某项给定操作后可能会被立即调用的数据进行一些操作。 #p#page_title#e#
乍看起来,这种策略似乎在系统中引入了有状态性,这可能会导致外扩问题,但实际上不会。
这种方法的确可以帮助您优化系统:只有在这些信息丢失时,系统运行速度才会减慢。预先操作失误的惟一情况就是所预测的结果不可用,例如下一个调用被路由到其他系统上去了。(这种策略对于那些采用服务器场的系统也非常有用。在这种情况中,信息可以简单地存储到一个跨场中所有服务器的集中储存库内。)
我们可以使用一个贸易应用程序的例子来进一步阐述这个问题,一名用户会访问一个订单。该请求被发送到应用服务器场内 3 个服务器中的第 1 个(server1)。应用程序预测这名用户接下来会访问同一订单的详细内容,并提前获取了这些数据。您可能会将这些数据存储在 server1 上。但是由于 server1 和 server2 繁忙,与该订单的详细信息有关的后续请求都被发送到了 server3 上。现在,如果与此订单的详细信息有关的数据在 server3 上不可用,那么它就只好自行获取这些数据。尽管这并没有带来预期的优化,但是也不会导致出现不一致的状态,因为订单的详细信息在任何一种情况中都可以获取到。为了利用这种优化机制,应将数据保存到场中所有服务器都可访问的共享存储中
从理论上来说,总是建议您保证数据的规范化;但是在实践中,过度的规范化(或者没有恰当反规范化的规范化)会导致对数据检索连接的依赖性。例如,考虑这样一种情况:患者姓名总是与其统计信息一起访问的,而患者 ID 是关键字。此时,将患者姓名与患者统计数据存储在一起是比较明智的(即便规范化要求与此不符),这样可以确保我们不需要在每次要访问患者统计信息时都需要对患者姓名执行一次连接操作。如果是这样,就需要在两个位置更新患者姓名。但由于更新患者姓名的频率远远低于更新患者统计信息的频率,因此就性能来说,系统依然得到了优化。
注意数据的规范化在任何面向数据库的应用程序中都是标准实践。在优化系统时,应首先从以充分规范化的数据为基础入手,这一点非常重要;否则,在更新数据时,就必须更新其所有实例。
在一个由多个服务器处理数据的 n 层应用程序中,聚合非常重要。例如,考虑这样一种情况,要打印的数据在抵达打印机之前必须通过多个服务器。如果不仔细考虑这个问题,这种类型的设置可能会造成相关服务器和打印进程的速度降低,在打印大量数据时更是如此。为了解决这个问题,请确保数据的使用点要与数据存储点尽可能地接近。(将数据移到数据库中时,这种技巧也同样适用。)
同时访问的数据应该总是存储在一起;分别访问的数据也应该独立存储。违背这一基本准则会导致数据库在访问数据时执行更多操作。将通常会被一起访问的数据分别存储将导致更多不必要的连接操作;将通常分别访问的数据存储在一起则意味着需要执行更多投影操作来过滤掉不需要的数据。
联机事务处理(OLTP)与联机分析处理(OLAP)并不相同,两种技术所使用的数据访问模式也有很大的区别。因此,每种处理可能需要不同的数据库 —— 即使这意味着需要将相同的数据重复存储两次,也是需要这样做的。独立存储 OLTP 数据和 OLAP 数据使您可为每种技术的访问模式进行优化。在 表 1 中,您可以看到 OLTP 和 OLAP 数据库间差异的细目,这些差异主要与每种技术使用的访问模式和资源使用情况相关。
表 1. OLTP 与 OLAP 处理的对比
特性 | OLTP | OLAP |
---|---|---|
访问模式 | 反复 | 偶尔 |
操作 | 读写 | 全表扫描 |
工作单位 | 基于哈希或关键字/索引的简单查询 | 复杂查询 |
所访问的记录数量 | 数十或数百 | 数百万(全表) |
大小 | 数十 MB 到 GB | 数十 GB 到 TB |
度量标准 | 事务处理吞吐量 | 查询吞吐量 |
尽管这个问题总是被忽视,但在数据密集型应用程序中,数据流确实会消耗大量带宽。应用程序越大、越复杂,其中的数据移动就越多,设计时考虑到的问题也必须越复杂。因此最好建模数据流通过应用程序的路径,并考虑清楚常规应用程序功能中可能减缓数据流速度的各点。接下来,考虑应用程序的架构,确定可以优化哪些地方来提高性能。只要遇到疑问,就回头来做最基本的事情:检查数据流的路径,确保应用程序在每个接合点处都执行良好。
现货:全球最快的Fusion-io SSD硬盘卡-提升IO速度200倍述评