Как кэшировать информацию в DAO потокобезопасным способом - PullRequest
2 голосов
/ 26 июня 2009

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

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

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

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

public class LocationDAOImpl implements LocationDAO {

private List<Location> locations = null;

public List<Location> getAllLocations() {
    if(locations == null) {
        loadAllLocations();
    }
    return locations;
}

Для получения дополнительной информации я использую Hibernate и Spring, но это требование будет применяться ко многим технологиям.

Некоторые дальнейшие мысли:

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

Заранее спасибо!

Ответы [ 6 ]

6 голосов
/ 26 июня 2009

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

3 голосов
/ 26 июня 2009

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

public PersistedUser getUser(String userName) throws MissingReferenceDataException {
    PersistedUser ret;

    rwLock.readLock().lock();
    try {
        ret = usersByName.get(userName);

        if (ret == null) {
            throw new MissingReferenceDataException(String.format("Invalid user name: %s.", userName));
        }
    } finally {
        rwLock.readLock().unlock();
    }

    return ret;
}

Единственный способ снять блокировку записи - refresh(), который я обычно выставляю через MBean:

public void refresh() {
    logger.info("Refreshing reference data.");
    rwLock.writeLock().lock();
    try {
        usersById.clear();
        usersByName.clear();

        // Refresh data from underlying data source.

    } finally {
        rwLock.writeLock().unlock();
    }
}

Между прочим, я выбрал реализацию собственного кэша, потому что:

  • Мои коллекции справочных данных небольшие, поэтому я всегда могу сохранить их все в памяти.
  • Мое приложение должно быть простым / быстрым; Я хочу как можно меньше зависимостей от внешних библиотек.
  • Данные редко обновляются, и когда происходит вызов функции refresh (), выполняется довольно быстро. Поэтому я с энтузиазмом инициализирую свои кеши (в отличие от вашего примера с соломенным человеком), что означает, что средства доступа никогда не должны снимать блокировку записи.
2 голосов
/ 26 июня 2009

Если вы просто хотите быстро внедрить собственное решение для кэширования, посмотрите эту статью о JavaSpecialist, которая является рецензией на книгу Параллелизм Java на практике от Брайан Гетц .

В нем рассказывается о реализации базового кеша с поддержкой потоков, используя FutureTask и ConcurrentHashMap .

Способ, которым это делается, гарантирует, что только один параллельный поток инициирует длительные вычисления (в вашем случае ваши вызовы базы данных в вашем DAO).

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

Еще одна мысль о том, чтобы кэшировать ее самостоятельно, это сборка мусора. Без использования WeakHashMap для вашего кэша, GC не сможет освободить память, используемую кэшем, если это необходимо. Если вы кэшируете данные, к которым редко обращаются (но данные, которые все еще стоили кэшировать, поскольку их трудно вычислить), вы можете помочь сборщику мусора при нехватке памяти с помощью WeakHashMap.

1 голос
/ 26 июня 2009

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

0 голосов
/ 26 июня 2009

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

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

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

  • Несколько потоков одновременно обращаются к методу loadAllLocations()
  • Некоторые потоки могут вводить loadAllLocations() даже после того, как другой поток завершил метод и присвоил результат locations - в модели памяти Java нет гарантии, что другие потоки увидят изменение в переменной без синхронизации.

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

0 голосов
/ 26 июня 2009

Я думаю, что лучше не делать это самостоятельно, потому что понять это очень сложно. Использование EhCache или OSCache с Hibernate и Spring - гораздо лучшая идея.

Кроме того, это делает ваши DAO полными, что может быть проблематично. У вас не должно быть никакого состояния, кроме объектов подключения, фабрики или шаблона, которыми управляет Spring.

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

...