cache技术提高Web应用性能(Enhancing Web Application Performance with Caching)

在学习享元模式时,发现这篇文章写的不错,翻译水平不高,有看到的同学请见谅。

原文链接:http://www.theserverside.com/tt/articles/article.tss?l=Caching

简介

内存资源是大型,繁忙应用的常见瓶颈。同时也是Web开发中最容易发生滥用和最容易获益的地方。在很多情况下,有效的缓存策略不但可以降低内存覆盖,也可以提高系统响应速度。缓存把最近使用的项目保留在内存中,预期它们还会被使用,因此是一个广为人知的优化技术。缓存可以有多种实现方式,包括正确的使用设计模式。

使用享元模式实现缓存

享元模式出现在四人组的书中,这本书软件开发中关于模式的重要工作。享元模式使用共享来支持大规模的细粒度对象引用。享元模式是一种让你保持对象池有效,然后为独特视角创建对象引用的策略。它使用标准对象的主意。一个标准对象是一个单一代表性的对象,代表了所有同一类型的其他对象。例如,你拥有一个特别的产品,它代表了所有同类型的产品。在一个应用中,取而代之的就是为每个用户创建一个产品的列表,你创建一个标准产品列表,而每个用户拥有该列表中对象的一个引用列表。

《Art of Java Web Development》中的默认应用eMotherEarth设计成为每个用户创建一个产品列表。然而,这样做是对内存的浪费。产品对所有用户都是相同的,而且产品的属性变化不是很频繁。图1表示了用户和目录中的产品列表当前的架构关系。

caching_01 图1 eMotherEarch应用中,每个用户在查看目录时拥有自己的产品列表。即使他们有不同的产品视图,也仍然看到相同的产品列表

为每个用户保留一个唯一列表造成内存浪费。尽管不同的用户有不同的产品视图,只有一个产品列表。用户可以改变顺序和目录页看到的产品页的价值,但是基本的产品属性对每个用户是相同。一个更好的设计是创建一个标准产品列表,对每个用户保留一个该列表的引用,这种用户/产品关系见图2.

caching_02 图2单一产品列表节省内存,为每个用户在给定时间看特定商品保留一个列表引用

在这个场景中,每个用户仍拥有特定产品集合的引用(为维护分页和排序),引用指回到标准产品列表。这个列表是应用中唯一的实际产品对象集合。它存储在中心位置,被所有应用的用户访问。

实现享元模式

eMotherEarth是Art of Java Web Development中突出的一个应用。它采用Model2 Web应用模型建模,很容易改为使用享元模式。这里展示的代码以改变边界和控制器类来实现缓存。可以在www.nealford.com/art.htm访问书的源代码下载该应用。第一步是创建标准产品列表并放在一个全局可访问的位置。很显然使用应用上下文。因此,eMotherEarth中的Welcome控制器被改为创建产品列表并放在应用上下文中,Welcome控制器中的修改过的init()和新的buildFlyweightReferences()方法见如下:

   buildFlyweightReferences()方法首先检查确保它没有被其他用户的welcome servlet调用创建。这个可能需要非常谨慎因为init()方法在载入内存时只调用一次。
但是,如果我们把这段代码放到doGet()或者doPost()方法就会被调用多次。这是一个足够简单的测试,它不会伤害到任何当前实现。如果这个标准列表还不存在,它就会
被创建,居中,放置在全局上下文。现在,当一个人用户需要从目录上查看产品,他们将从全局列表读取。这个目录控制器已经改变为从全球缓存读取产品列表供显示而不是创
建一个新的列表。目录控制器的doPost()方法如下:
    先前版本的目录控制器调用一个方法来创建和占据一个ProductDb边界类。然而,这个版本是简化了,因为它可以安全的假设产品记录已经存在于内存中。
因此,整个getProductBoundary()方法不再存在于程序这个版本。这是一种更少的代码,更快的性能,并占用内存少的少见情况!然而,另一个微小的变化是需要适应缓存。
在这以前,sortPagesForDisplay()方法如果没有分类标准存在于请求参数中,它简单的返回无序记录。这个控制器被设计成在getProductListSlice()方法中返回标
准列表的一部分。

以前,缺乏排序标准不引起任何问题,因为每一位用户都有主要列表的副本。这个方法返回用户的列表的一个子集。然而,现在所有的用户共享同一列表。这个来自集合API的subList()方法并不复制列表中的项目,而返回它们的引用。这是一个希望的特征,因为如果它复制了列表条目并返回,缓存列表的产品是无意义的。然而,因为现在只有一个实际的列表,当用户得到一些列表中记录的引用并调用该该sortPagesForDisplay()方法,列表成员在页面大小的块中排序。

先前的版本的sortPagesForDisplay()方法只当用户在请求参数中指定比较器时(即当用户在视图中点击一个列的标题)才调用排序方法。然而,如果这个实现保留的话,那么一个新的用户登录进来后仍然会看到上个用户对页面记录快的排序列表。这是因为一个新的用户没有指定一个分类标准(换句话说,他们还没有机会去点击一列标题和生成的分类标志)。缓存技术的副作用就是每一个用户排序列表的产品在page-sized块。尽管这对于一个给定的页面大小的块只是记录位置的改变,每个用户在他们看到的记录之前运用自己的分类标准的对列表排序。在记录数不多时,这种实现可以防止这种副作用而不会影响的性能。
使用这种设计模式是该控制器容易更新。用户在排序之前选择他们的记录页。如果排序在用户选择他们想要的记录之前发生,该控制器必须改变。然而,用户不会做出这样的要求,他们将不得不猜得到的页面记录分类。

考虑享元模式

享元模式作为一种缓存机制很大程度上依赖于数据的特点:

  • 应用程序使用大量的对象
  • 为多用户复制对象的内存开销很大
  • 对象不可变或者状态可以被外化
  • 相对来说很少的共享对象可以取代很多对象组
  • 这个应用程序并不取决于对象的属性。当用户可能认为他们是获得一个唯一的对象,实际上从缓存取得引用。

使用这种风格的缓存是关键是对象的状态信息。在上面的例子中,对用户而言产品对象是永恒不变。如果允许用户修改对象,这种缓存机制不会工作。它依靠的是对象存储在缓存的只读性。也可以对可变对象使用享元模式,但是他们的一些状态信息必须在对象外部。如图3。

caching_03 图3 通过添加外部信息连接产品对象与其引用,使得享元模式应用于可变对象。

存储可变对象需要对享元引用和享元对象之间的小的连接类的引用。一个很好的例子,这种类型的外部状态信息在eMotherEarth就是特定的项目是重量。这是特别给用户的信息,所以它不应被储存在高速缓存中。然而,有一个每一个产品的离散块信息。这种(或其他)会储存在一个关联类中。系在产品和引用之间。当你使用这个选项,和享元引用本身相比这些信息消耗很少的内存。否则,使用享元你不要保存任何资源。
当缓存中的对象改变很快时这种模式并不可取。当eMotherEarth产品每天修改多次,它就不是一个合适的缓存策略。然而,产品是入库的这似乎是不可能的。当你在你的全部或大部分用户之间共享不变对象时这种解决方案工作很好。当并发用户越多内存节省越明显。从而引出了一个很有趣的问题——你怎么知道缓存有效呢?有许多新式的虚拟机、高速缓存不是必需的,因为有高效的垃圾回收、内存管理器。测度这种改变是值得的。

效率测试

(略)

你可能感兴趣的:(performance)