2013-08-16 5 views
1

У меня есть интересная задача, где нужно кэшировать результаты моего метода, который на самом деле просто с кэшем весной абстракциивесенний кэш, TTL unles обслуживание вниз

@Cachable(...) 
public String getValue(String key){ 
    return restService.getValue(key); 
} 

restService.getValue() задает службу REST, которая может отвечать или нет, если конечная точка не работает.

Мне нужно установить определенный TTL для значения кеша, скажем 5 минут, но в случае, если сервер не работает, мне нужно вернуть последнее значение, даже если оно продолжается 5 минут.

Я думал о наличии второго метода кэширования, который не имеет TTL и всегда возвращает последнее значение, он будет вызываться из getValue, если restService ничего не возвращает, но, может быть, есть лучший способ?

ответ

1

Я был тоже заинтересован в этом. Извините, что я не нашел тривиального способа сделать это. Весна не сделает этого для вас, это скорее вопрос о том, может ли эта реализация кэша использовать упаковку. Я предполагаю, что вы используете реализацию EhCache. К сожалению, эта функциональность не выходит из коробки, насколько я знаю.

Существуют различные способы, можно достичь что-то подобное в зависимости от вашей проблемы

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

@Autowired  
EhCacheCacheManager ehCacheCacheManager; 
... 
//in the infinite loop 
      List keys = ((Ehcache) ehCacheCacheManager.getCache("test").getNative    Cache()).getKeys(); 
      for (int i = 0; i < keys.size(); i++) { 
       Object o = keys.get(i); 
       Ehcache ehcache = (Ehcache)ehCacheCacheManager.getCache("test").getNativeCache() 
       Element item = (ehcache).get(o); 

       //get the data based on some info in the value, and if no exceptions   
       ehcache.put(new Element(element.getKey(), newValue)); 

      } 
  • выгоды это очень быстро для @Cacheable абонента, недостатком является то, ваш сервер может получить больше хитов чем необходимо

2) Вы можете сделать CacheListener для прослушивания события выселения, временно сохранить данные. И если вызов сервера завершится неудачно, используйте эти данные и вернитесь из метода.

ehcache.xml

<cacheEventListenerFactory class="caching.MyCacheEventListenerFactory"/> 

    </cache> 
</ehcache> 

Фабрика: импорт net.sf.ehcache.event.CacheEventListener; import net.sf.ehcache.event.CacheEventListenerFactory; импорт java.util.Properties;

public class MyCacheEventListenerFactory extends CacheEventListenerFactory { 
    @Override 
    public CacheEventListener createCacheEventListener(Properties properties) { 
    return new CacheListener(); 
    } 
} 

Псевдо-реализация импорта net.sf.ehcache.CacheException; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Element; импорт net.sf.ehcache.event.CacheEventListener;

import java.util.concurrent.ConcurrentHashMap; 

public class CacheListener implements CacheEventListener { 
    //prob bad practice to use a global static here - but its just for demo purposes 
    public static ConcurrentHashMap myMap = new ConcurrentHashMap(); 

    @Override 
    public void notifyElementPut(Ehcache ehcache, Element element) throws CacheException { 
    //we can remove it since the put happens after a method return 
    myMap.remove(element.getKey()); 
    } 

    @Override 
    public void notifyElementExpired(Ehcache ehcache, Element element) { 
    //expired item, we should store this 
    myMap.put(element.getKey(), element.getValue()); 
    } 
//.... 
} 
  • Задача здесь в том, что ключ не очень полезно, возможно, потребуется хранить что-то о ключе в возвращаемое значение, чтобы иметь возможность забрать его, если вызов сервера не удается. Это кажется немного взломанным, и я не определил, действительно ли это доказательство пули. Это может потребоваться некоторое тестирование.

3) Много усилий, но работает:

@Cacheable("test") 
public MyObject getValue(String data) { 
    try { 
     MyObject result = callServer(data); 
     storeResultSomewhereLikeADatabase(result); 
    } catch (Exception ex) { 
     return getStoredResult(data); 
    } 
} 

Про здесь является то, что он будет работать между перезагрузкой сервера, и вы можете расширить это просто разрешить общие кэша между кластерными серверами. У меня была версия в 12 кластерной среде, где каждый из них сначала проверял базу данных, чтобы узнать, получили ли какие-либо другие кластеры «дорогие» данные сначала , а затем повторно использовали это, а не вызывали вызов сервера.

Небольшой вариант также должен был бы использовать второй метод @Cacheable вместе с @CachePut, а не БД для хранения данных. Но это означало бы удвоение использования памяти. Это может быть приемлемо в зависимости от ваших размеров результатов.

0

Возможно, вы можете использовать spel для изменения используемого кеша (один использует ttl, а второй нет), если условие (является сервисом up?) Является истинным или ложным, я никогда не использовал этот способ (я использовал его изменить ключ на основе некоторого запроса Params), но я думаю, что она могла бы работать

@Cacheable(value = "T(com.xxx.ServiceChecker).checkService()",...) 

где checkService() статический метод, который возвращает имя кэша, который следует использовать

+0

это звучит так, как будто на целевой сервер требуется 2 вызова, один из которых определяет, находится ли сервер, а второй - для получения фактических данных. 2 вызова означает, что сервер может опуститься посередине. – zibi

+0

, возможно, использование какой-то пользовательской логики внутри этого метода может многое помочь, но в конце это не большое улучшение, как вы предложили в качестве основной альтернативы в своем вопросе :( –