Java Web 应用程序:如何实现缓存技术?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/699996/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 18:30:08  来源:igfitidea点击:

Java Web Application: How to implement caching techniques?

javaservletscachingjakarta-ee

提问by LordOfThePigs

I am developing a Java web application that bases it behavior through large XML configuration files that are loaded from a web service. As these files are not actually required until a particular section of the application is accessed, they are loaded lazily. When one of these files are required, a query is sent to the webservice to retrieve the corresponding file. As some of the configuration files are likely to be used much, much more often than others I'd like to setup some kind of caching (with maybe a 1 hour expiration time) to avoid requesting the same file over and over.

我正在开发一个 Java Web 应用程序,该应用程序基于从 Web 服务加载的大型 XML 配置文件的行为。由于在访问应用程序的特定部分之前实际上不需要这些文件,因此它们被延迟加载。当需要这些文件之一时,会向 Web 服务发送查询以检索相应的文件。由于某些配置文件的使用频率可能比其他文件高得多,因此我想设置某种缓存(可能有 1 小时的到期时间)以避免一遍又一遍地请求相同的文件。

The files returned by the web service are the same for all users across all sessions. I do not use JSP, JSF or any other fancy framework, just plain servlets.

Web 服务返回的文件对于所有会话中的所有用户都是相同的。我不使用 JSP、JSF 或任何其他花哨的框架,只使用普通的 servlet。

My question is, what is considered a best practice to implement such a global, static cache within a java Web application? Is a singleton class appropriate, or will there be weird behaviors due to the J2EE containers? Should I expose something somewhere through JNDI? What shall I do so that my cache doesn't get screwed in clustered environments (it's OK, but not necessary, to have one cache per clustered server)?

我的问题是,在 Java Web 应用程序中实现这种全局静态缓存的最佳实践是什么?单例类是否合适,还是会因为 J2EE 容器而出现奇怪的行为?我应该通过 JNDI 在某处公开一些东西吗?我应该怎么做才能让我的缓存在集群环境中不会被搞砸(可以,但没有必要,每个集群服务器有一个缓存)?

Given the informations above, Would it be a correct implementation to put an object responsible for caching as a ServletContext attribute?

鉴于上述信息,将负责缓存的对象作为 ServletContext 属性是否是正确的实现?

Note: I do not want to load all of them at startup and be done with it because that would

注意:我不想在启动时加载所有这些并完成它,因为那会

1). overload the webservice whenever my application starts up
2). The files might change while my application is running, so I would have to requery them anyway
3). I would still need a globally accessible cache, so my question still holds

1)。每当我的应用程序启动时都会使网络服务过载
2)。在我的应用程序运行时文件可能会更改,因此无论如何我都必须重新查询它们
3)。我仍然需要一个全局可访问的缓存,所以我的问题仍然成立

Update: Using a caching proxy (such as squid) may be a good idea, but each request to the webservice will send rather large XML query in the post Data, which may be different each time. Only the web application really knows that two different calls to the webservice are actually equivalent.

更新:使用缓存代理(例如squid)可能是个好主意,但是对webservice 的每个请求都会在post Data 中发送相当大的XML 查询,每次可能都不同。只有 Web 应用程序真正知道对 Web 服务的两个不同调用实际上是等效的。

Thanks for your help

谢谢你的帮助

采纳答案by Rastislav Komara

Your question contains several separate questions together. Let's start slowly. ServletContext is good place where you can store handle to your cache. But you pay by having cache per server instance. It should be no problem. If you want to register cache in wider range consider registering it into JNDI.

您的问题包含几个单独的问题。让我们慢慢开始。ServletContext 是您可以存储缓存句柄的好地方。但是您通过为每个服务器实例设置缓存来付费。应该没有问题。如果您想在更大范围内注册缓存,请考虑将其注册到 JNDI。

The problem with caching. Basically, you are retrieving xml via webservice. If you are accesing this webservice via HTTP you can install simple HTTP proxy server on your side which handle caching of xml. The next step will be caching of resolved xml in some sort of local object cache. This cache can exists per server without any problem. In this second case the EHCache will do perfect job. In this case the chain of processing will be like this Client - http request -> servlet -> look into local cache - if not cached -> look into http proxy (xml files) -> do proxy job (http to webservice).

缓存的问题。基本上,您是通过 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)

Pros:

优点:

  • Local cache per server instance, which contains only objects from requested xmls
  • One http proxy running on same hardware as our webapp.
  • Possibility to scale webapp without adding new http proxies for xml files.
  • 每个服务器实例的本地缓存,仅包含来自请求的 xml 的对象
  • 一个 http 代理运行在与我们的 web 应用程序相同的硬件上。
  • 无需为 xml 文件添加新的 http 代理即可扩展 web 应用程序的可能性。

Cons:

缺点:

  • Next level of infrastructure
  • +1 point of failure (http proxy)
  • More complicated deployment
  • 下一级基础设施
  • +1 点故障(http 代理)
  • 更复杂的部署

Update: don't forget to always send HTTP HEAD request into proxy to ensure that cache is up to date.

更新:不要忘记始终将 HTTP HEAD 请求发送到代理以确保缓存是最新的。

回答by Tim O'Brien

Option #1: Use an Open Source Caching Library Such as EHCache

选项 #1:使用开源缓存库,例如 EHCache

Don't implement your own cache when there are a number of good open source alternatives that you can drop in and start using. Implementing your own cache is much more complex than most people realize and if you don't know exactly what you are doing wrt threading you'll easily start reinventing the wheel and resolving some very difficult problems.

当有许多优秀的开源替代方案可供您插入并开始使用时,请不要实现您自己的缓存。实现你自己的缓存比大多数人意识到的要复杂得多,如果你不确切地知道你在做什么 wrt 线程,你将很容易开始重新发明轮子并解决一些非常困难的问题。

I'd recommend EHCache it is under an Apache license. You'll want to take a look at the EHCace code samples.

我推荐 EHCache 它是在 Apache 许可下。您需要查看EHCace 代码示例

Option #2: Use Squid

选项#2:使用鱿鱼

An even easier solution to your problem would be to use Squid... Put Squid in between the process that requests the data to be cached and the system making the request: http://www.squid-cache.org/

解决您的问题的更简单的方法是使用 Squid...将 Squid 置于请求缓存数据的进程和发出请求的系统之间:http: //www.squid-cache.org/

回答by LordOfThePigs

After doing some more looking around myself, it seems that the easiest way to achieve what I need (within the requirements and acceptable limitations described in the question), would be to add my caching object to the Servlet Context, and looking it up (or passing it around) where needed.

在对自己进行了更多环顾之后,似乎实现我需要的最简单方法(在问题中描述的要求和可接受的限制范围内)是将我的缓存对象添加到 Servlet 上下文,并查找它(或在需要的地方传递它)。

I'd just instantiate my configuration loader from a ServletContextListener, and within the contextInitialized() method, I'd just store it into the ServletContext using ServletContext.setAttribute(). It's then easy to look it up from the servlets themselves using request.getSession().getServletContext().getAttribute().

我只是从 ServletContextListener 实例化我的配置加载器,在 contextInitialized() 方法中,我只是使用 ServletContext.setAttribute() 将它存储到 ServletContext 中。然后很容易使用 request.getSession().getServletContext().getAttribute() 从 servlet 本身查找它。

I suppose this is the proper way to do it without introducing spring or any other dependency injection framework.

我想这是在不引入 spring 或任何其他依赖注入框架的情况下做到这一点的正确方法。

回答by javito

Here's an example of caching with EhCache. This code is used in several projects to implement ad hoc caching.

这是使用 EhCache 进行缓存的示例。此代码在多个项目中用于实现临时缓存。

1) Put your cache in the global context. (Don't forget to add the listener in WEB.XML).

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) Retrieve the cache instance when you need it. i.e. from a servlet:

2) 在需要时检索缓存实例。即来自 servlet:

cache = (Cache) this.getContext().getAttribute("dbCache");

cache = (Cache) this.getContext().getAttribute("dbCache");

3) Query the cache just before you do an expensive operation.

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) Also don't forget to invalidate cached objects when appropriate.

4) 也不要忘记在适当的时候使缓存的对象无效。

You can find more samples here

您可以在此处找到更多示例

回答by Monsif EL AISSOUSSI

Bref , you can use this ready spring ehcache configuration

Bref ,你可以使用这个现成的 spring ehcache 配置

1- ehcache.xml: show global configuration of 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: create EhCacheManagerFactoryBean and EhCacheFactoryBean.

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- Inject "myCache" bean in your business class , see the following exemple to get started with getting and putting a object in your cache.

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; 

} 

回答by Mitja Gustin

I did not had any problems with putting cached object instance inside ServletContext. Do not forget other 2 options (request scope, session scope) with setAttributes methods of this objects. Anything that is supported natively inside webcontainers and j2ee serveers is good (by good I mean it's vendor independed, and without heavy j2ee librarires like Spring). My biggest requirements is that servers gets up and running in 5-10 seconds.

将缓存的对象实例放入 ServletContext 中没有任何问题。不要忘记带有此对象的 setAttributes 方法的其他 2 个选项(请求范围、会话范围)。任何在 webcontainers 和 j2ee 服务器中本地支持的东西都是好的(好的,我的意思是它独立于供应商,并且没有像 Spring 这样沉重的 j2ee 库)。我最大的要求是服务器在 5-10 秒内启动并运行。

I really dislike all caching solution, beacuse it's so easy to get it working on local machine, and hard to get it working on production machines. EHCACHE, Infinispan etc.. Unless you need cluster wide replication / distribution, tightly integrated with Java ecosystem, you can use REDIS (NOSQL datatabase) or nodejs ... Anything with HTTP interface will do. Especially

我真的不喜欢所有的缓存解决方案,因为它很容易在本地机器上运行,而在生产机器上运行却很难。EHCACHE、Infinispan 等。除非你需要集群范围内的复制/分发,与 Java 生态系统紧密集成,否则你可以使用 REDIS(NOSQL 数据库)或 nodejs ......任何带有 HTTP 接口的都可以。尤其

Caching can be really easy, and here is the pure java solution (no frameworks):

缓存真的很容易,这里是纯 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();
    }
  }
}

Please refer to this linkfor further improvements.

请参阅此链接以获得进一步的改进。