Java Web 应用程序:如何实现缓存技术?
Posted
技术标签:
【中文标题】Java Web 应用程序:如何实现缓存技术?【英文标题】:Java Web Application: How to implement caching techniques? 【发布时间】:2010-10-16 13:06:27 【问题描述】:我正在开发一个 Java Web 应用程序,该应用程序的行为基于从 Web 服务加载的大型 XML 配置文件。由于在访问应用程序的特定部分之前实际上并不需要这些文件,因此它们是延迟加载的。当需要这些文件之一时,会向 Web 服务发送查询以检索相应的文件。由于某些配置文件可能比其他配置文件更频繁地使用,因此我想设置某种缓存(可能有 1 小时的到期时间)以避免一遍又一遍地请求同一个文件。
Web 服务返回的文件对于所有会话中的所有用户都是相同的。我不使用 JSP、JSF 或任何其他花哨的框架,只使用普通的 servlet。
我的问题是,在 Java Web 应用程序中实现这种全局静态缓存的最佳实践是什么?单例类是否合适,还是会因为 J2EE 容器而出现奇怪的行为?我应该通过 JNDI 在某个地方公开一些东西吗?我应该怎么做才能使我的缓存不会在集群环境中被搞砸(可以,但不是必须的,每个集群服务器都有一个缓存)?
鉴于以上信息,将负责缓存的对象作为 ServletContext 属性放置是正确的实现吗?
注意:我不想在启动时加载所有这些并完成它,因为那样会
1)。每当我的应用程序启动时重载 Web 服务 2)。在我的应用程序运行时文件可能会发生变化,所以无论如何我都必须重新查询它们 3)。我仍然需要一个全局可访问的缓存,所以我的问题仍然存在
更新:使用缓存代理(例如 squid)可能是个好主意,但是对 web 服务的每个请求都会在 post Data 中发送相当大的 XML 查询,每次可能不同。只有 Web 应用程序真正知道对 Web 服务的两个不同调用实际上是等效的。
感谢您的帮助
【问题讨论】:
【参考方案1】:将缓存对象实例放入 ServletContext 没有任何问题。不要忘记此对象的 setAttributes 方法的其他 2 个选项(请求范围、会话范围)。任何在 webcontainers 和 j2ee 服务器中本机支持的东西都是好的(好的我的意思是它独立于供应商,并且没有像 Spring 这样的重型 j2ee 库)。我最大的要求是服务器在 5-10 秒内启动并运行。
我真的不喜欢所有的缓存解决方案,因为它很容易让它在本地机器上运行,而很难让它在生产机器上运行。 EHCACHE、Infinispan 等。除非您需要集群范围的复制/分发,与 Java 生态系统紧密集成,否则您可以使用 REDIS(NOSQL 数据库)或 nodejs ......任何带有 HTTP 接口的东西都可以。特别是
缓存真的很简单,这里是纯 java 解决方案(无框架):
import java.util.*;
/*
ExpirableObject.
Abstract superclass for objects which will expire.
One interesting design choice is the decision to use
the expected duration of the object, rather than the
absolute time at which it will expire. Doing things this
way is slightly easier on the client code this way
(often, the client code can simply pass in a predefined
constant, as is done here with DEFAULT_LIFETIME).
*/
public abstract class ExpirableObject
public static final long FIFTEEN_MINUTES = 15 * 60 * 1000;
public static final long DEFAULT_LIFETIME = FIFTEEN_MINUTES;
protected abstract void expire();
public ExpirableObject()
this(DEFAULT_LIFETIME);
public ExpirableObject(long timeToLive)
Expirer expirer = new Expirer(timeToLive);
new Thread(expirer).start();
private class Expirer implements Runnable
private long _timeToSleep;
public Expirer (long timeToSleep)
_timeToSleep = timeToSleep;
public void run()
long obituaryTime = System.currentTimeMillis() + _timeToSleep;
long timeLeft = _timeToSleep;
while (timeLeft > 0)
try
timeLeft = obituaryTime - System.currentTimeMillis();
if (timeLeft > 0)
Thread.sleep(timeLeft);
catch (InterruptedException ignored)
expire();
请参阅此link 以获得进一步的改进。
【讨论】:
【参考方案2】:Bref,你可以使用这个现成的spring ehcache配置
1- ehcache.xml : 显示 Ehcache 的全局配置。
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true" name="myCacheManager">
<!--
see ehcache-core-*.jar/ehcache-fallback.xml for description of elements
Attention: most of those settings will be overwritten by hybris
-->
<diskStore path="java.io.tmpdir"/>
</ehcache>
2- ehcache-spring.xml : 创建 EhCacheManagerFactoryBean 和 EhCacheFactoryBean。
<bean id="myCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
scope="singleton">
<property name="configLocation" value="ehcache.xml" />
<property name="shared" value="true" />
</bean>
<bean id="myCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean" scope="singleton">
<property name="cacheManager" ref="myCacheManager" />
<property name="cacheName" value="myCache" />
<property name="maxElementsInMemory" value="1000" />
<property name="maxElementsOnDisk" value="1000" />
<property name="eternal" value="false" />
<property name="diskPersistent" value="true" />
<property name="timeToIdle" value="600" />
<property name="timeToLive" value="1200" />
<property name="memoryStoreEvictionPolicy" value="LRU" />
<property name="statisticsEnabled" value="true" />
<property name="sampledStatisticsEnabled" value="true" />
</bean>
3- 在您的业务类中注入“myCache”bean,请参阅以下示例以开始获取对象并将其放入缓存中。
@Resource("myCache")
private net.sf.ehcache.Cache myCache;
@Resource("myService")
private Service myService;
public byte[] getFromCache(final String code)
// init Cache
final StringBuilder builder = new StringBuilder();
// key to identify a entry in cache map
final String key = code;
// get form the cache
final Element element = myCache.get(key);
if (element != null && element.getValue() != null)
return (byte[]) element.getValue();
final byte[] somethingToBeCached = myService.getBy(code);
// store in the cache
myCache.put(new Element(key, somethingToBeCached));
return somethingTobeCached;
【讨论】:
【参考方案3】:选项 #1:使用开源缓存库,例如 EHCache
当有许多不错的开源替代方案可供您加入并开始使用时,不要实现自己的缓存。实现自己的缓存比大多数人意识到的要复杂得多,如果你不知道你在用线程做什么,你很容易开始重新发明***并解决一些非常困难的问题。
我推荐 EHCache 它是在 Apache 许可下的。你会想看看the EHCace code samples。
选项 #2:使用 Squid
更简单的解决方案是使用 Squid...将 Squid 置于请求缓存数据的进程和发出请求的系统之间:http://www.squid-cache.org/
【讨论】:
谢谢,但这并不能真正回答我的问题。如果我决定使用 EHCache,如何在 java ee 容器中设置它,以便它在会话、各种类加载器混乱和集群环境中正常工作? 我不相信 Squid 会是正确的解决方案。它看起来非常聪明,但我不相信网络服务会尊重“如果修改”【参考方案4】:您的问题包含几个单独的问题。让我们慢慢开始吧。 ServletContext 是您可以将句柄存储到缓存的好地方。但是您通过每个服务器实例的缓存来付费。应该没问题。如果您想在更广泛的范围内注册缓存,请考虑将其注册到 JNDI 中。
缓存问题。基本上,您正在通过 web 服务检索 xml。如果你通过 HTTP 访问这个 web 服务,你可以在你身边安装简单的 HTTP 代理服务器来处理 xml 的缓存。下一步将在某种本地对象缓存中缓存已解析的 xml。每个服务器都可以存在此缓存,没有任何问题。在第二种情况下,EHCache 将完成完美的工作。在这种情况下,处理链将像这样Client - http request -> servlet -> look into local cache - if not cached -> look into http proxy (xml files) -> do proxy job (http to webservice)
。
优点:
每个服务器实例的本地缓存,仅包含来自请求的 xml 的对象 一个 http 代理在与我们的 web 应用程序相同的硬件上运行。 无需为 xml 文件添加新的 http 代理即可扩展 web 应用程序。缺点:
下一级基础设施 +1 故障点(http 代理) 更复杂的部署更新:不要忘记始终将 HTTP HEAD 请求发送到代理以确保缓存是最新的。
【讨论】:
【参考方案5】:这是一个使用 EhCache 进行缓存的示例。此代码在多个项目中用于实现临时缓存。
1) 将缓存放在全局上下文中。 (别忘了在WEB.XML中添加监听器)。
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
public class InitializationListener implements ServletContextListener
@Override
public void contextInitialized(ServletContextEvent sce)
ServletContext ctx = sce.getServletContext();
CacheManager singletonManager = CacheManager.create();
Cache memoryOnlyCache = new Cache("dbCache", 100, false, true, 86400,86400);
singletonManager.addCache(memoryOnlyCache);
cache = singletonManager.getCache("dbCache");
ctx.setAttribute("dbCache", cache );
2) 在需要时检索缓存实例。即来自 servlet:
cache = (Cache) this.getContext().getAttribute("dbCache");
3) 在执行昂贵操作之前查询缓存。
Element e = getCache().get(key);
if (e != null)
result = e.getObjectValue(); // get object from cache
else
// Write code to create the object you need to cache, then store it in the cache.
Element resultCacheElement = new Element(key, result);
cache.put(resultCacheElement);
4) 也不要忘记在适当的时候使缓存对象无效。
您可以找到更多示例here
【讨论】:
【参考方案6】:在自己环顾四周之后,似乎实现我所需要的最简单的方法(在问题中描述的要求和可接受的限制范围内)是将我的缓存对象添加到 Servlet 上下文中,并查看它在需要的地方向上(或传递)。
我只需从 ServletContextListener 实例化我的配置加载器,并在 contextInitialized() 方法中,我只需使用 ServletContext.setAttribute() 将其存储到 ServletContext 中。然后很容易使用 request.getSession().getServletContext().getAttribute() 从 servlet 本身查找它。
我想这是不引入 spring 或任何其他依赖注入框架的正确方法。
【讨论】:
你知道番石榴缓存吗?如果未设置,获取或检索? code.google.com/p/guava-libraries/wiki/CachesExplained以上是关于Java Web 应用程序:如何实现缓存技术?的主要内容,如果未能解决你的问题,请参考以下文章