Как работает кэш второго уровня Hibernate? Часть 2

В первой части мы рассмотрели концепцию кэша второго уровня и способы его конфигурации. Во второй части рассмотрим стратегии кэширования и конфигурацию EhCache.

Пример проекта в котором Вы можете посмотреть работу кэша и поэксперементировать находится на GitHub.

Стратегии кэширования

Под стратегией понимается, что можно делать над объектом кэша – нашей сущностью: изменять, удалять, вставлять, читать, давайте рассмотрим их:

  1. read-only — самая простая стратегия, кэш может только читаться, операции обновления (update) и удаления (delete) не разрешены, однако можно вставлять новые данные (insert) отлично подходит к кэшированию различных справочников,  например наименование регионов, городов, улиц … и т. д.
  2. nonstrict read-write — данные этого кэша могут меняться, возможен конкурентный доступ к одному и тому же объекту. Может произойти ситуация когда в кэше содержатся не последняя измененная сущность, т. е. данные сущности в кэше могут быть не равны данным в базе данных. Отсюда следует, что нужно избегать конкурентного доступа, точнее одни и те же записи не должны  редактировать 2 пользователя. Приведу пример: идеальный случаи это когда кадровики редактируют только свои данные по работникам:  1-й кадровик с 1 по 200 таб. номер, 2-й кадровик с 201 по 400 таб. номер и т. д. Но все же если есть конкурентный доступ к сущности,  то изменения сущности должны  происходить мгновенно. Не имеет никаких блокировок, поэтому есть шанс возвратить устаревшие данные из кэша.
  3. read-write — в целом похож на nonstrict read-write, позволяет более гибко настроить конкурентный доступ, поведение кэша зависит от настройки transaction isolation уровня базы данных, т. е. поведение изменения данных в кэше копирует поведение транзакции.  Использует блокировки, но асинхронно. Максимум, чего можно выжать — это «repeatable read transaction isolation» уровень базы данных. Применяется для не кластерного кэша, используется в основном для предотвращения считывания устаревших  данных с кэша.
  4. transactional — изменения в кэше и изменения в базе данных, полностью записываются в одной транзакции. У предыдущих двух стратегий была отложенная запись кэша, т. е. при изменении сущности, с начала, происходит блокировка в кэше (soft locked), после применения транзакции с некоторым опозданием выполняется замена старого значения в кэше, новым.  Уровень этого кэша — это «serializable transaction isolation»  уровень базы данных. Применяется только для распределенных кэшей, в управляемых транзакциях JTA. Полностью исключается чтение устаревших данных из кэша.

Обратите внимание что, чем менее строгую стратегию для кэша вы выбираете, тем больше затраты ресурсов  у кэша второго уровня.  Hibernate имеет стратегию кэша по умолчанию, для этого нужно использовать свойство hibernate.cache.default_cache_concurrency_strategy  в файле настроек hibernate.cfg.xml, для примера сделаем  стратегию кэша по умолчанию  read-write:

<property name="hibernate.cache.default_cache_concurrency_strategy">read-write</property>

Как настроить Ehcache?

Приведу пример конфигурационного файла ehcache.xml:


<?xml version="1.0" encoding="utf-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true">
    <diskStore path="java.io.tmpdir"/>
    <defaultCache maxElementsInMemory="1" 
eternal="false" 
timeToIdleSeconds="120" 
timeToLiveSeconds="120" 
overflowToDisk="true" />
        
    <cache name="Article" 
maxElementsInMemory="500" 
eternal="true" 
timeToIdleSeconds="0" 
timeToLiveSeconds="100" 
overflowToDisk="false" />
</ehcache>

В этом файле, Вы можете индивидуально изменить конфигурации кэша для отдельных сущностей. Например, мы изменили конфигурацию кэша для сущности Article явно задав настройки.

Название атрибута Описание
diskStore Используется для определения пути, по которому создастся файловое хранилище для кэша на диске
name Имя региона кэша. Обязателен для элемента cache.
maxElementsInMemory Максимальное количество объектов сущностей, которые должны храниться всегда в памяти кэша.
eternal Могут ли храниться  объекты кэша в памяти ”бесконечно”  долго. – true да, false нет.
timeToIdleSeconds Промежуток времени обращения к объекту кэша, измеряется в секундах, если в течение этого времени не было обращения к объекту кэша, то сущность выгружается из кэша.  Если поставить значение в 0, то  объект кэша будет находиться в памяти  “бесконечно” долго.
timeToLiveSeconds Промежуток времени в секундах, в течение которого объект кэша может находиться в кэше, после истечения этого промежутка, объект выгружается из памяти кэша. Если поставить значение в 0, то  объект кэша будет находиться в памяти  “бесконечно” долго.
overflowToDisk Если превысило количество объектов кэша в памяти больше значения maxElementsInMemory, то выгружать ли данные объектов кэша, на диск в “файлы подкачки”. true да, false нет.
diskExpiryThreadIntervalSeconds Сколько секунд по времени после помещения  объекта кэша в “файл подкачки”, он может там находиться.
memoryStoreEvictionPolicy Параметр отвечает за процесс вытеснения объектов кэша, из кэша. Очередь, первым пришел, первым ушел — FIFO. По максимальному времени не использования объекта кэша в кэше – LRU.  По количеству использования объектов кэша, т. е. как часто используется объект в кэше,  и наименьше используемый объект кэша, выгружается из него – LFU.
maxElementsOnDisk Максимальное количество объектов кэша, которое может содержаться  в “файле подкачки”.
diskPersistent Допустим, вы перезапускаете свое приложение, нужно ли сохранять кэш на диске и после  перезапуска восстанавливать кэш, true да, false нет.

Очищение кэша второго уровня (Evict)

Для того что удалить полностью кэш второго уровня, мы можем использовать следующий код:


@Autowired
private SessionFactory sessionFactory;
public void evictAll() {
 SessionFactory sf = currentSession().getSessionFactory();
 Cache cache = sf.getCache();
 cache.evictQueryRegions();
 cache.evictDefaultQueryRegion();
 cache.evictCollectionRegions();
 cache.evictEntityRegions();
}
protected Session currentSession() {
 return sessionFactory.getCurrentSession();
}

Если мы хотим удалить одну сущность, все сущности или коллекцию из кэша, мы можем сделать это так:


SessionFactory sf = currentSession().getSessionFactory();
cache.evictEntity(Cat.class, catId); //evict a particular Cat
cache.evictEntityRegion(Cat.class); //evict all Cats
cache.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
cache.evictCollectionRegion("Cat.kittens"); //evict all kitten collections

Заключение

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

Политики кэширования определяются следующими шагами:

■ На каких классах включить кэширование

■ Определиться со стратегией кэширования

■ Политики в отношении времени жизни кэша (смотри настройки EhCache)

■ Физический формат кэша (память, файлы-индексы, кластеры и репликации)

Кэширование полезно если:

  • Вы пишите в БД только через Hibernate (это необходимо для единого пути обнаружения и инвалидации данных)
  • Вы часто читаете объекты
  • Вы имеете одну ноду, и не имеете репликацию. Иначе, Вам необходимо будет реплицирвоать сам кэш (например, с помощью JGroups) что добавит сложности и затруднит масштабирование.

Когда работает кэш?

  • Когда вы вызываете методы session.get() или session.load() для объекта, который до этого размещен в кэше. Кэш это хранилище, в котором ID это ключ и свойства сущности являются значением. Так что получить сущность из кэша возможно только по его ID.
  • Когда Ваши ассоциации загружаются lazy (или eager с селектами вместо joins)

Не работает когда:

  • Если Вы делаете выборку не по ID. Если Вы делаете запрос как: from Authors where name = :name, то тогда запрос не попадет в кэш.
  • Когда вы используете HQL (даже если задавать where id = ?).
  • Если в маппинге проставлено fetch="join", это означает что надо выгружать ассоциацию с помощью джойнов везде, вместо отдельных запросов селекта. Кэш работает, только если используется fetch="select".

Пример проекта в котором Вы можете посмотреть работу кэша и поэксперементировать находится на GitHub.

 

 

(Visited 2 735 times, 1 visits today)

Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.