Веб-приложение Java: как реализовать методы кеширования?

Я разрабатываю веб-приложение на Java, поведение которого основывается на больших файлах конфигурации XML, загружаемых из веб-службы. Поскольку эти файлы фактически не требуются до тех пор, пока не будет получен доступ к определенному разделу приложения, они загружаются лениво. Когда требуется один из этих файлов, в веб-службу отправляется запрос на получение соответствующего файла. Поскольку некоторые из файлов конфигурации, вероятно, будут использоваться намного, намного чаще, чем другие, я хотел бы настроить какое-то кеширование (возможно, со сроком действия 1 час), чтобы не запрашивать один и тот же файл снова и снова.

Файлы, возвращаемые веб-службой, одинаковы для всех пользователей во всех сеансах. Я не использую JSP, JSF или какой-либо другой модный фреймворк, только простые сервлеты.

Мой вопрос: что считается наилучшей практикой для реализации такого глобального статического кеша в веб-приложении Java? Подходит ли одноэлементный класс, или из-за контейнеров J2EE будет странное поведение? Стоит ли через JNDI что-то выставлять? Что мне делать, чтобы мой кеш не зависал в кластерных средах (это нормально, но не обязательно, иметь один кеш на каждый кластерный сервер)?

Учитывая приведенную выше информацию, будет ли правильная реализация поместить объект, ответственный за кеширование, в качестве атрибута ServletContext?

Примечание: я не хочу загружать их все при запуске и заканчивать с этим, потому что это

1). перегружать веб-сервис всякий раз, когда мое приложение запускается
2). Файлы могут измениться во время работы моего приложения, поэтому мне все равно придется запросить их повторно
3). Мне все равно понадобится глобально доступный кеш, поэтому мой вопрос все еще остается в силе

Обновление: использование кэширующего прокси (такого как squid) может быть хорошей идеей, но каждый запрос к веб-сервису будет отправлять довольно большой XML-запрос в данных публикации, которые каждый раз могут отличаться. Только веб-приложение действительно знает, что два разных вызова веб-службы фактически эквивалентны.

Спасибо за вашу помощь


person LordOfThePigs    schedule 31.03.2009    source источник


Ответы (6)


Ваш вопрос состоит из нескольких отдельных вопросов. Начнем медленно. ServletContext - хорошее место, где вы можете хранить дескриптор вашего кеша. Но вы платите, имея кеш на каждый экземпляр сервера. Это не должно быть проблемой. Если вы хотите зарегистрировать кеш в более широком диапазоне, рассмотрите возможность регистрации его в JNDI.

Проблема с кешированием. По сути, вы получаете xml через веб-сервис. Если вы получаете доступ к этому веб-сервису через HTTP, вы можете установить на своей стороне простой 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).

Плюсы:

  • Локальный кеш для каждого экземпляра сервера, который содержит только объекты из запрошенных xmls
  • Один http-прокси, работающий на том же оборудовании, что и наше веб-приложение.
  • Возможность масштабировать веб-приложение без добавления новых http-прокси для файлов xml.

Минусы:

  • Новый уровень инфраструктуры
  • +1 точка отказа (http прокси)
  • Более сложное развертывание

Обновление: не забывайте всегда отправлять HTTP-запрос HEAD в прокси, чтобы обеспечить актуальность кеша.

person Community    schedule 31.03.2009

Вот пример кеширования с помощью 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) Получите экземпляр кеша, когда он вам понадобится. то есть из сервлета:

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) Также не забудьте сделать недействительными кэшированные объекты, когда это необходимо.

Дополнительные образцы можно найти здесь

person javito    schedule 31.03.2009

Вариант №1: используйте библиотеку кэширования с открытым исходным кодом, такую ​​как EHCache

Не реализуйте свой собственный кеш, если есть несколько хороших альтернатив с открытым исходным кодом, которые вы можете использовать и начать использовать. Реализация собственного кеша намного сложнее, чем думает большинство людей, и если вы точно не знаете, что делаете с потоками, вы легко начнете изобретать колесо и решать некоторые очень сложные проблемы.

Я бы рекомендовал EHCache, он находится под лицензией Apache. Вы захотите взглянуть на образцы кода EHCace.

Вариант 2. Используйте Squid

Еще более простым решением вашей проблемы было бы использование Squid ... Поместите Squid между процессом, который запрашивает данные для кеширования, и системой, выполняющей запрос: http://www.squid-cache.org/

person Tim O'Brien    schedule 31.03.2009
comment
Спасибо, но это не совсем ответ на мой вопрос. Если я решу использовать EHCache, как мне настроить его в контейнере java ee, чтобы он правильно работал во всех сеансах, различных беспорядках загрузчика классов и в кластерных средах? - person LordOfThePigs; 31.03.2009
comment
Я не уверен, что Squid будет правильным решением. Это кажется ужасно умным, однако я не верю, что веб-сервисы будут уважать, если будут изменены - person monksy; 01.11.2009

После того, как я еще немного осмотрелся, кажется, что самый простой способ достичь того, что мне нужно (в рамках требований и приемлемых ограничений, описанных в вопросе), - это добавить мой объект кэширования в контекст сервлета и найти его (или передавая его) там, где это необходимо.

Я бы просто создал экземпляр загрузчика конфигурации из ServletContextListener, а в методе contextInitialized () я бы просто сохранил его в ServletContext с помощью ServletContext.setAttribute (). Затем его легко найти в самих сервлетах, используя request.getSession (). GetServletContext (). GetAttribute ().

Я полагаю, что это правильный способ сделать это без использования Spring или какой-либо другой инфраструктуры для внедрения зависимостей.

person LordOfThePigs    schedule 31.03.2009
comment
Вы знаете о кеш-памяти гуавы? получить или получить, если не настроен? code.google.com/p/guava-libraries/wiki/CachesExplained - person maress; 12.07.2015

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. Внедрите bean-компонент «myCache» в свой бизнес-класс, см. Следующий пример, чтобы начать работу с получением и помещением объекта в ваш кеш.

@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; 

} 
person Monsif EL AISSOUSSI    schedule 13.06.2016

У меня не было проблем с помещением экземпляра кешированного объекта в ServletContext. Не забудьте еще 2 варианта (область запроса, область сеанса) с методами setAttributes этих объектов. Все, что изначально поддерживается внутри веб-контейнеров и j2ee-серверов, хорошо (под хорошим я подразумеваю, что оно не зависит от производителя и без тяжелых j2ee-библиотек, таких как Spring). Мои самые большие требования - это чтобы серверы запускались за 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();
    }
  }
}

См. Эту ссылку для дальнейших улучшений.

person Mitja Gustin    schedule 21.06.2016