2014-12-18 3 views
4

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

List<Metrics> GetAllInterestingMetrics5Mins(); 
List<Metrics> GetAllInterestingMetrics10Mins(); 
List<Metrics> GetAllInterestingMetrics30Mins(); 

Мой curent решение использовать 3 гуавы тайники с выселением на основе времени, установленного на 5, 10 & 15 минут. Когда кто-то вызывает одну из вышеперечисленных функций, я возвращаю все показатели из кэша relvant.

Есть 2 проблемы с этим -

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

Есть ли способ решить эти 2 проблемы в Guava или любом другом из кеширования?

ответ

1

Что касается Темы 1:

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

Все распространенные продукты кеша не поддерживают точную, так называемую «точку во времени», истекают. Нам очень нужна эта функция в наших приложениях, поэтому я потратил некоторое усилие на cache2k, чтобы поддержать это.

Вот план для cache2k:

static class MetricsEntry { 

    long nextUpdate; 
    List<Metrics> metrics; 

} 

static class MyEntryExpiryCalculator implements EntryExpiryCalculator<Integer, MetricsEntry> { 
    @Override 
    public long calculateExpiryTime(Integer _key, MetricsEntry _value, long _fetchTime, CacheEntry _oldEntry) { 
    return _value.nextUpdate; 
    } 
} 

Cache createTheCache() { 
    Cache<Integer, MetricsEntry> cache = 
    CacheBuilder.newCache(Integer.class, MetricsEntry.class) 
     .sharpExpiry(true) 
     .entryExpiryCalculator(new MyEntryExpiryCalculator()) 
     .source(new MySource()) 
     .build(); 
    return cache; 
} 

Если у вас есть ссылки времени в объектах метрик, вы можете использовать это, и вы можете пропустить дополнительный класс входа. sharpExpiry(true) инструктирует cache2k для точного истечения срока действия. Если вы оставите это, истечение срока действия может занять несколько миллисекунд, но время доступа будет немного быстрее.

Что касается темы 2:

Прямой вперед подход будет использовать интервал минут как ключ кэша.

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

static class MySource implements CacheSource<Integer, MetricsEntry> { 
    @Override 
    public MetricsEntry get(Integer interval) { 
    MetricsEntry e = new MetricsEntry(); 
    boolean crossedIntervalEnd; 
    do { 
     long now = System.currentTimeMillis(); 
     long intervalMillis = interval * 1000 * 60; 
     long startOfInterval = now % (intervalMillis); 
     e.metrics = calculateMetrics(startOfInterval, interval); 
     e.nextUpdate = startOfInterval + intervalMillis; 
     now = System.currentTimeMillis(); 
     crossedIntervalEnd = now >= e.nextUpdate; 
    } while (crossedIntervalEnd); 
    return e; 
    } 
} 

Это был бы вернуть показатели для 10: 00-10: 05, если вы делаете запрос на скажем 10:07.

Если вы просто хотите, чтобы вычислить мгновенно метрики прошлого интервала, то проще:

static class MySource implements CacheSource<Integer, MetricsEntry> { 
    @Override 
    public MetricsEntry get(Integer interval) { 
    MetricsEntry e = new MetricsEntry(); 
    long intervalMillis = interval * 1000 * 60; 
    long startOfInterval = System.currentTimeMillis(); 
    e.metrics = calculateMetrics(startOfInterval, interval); 
    e.nextUpdate = startOfInterval + intervalMillis; 
    return e; 
    } 
} 

Использование источника кэша имеет преимущество над put(). cache2k блокируется, поэтому, если для одной метрики приходит несколько запросов, запускается только один метрический расчет.

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

Имейте хороший!

1

Вы считаете вместо этого что-то вроде Deque? Просто поставьте метрики в очередь и когда вы хотите получить показатели за последние N минут, просто запустите в конце самые последние дополнения и возьмите все, пока не найдете тот, который находится от> N минут назад. Вы можете выселить записи, которые слишком стары с другого конца аналогичным образом. (Мне непонятно, как относится к вашей проблеме ключевой/стоимостный аспект Cache.)

+0

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

+1

Не могли бы вы просто удалить истекшие записи при добавлении новых записей или, возможно, когда вызывается один из ваших методов чтения записей? По сути, это то, что делает «Cache»: он удаляет выселенные записи во всех файлах записи (для того, что записывается в любом случае) и при случайных чтениях. – ColinD

+2

@Rohitchauhan Выселение из кеша - это всего лишь «крошечная дополнительная функция» для кеширования, которую вы не смогли бы эффективно реализовать. Главное - кеширование; Отсутствие ключей подразумевает отсутствие причины использовать кеш. Сначала я хотел предложить использовать один 30-минутный кеш и фильтр вручную, но «Deque» или, может быть, «PriorityQueue», кажется, имеет гораздо больше смысла. Добавление выселения довольно тривиально. – maaartinus

2

Существует особая разница между решениями кэширования, такими как Guava и EHCache, и тем, что вы пытаетесь реализовать. Единственная цель этих кешей - действовать так же, как работа геттеров. Таким образом, кеши предназначены для извлечения одного элемента по его ключу и сохранения его для дальнейшего использования; выселяя его после прекращения использования.

E.g.

@Cacheable 
public Object getter(String key){ 
... 
} 

Вот почему получать целый набор объектов из кэша чувствует себя немного, как заставить кэш и политику выселения работать по-разному от своей первоначальной цели.

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

Итак, мое предложение было бы следующее:

Даже если это возможно гуава вести себя так, как вы хотите его, вы обнаружите, что вы не используете функции, которые делают Guava действительно ценными , и вы «вынуждаете» его вести себя по-другому. Поэтому я предлагаю вам забыть о реализации Guava и рассмотреть возможность использования, например, специализации из класса AbstractMap, а также функцию таймера, которая будет вытеснять ее содержимое каждые N секунд.

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

Смежные вопросы