Веб-приложение на Java: как реализовать методы кэширования? - PullRequest
24 голосов
/ 31 марта 2009

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

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

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

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

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

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

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

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

Ответы [ 6 ]

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

Вы можете найти больше образцов здесь

13 голосов
/ 31 марта 2009

Ваш вопрос содержит несколько отдельных вопросов вместе. Давайте начнем медленно. 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 в прокси, чтобы обеспечить актуальность кэша.

8 голосов
/ 31 марта 2009

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

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

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

Вариант № 2: Использовать Squid

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

1 голос
/ 13 июня 2016

Бреф, вы можете использовать эту готовую конфигурацию 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; 

} 
1 голос
/ 31 марта 2009

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

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

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

0 голосов
/ 21 июня 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();
    }
  }
}

Пожалуйста, обратитесь к этой ссылке для дальнейших улучшений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...