Как EHCache, использующий Terracotta, справляется с вытеснением из распределенной кучи?

Недавно мы начали использовать EHCache с Terracotta для запуска распределенного кеша данных для данных приложения. Предположим, что любой клиентский узел имеет около 2 ГБ для своих куч, тогда как серверные узлы имеют 8 ГБ. Мы генерируем много данных, около 1,5 ГБ в день.

Как правило, любой клиент будет использовать набор данных за определенный день (около 1,5 ГБ), но сервер, очевидно, должен хранить их все.

Я бы хотел, чтобы истечение срока действия работало на основе LRU, когда куча становится большой. Поэтому, если какой-либо конкретный кэш на стороне клиента L1 становится слишком большим (скажем, при переключении с дня 1 на день 2), я ожидаю, что он вытеснит из L1 все данные дня 1. Если L2 становится слишком большим, когда мы получаем 6-й набор данных, срок действия самого старого набора данных полностью истечет. На самом деле у меня нет никаких мнений о том, какими должны быть значения time-to-live или time-to-idle, поэтому я оставляю их неустановленными.

После нескольких дней просмотра я не думаю, что это работает так, как я ожидал. Например, я провел тест с максимальным количеством элементов L2, равным 4. Я заполнил его четырьмя элементами. Потом пошел ставить пятый элемент. Cache.put() возвратил результат без исключений, но Cache.get() сразу после этого с тем же ключом вернул null!

Итак, мой вопрос: как мне заставить EHCache+Terracotta делать то, что я хочу, или хотя бы что-то близкое?


person sharakan    schedule 07.03.2011    source источник


Ответы (1)


После долгого возни выясняется, что загадочное поведение определяется тем, знает ли кэш L1 что-либо о том, что находится в кеше L2. Прикрепленные модульные тесты предназначены для выполнения парами, каждый элемент пары выполняется в отдельной JVM, чтобы избежать одноэлементности EHCache.

То, как я думаю, должно работать правильное отношение L1-> L2, заключается в том, что если вы выполняете .put() без ошибок, вы должны быть в состоянии выполнить get() того же ключа сразу после этого без проблем (при условии никакие другие параллельно работающие потоки не возятся с вещами). Однако с Terracotta EHCache, если для этого .put() требуется что-то вытеснить, вытеснение НЕ происходит, а put() молча игнорируется, если только клиент L1 не «знает» о ключах, которые могут быть вытеснены. В тестах *JVM2 он узнает об этих других ключах с помощью .getAllWithLoader(), а затем тесты *JVM2 работают как положено.

Итак, что мы сделали, чтобы обратиться к нашему оригиналу, так это сделать так, чтобы клиенты периодически выполняли .getAllWithLoader(). Тогда мы можем быть уверены, что все правила выселения соблюдены.

package com.mine;

import java.io.Serializable;

import junit.framework.TestCase;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.TerracottaClientConfiguration;
import net.sf.ehcache.config.TerracottaConfiguration;

public class CacheEvictionTest extends TestCase {

    private static final String SERVER = "localhost:9510";
    private CacheManager cacheManager;
    private Cache cache;
    private final Serializable keyA = "a";
    private final Serializable keyB = "b";
    private final Serializable keyC = "c";

    @Override
    protected void setUp() throws Exception {
        Configuration configuration = new Configuration();
        TerracottaClientConfiguration terracottaConfig = new TerracottaClientConfiguration();
        terracottaConfig.setUrl(SERVER);
        configuration.addTerracottaConfig(terracottaConfig);

        int maxElementsInMemory = 1;
        int maxElementsOnDisk = 2;
        long timeToIdleSeconds = 15;
        long timeToLiveSeconds = 15;
        String cacheName = "TEST_CACHE";
        CacheConfiguration amoebaCache = new CacheConfiguration(cacheName, maxElementsInMemory).statistics(true)
                        .terracotta(new TerracottaConfiguration())
                        .logging(true)
                        .maxElementsOnDisk(maxElementsOnDisk)
                        .timeToIdleSeconds(timeToIdleSeconds)
                        .timeToLiveSeconds(timeToLiveSeconds);
        configuration.addCache(amoebaCache);
        configuration.addDefaultCache(new CacheConfiguration("default", 0));

        cacheManager = new CacheManager(configuration);
        cache = cacheManager.getCache(cacheName);
    }

    @Override
    protected void tearDown() throws Exception {
        if (cache != null) {
            cache.removeAll();
            cache.clearStatistics();
        }
    }

    public void testMaxElementOnDiskEvictionJVM1() throws Exception {
        cache.clearStatistics();

        cache.put(new Element(keyA, keyA));
        cache.put(new Element(keyB, keyB));

        cache = null;
    }

    public void testMaxElementOnDiskEvictionJVM2() throws Exception {
        assertEquals(2, cache.getSize());

        for (Object key : cache.getKeys()) {
            cache.get(key;
        }

        cache.put(new Element(keyC, keyC));

        assertEquals(2, cache.getSize());
        assertNotNull(cache.get(keyC));
    }

    public void testEvictsExpiredElementsFromDiskWhenNotInMemoryAndWeNeverKnewAboutItJVM1() throws Exception {
        cache.clearStatistics();
        cache.put(new Element(keyA, keyA));

        cache = null;
        cacheManager = null;
    }

    public void testEvictsExpiredElementsFromDiskWhenNotInMemoryAndWeNeverKnewAboutItJVM2() throws Exception {
        cache.clearStatistics();

        for (Object key : cache.getKeys()) {
            cache.get(key;
        }
        assertEquals(0, cache.getStatistics().getEvictionCount());

        Thread.sleep(20000);

        assertEquals(1, cache.getStatistics().getEvictionCount());
    }
}
person sharakan    schedule 09.03.2011