Предотвращение повторного заполнения одной и той же области кэша (из-за параллелизма) - PullRequest
13 голосов
/ 05 октября 2009

У меня сайт с высоким трафиком и я использую hibernate. Я также использую ehcache для кэширования некоторых сущностей и запросов, которые необходимы для генерации страниц.

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

Мне удалось преобразовать 1 сущность и 1 кэш запросов в BlockingCache , предоставив мою собственную реализацию hibernate.cache.provider_class, но семантика BlockingCache, похоже, не работает. Даже хуже, иногда BlockingCache блокируется (блокируется), и приложение полностью зависает. Дамп потока показывает, что обработка на мьютексе BlockingCache заблокирована при выполнении операции get.

Итак, вопрос в том, поддерживает ли Hibernate этот вид использования?

А если нет, то как решить эту проблему на производстве?

Редактировать : hibernate.cache.provider_class указывает на мой пользовательский поставщик кэша, который является копией вставки из SingletonEhCacheProvider и в конце запуска ( ) метод (после строки 136) я делаю:

Ehcache cache = manager.getEhcache("foo");
if (!(cache instanceof BlockingCache)) {
    manager.replaceCacheWithDecoratedCache(cache, new BlockingCache(cache));
}

Таким образом, после инициализации, и прежде чем кто-либо еще коснется кеша с именем "foo", я украшаю его с помощью BlockingCache. «foo» - это кеш запросов, а «bar» (тот же код, но пропущен) - кеш сущностей для pojo.

Редактировать 2 : «Кажется, не работает» означает, что первоначальная проблема все еще существует. Из-за параллелизма кэш "foo" все еще много раз переполняется одними и теми же данными. Я подтверждаю это, подчеркивая сайт с JMeter с 10 потоков. Я ожидал, что 9 потоков будут блокироваться до тех пор, пока первый, который запросит данные у «foo», не завершит свою работу (выполнит запросы, сохранит данные в кеше), а затем получит данные непосредственно из кеша.

Редактировать 3 : Другое объяснение этой проблемы можно увидеть в https://forum.hibernate.org/viewtopic.php?f=1&t=964391&start=0, но без определенного ответа.

Ответы [ 2 ]

5 голосов
/ 05 октября 2009

Я не совсем уверен, но:

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

Не означает ли это, что Hibernate будет ждать, пока какой-нибудь другой поток не поместит объект в кеш? Это то, что вы наблюдаете, верно?

Hib и кеш работают так:

  1. Hib получает запрос на объект
  2. Hib проверяет, находится ли объект в кеше - cache.get ()
  3. Нет? Hib загружает объект из БД и помещает в кеш - cache.put ()

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

Я думаю, что вам нужен вариант кеша, когда поток только ждет объект в течение короткого времени. Например. 100мс. Если объект не прибыл, поток должен получить значение null (и, таким образом, Hibernate загрузит объект из БД и поместит в кэш).

На самом деле, лучшая логика была бы:

  1. Убедитесь, что другой поток запрашивает тот же объект
  2. Если true, подождите долго (500 мс), пока объект не прибудет
  3. Если не верно, немедленно вернуть ноль

(Мы не можем ждать 2 вечно, поскольку поток может не помещать объект в кеш - из-за исключения).

Если BlockingCache не поддерживает это поведение, вам необходимо реализовать кеш самостоятельно. Я делал это раньше, это не сложно - основными методами являются get () и put () (хотя API с тех пор явно вырос).

UPDATE

На самом деле, я только что прочитал источники BlockingCache. Это именно то, что я сказал - заблокировать и ждать тайм-аута. Таким образом, вам не нужно ничего делать, просто используйте это ...

public Element get(final Object key) throws RuntimeException, LockTimeoutException {
    Sync lock = getLockForKey(key);
    Element element;
        acquiredLockForKey(key, lock, LockType.WRITE);
        element = cache.get(key);
        if (element != null) {
            lock.unlock(LockType.WRITE);
        }
    return element;
}

public void put(Element element) {
    if (element == null) {
        return;
    }
    Object key = element.getObjectKey();
    Object value = element.getObjectValue();

    getLockForKey(key).lock(LockType.WRITE);
    try {
        if (value != null) {
            cache.put(element);
        } else {
            cache.remove(key);
        }
    } finally {
        getLockForKey(key).unlock(LockType.WRITE);
    }
}

Так что странно, это не работает для вас. Скажите мне что-нибудь: в вашем коде это место:

Ehcache cache = manager.getEhcache("foo");

это синхронизировано? Если одновременно поступит несколько запросов, будет ли только один экземпляр кэша?

1 голос
/ 27 марта 2011

Самое большое улучшение в этом вопросе - это то, что ehcache теперь (начиная с 2.1) поддерживает политику кэширования transactional hibernate . Это значительно уменьшает проблемы, описанные в этом выпуске.

Чтобы пойти дальше (заблокировать потоки при доступе к той же области кэша запросов), необходимо реализовать QueryTranslatorFactory для возврата пользовательских (расширенных) QueryTranslatorImpl экземпляров, которые проверьте запрос и параметры и заблокируйте при необходимости в методе списка. Это, конечно, относится к конкретному случаю использования кэша запросов, использующего hql, который извлекает много сущностей.

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