Chapter 19. 缓存

数据库成为了大多数企业应用的主要瓶颈,也成为了运行环境中最不具伸缩性的层。PHP/Ruby的用户会说什么都不共享(share nothing)的架构照样具有很好的伸缩性。从表面上看也许是对的,可惜我不知道是否存在这样的多用户应用,其实现是能够在集群的不同结点间不共享资源。这些傻瓜真正想的是“除了数据库以外什么都不共享(Share nothing except for the database)”的架构。当然,共享数据库是多用户应用伸缩性的主要问题——因此声称这样的架构具有高伸缩性是荒谬的,你可要知道它们花费了这些人的大部分时间。

通常,几乎所有通过共享数据库做的事情并不值得这样去做。

这就是缓存(Cache)产生的原因。嗯,当然并不只是一个缓存。一个设计良好的Seam应用将具有丰富的多层缓存策略,这也影响着应用的每一层:

如要获得更多关于二级缓存的信息,你可以参考你的ORM解决方案的文档,因为这是个极为复杂的话题。在这节中我们会直接讨论通过 pojoCache 组件使用JBossCache,或者通过 <s:cache>控制充当页片段(page fragment)缓存。

19.1. 在Seam中使用JBossCache

内建的 pojoCache 组件用来管理 org.jboss.cache.aop.PojoCache 实例。你可以安全的把任何不可变的Java对象保存在缓存里,然后它将通过集群被复制(假设打开了复制)。如果你想在缓存里保存可变的Java对象,你需要运行JBossCache字节码预处理器来确保这些对象的变化能够自动的被探测到并被复制。

为了使用 pojoCache,你需要做的就是把JBossCache jar文件放在classpath中,并提供一个名为 treecache.xml 的文件,它应该有一个合适的cache配置。JBossCache有很多让人觉得恐怖和迷惑的配置,因此我们不在这里讨论它们。请参考JBossCache文档来获得更多信息。

examples/blog/resources/treecache.xml 里有一个 treecache.xml 的范例。

对Seam的EAR部署,我们推荐把JBossCache jar文件和配置文件直接放到EAR里。确保你在EAR的lib目录里放置了 jboss-cache.jarjgroups.jar

现在你可以把缓存注入到任何Seam组件:

@Name("chatroom")
public class Chatroom {
    @In PojoCache pojoCache;

    public void join(String username) {
      try
      {
         Set<String> userList = (Set<String>) pojoCache.get("chatroom", "userList");
         if (userList==null)
         {
            userList = new HashSet<String>();
            pojoCache.put("chatroom", "userList", userList);
         }
         userList.put(username);
      }
      catch (CacheException ce)
      {
         throw new RuntimeException(ce);
      }
    }
}

如果你想在应用中使用多个JBossCache配置,需要使用 components.xml:

<core:pojo-cache name="myCache" cfg-resource-name="myown/cache.xml"/>

19.2. 页片段缓存

使用JBossCache最有趣的是 <s:cache> 标签,Seam用它来解决JSF的页片段缓存问题。<s:cache> 内部使用了 pojoCache,因此你在使用它时需要遵循上面列出的步骤。(把jar文件放到EAR里,并进行令人恐怖的配置选项等。)

<s:cache>被用来缓存一些不太变化的内容。例如我们blog欢迎页面显示的近期blog:

<s:cache key="recentEntries-#{blog.id}" region="welcomePageFragments">
   <h:dataTable value="#{blog.recentEntries}" var="blogEntry">
      <h:column>
         <h3>#{blogEntry.title}</h3>
         <div>
            <s:formattedText value="#{blogEntry.body}"/>
         </div>
      </h:column>
   </h:dataTable>
</s:cache>

key使得每个页片段拥有多个缓存版本。在这种情形下,每个blog拥有一个缓存版本。region 决定了所有版本都将保存在哪个JBossCache结点里。不同结点可能有不同的过期策略。(这是你设置上述令人恐怖的配置选项做的事情。)

当然,<s:cache>的一个重要问题是它无法知道数据的变化(例如当博客作者发布一个新实体)。因此你需要自己管理被缓存的片段。

public void post() {
    ...
    entityManager.persist(blogEntry);
    pojoCache.remove("welcomePageFragments", "recentEntries-" + blog.getId() );
}

另一种方法是,如果不必把数据的变化立刻反馈给用户,那么你可以在JbossCache结点上设置一个短的过期时间。