Подтвердить что ты не робот

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

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

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

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

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

Примечание. Я не хочу загружать их все при запуске и делать с ним, потому что это будет

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

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

Спасибо за помощь

4b9b3361

Ответ 1

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

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

Минусы:

  • Следующий уровень инфраструктуры
  • +1 точка отказа (http proxy)
  • Более сложное развертывание

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

Ответ 2

Вот пример кэширования с EhCache. Этот код используется в нескольких проектах для реализации кэширования ad hoc.

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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; 

} 

Ответ 6

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

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