Hibernate транзакции, параллельность и кэширование. Основы транзакций. (Ч1)
Очистка сессии, уровни изоляции транзакций, выбор и установка уровня изоляции транзакций в Hibernate (Ч2)
Пессимистическая и оптимистическая блокировки, транзакции приложения, версионирование, детализация сессии (Ч3)
Кэширование в Hibernate (Ч4)
5.3.3 Кэширование на практике
Помните, что вам не нужно явно разрешать кэш первого уровня. Итак, давайте объявим политику кэширования и установим поставщиков КЭШа для КЭШа второго уровня, в нашем приложении CaveatEmptor.
Класс Category имеет малое количество экземпляров и обновляется редко, и экземпляры распределены среди многих пользователей, так что это отличный кандидат для использования КЭШа второго уровня. Мы начнем с добавления отображаемого элемента, это необходимо для того, чтобы сказать Hibernate, что нужно кэшировать экземпляры Category:
<classАтрибут usage=”read-write” говорит Hibernate использовать стратегию параллелизма чтения-записи для КЭШа Category. Hibernate будет теперь пробовать достать запись из КЭШа второго уровня, когда мы перейдем на Category или, когда мы загрузим Category по идентификатору.
name="Category"
table="CATEGORY">
<cache usage="read-write"/>
<id ....
</class>
Мы выбрали чтение-запись заместо нестрогой-чтения-записи, так как класс Category интенсивно используется одновременно, распределяется среди большого количества одновременных транзакций, и становится понятно, что уровень изоляции чтения зафиксированного является достаточно хорошим. Однако, нестрогое-чтение-запись, вероятно, будет приемлимой альтернативой, поскольку существующая небольшая вероятность несоответствия между КЭШем и БД приемлема (иерархия категорий имеет малый финансовый смысл).
Этого отображения было достаточно, чтобы сказать Hibernate кэшировать все простые значения свойств, но не ассоциации или коллекции. Коллекции требуют своего <cache> элемента. Для коллекции items, мы используем стратегию чтение-запись:
<class
name="Category"
table="CATEGORY">
<cache usage="read-write"/>
<id ....
<set name="items" lazy="true">
<cache usage="read-write"/>
<key ....
</set>
</class>
Этот кэш будет использован, когда мы вызываем, например, category.getItems().iterate().
Теперь кэш коллекции хранит только идентификаторы ассоциированных экземпляров элементов. Так, что если мы требуем закэшировать сами экземпляры элементов, то мы должны включить кэширование в классе Item. Стратегия чтения-записи здесь особенно уместна. Наши пользователи не хотят принимать решение (размещая ставки), основанные на возможно устаревших данных. Давайте пойдем ещё дальше и рассмотрим коллекцию ставок. В частности ставка и коллекции ставок является неизменяемой, но мы отметили коллекцию, используя чтение-запись, так как новые ставки могут быть сделаны в любое время (важно сразу же быть в курсе новых предложений):
<classКэшированная ставка является верной всегда, поскольку ставки никогда не обновляются. Условия на недействительность КЭШа не требуются (экземпляры могут быть удалены из поставщика КЭШа, например, если достигнуто максимально количество объектов в КЭШе).
name="Item"
table="ITEM">
<cache usage="read-write"/>
<id ....
<set name="bids" lazy="true">
<cache usage="read-write"/>
<key ....
</set>
</class>
Для класса неизменяемой ставки, мы применим стратегию только-для-чтения:
<class
name="Bid"
table="BID">
<cache usage="read-only"/>
<id ....
</class>
Пользователь является примером класса, который может быть помещен в кэш нестрого-чтения-записи, но мы не уверены, что кэширование пользователей имеет смысл.
Давайте установим поставщика КЭШа, политику истечения срока, а также физические свойства нашего КЭШа. Мы используем регионы кэша для настройки класса и кэширование коллекций в индивидуальном порядке.
Понимание регионов КЭШа
Hibernate хранит разные классы/коллекции в различных регионах КЭШа. Регион – это именованный кэш: указатель, на который вы можете ссылать классы и коллекции в настройке поставщика конфигурации и устанавливать политику срока вытеснения, применимую к этому региону.
Имя региона является именем класса, в случае если это кэш класса; или имя класса с именем свойства, в случае КЭШа коллекций. Экземпляры Category кэшируются в регионе с именем org.hibernate.auction.Category и элементы коллекции кэшируются в регионе с именем org.hibernate.auction.Category.items.
Вы можете использовать конфигурационный параметр hibernate.cache.region_prefix для указания названия корневого региона для конкретной SessionFactory. Например, если префикс был установлен в node1, категория будет закэширована в регионе с именем node1.org.hibernate.auction.Category. Эта опция полезна, если ваше приложение содержит несколько экземпляров SessionFactory.
Теперь, когда вы знаете о регионах КЭШа, давайте настроим политику сроков вытеснения для КЭШа категорий. Сначала мы выберем поставщика КЭШа. Предположим, что мы запускаем наш аукцион на одной JVM, поэтому нам не нужна кластерно-безопасная реализация (которая бы ограничила наши возможности).
Установка локального поставщика КЭШа
Нам нужно установить свойство, которое выбирает поставщика КЭШа:
hibernate.cache.provider_class=net.sf.ehcache.hibernate.Provider
Мы выбрали EHCache, в качестве нашего кэша второго уровня.
Теперь нам необходимо определить политику вытеснения для регионов КЭШа. EHCache имеет собственный конфигурационный файл, ehcache.xml в пути классов приложения. Hibernate поставляется с примерами конфигурационных файлов для всех встроенных поставщиков КЭШа, поэтому мы рекомендуем использовать комментарии в этих файлах для подробностей и принять варианты по умолчанию для всех опций, которые мы не указываем в явном виде.
Конфигурация КЭШа в ehcache.xml для класса категорий может выглядеть следующим образом:
<cache name="org.hibernate.auction.model.Category"Существует небольшое количество экземпляров категорий, и все они распределяются среди большого количества одновременных транзакций. Поэтому мы отключим вытеснение по таймауту, выбрав размер ограничения КЭШа больше, чем число категорий в нашей системе, устанавливая eternal=”true”. Нет необходимости в истечении срока действия данных КЭШа по тайм-ауту, потому что категории кэшируются используя стратегию чтения-записи, и потому что не других приложений, изменяющих данные категории. Мы также запретим кэширование на диске, поскольку мы знаем, что есть только несколько экземпляров категорий и что потребление памяти не будет проблемой.
maxElementsInMemory="500"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
Ставки, с другой стороны являются небольшими и неизменными, но их много; поэтому мы должны настроить EHCache для аккуратного выделения кэш-памяти. Мы используем и тайм-аут и максимальный размер КЭШа:
<cache name="org.hibernate.auction.model.Bid"
maxElementsInMemory="5000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="100000"
overflowToDisk="false"
/>
Атрибут timeToIdle определяет время вытеснения в секундах, с момента последнего доступа к элементу в кэше. Мы должны установить разумные значения здесь, поскольку мы не хотим, чтобы неиспользуемые ставки потребляли память. Атрибут timeToLiveSeconds определяет максимальное время вытеснения в секундах, с момента, когда элемент был добавлен в кэш. Поскольку ставки остаются неизменными, мы не должны удалять их из КЭШа, если к ним обращаются регулярно. Следовательно, атрибуту timeToLiveSeconds установлено большое число.
Результатом является то, что кэшированные ставки будут удалены из КЭШа, если они не были использованы в течение последних 30 минут или если они наименее используемый элемент, если общий размер кэш-памяти достиг своего максимального предела в 5000 элементов.
Мы отключили дисковый кэш в этом примере, так как мы ожидаем, что сервер приложений будет развернут на том же компьютере, что и БД. Если ожидаются различные физические архитектуры, то мы могли бы включить дисковый кэш.
Оптимальная политика вытеснения из КЭШа, как вы видите, специфична для конкретных данных и особенностей применения. Вы должны учитывать множество внешних факторов, в том числе количество доступной памяти на компьютере, сервере приложений, ожидаемой нагрузки на БД компьютера, сетевые задержки, существование внешних приложений и т.д. Некоторые из этих факторов не могут быть известны во время разработки, поэтому вам часто приходится итеративно испытывать эффективность воздействия различных параметров окружения в реальных условиях или, симулируя эти условия.
Это особенно верно в более сложных сценариях, с репликаций кэша, развернутого на кластере серверов.
Установка реплицируемого КЭШа
EHCache – это отличный поставщик КЭШа, если ваше приложение развертывается на одной виртуальной машине. Однако, корпоративному приложению поддерживающему тысячи одновременных пользователей, может потребоваться больше вычислительной мощности, и масштабирование приложения может иметь решающее значение для успеха вашего проекта. Hibernate приложения являются естественно масштабируемыми, то есть Hibernate ведет себе также, если он развернут на одной машине или на нескольких. Единственная особенность Hibernate, которая должна быть настроена специально для кластерных операций – это кэш второго уровня. Благодаря нескольким изменениям в нашей конфигурации КЭШа, мы можем использовать кластерные системы кэширования.
Это не обязательно плохо использовать использовать чисто локального (не кластерного) поставщика КЭШа в кластере. Некоторые данные, особенно неизменяемые данные, или данные которые могут обновлены по тайм-ауту КЭШа – не требуют кластерного подтверждения и могут быть безопасно кэшированы локально, даже в кластерной среде. Мы могли бы установить каждому узлу в кластере использование локального EHCache, и тщательно выбирать достаточно короткий timeToLiveSeconds тайм-аут.
Однако, если вам необходимо строгое состояние КЭШа в кластерной среде, то мы должны использовать более сложного поставщика КЭШа. Мы рекомендуем JBossCache, полностью транзакционную, кластерно-безопасную систему кэширования, основанную на JGroups библиотеке. JBossCache чрезвычайно производительный, и кластерная коммуникация может быть настроена любым воображаемым способом.
Мы сейчас пошагово настроим JBossCache для CaveatEmptor для небольшого кластера из двух узлов: узел А и узел Б. Однако, мы только дотронемся до темы очень поверхностно; кластерные конфигурации по своей природе являются сложными, и многие параметры зависят от конкретного сценария.
Во-первых, мы должны проверить, что все наши отображающие файлы используют только стратегии кэша только-для-чтения или транзакционную. Это единственные стратегии, поддерживаемые JBossCache поставщиком. Красивым приемом, которые поможет нам избежать проблемы поиска и замены в будущем: заместо размещения <cache> элементов в наших файлах отображения, мы централизовать конфигурацию КЭШа в hibernate.cfg.xml:
<hibernate-configuration>
<session-factory>
<property .../>
<mapping .../>
<class-cache
class="org.hibernate.auction.model.Item"
usage="transactional"/>
<collection-cache
collection="org.hibernate.auction.model.Item.bids"
usage="transactional"/>
</session-factory>
</hibernate-configuration>
Мы включили транзакционное кэширование для Item и для коллекции ставок в нашем примере. Однако есть важная оговорка: на момент написания этой книги, Hibernate вступит в конфликт, если у вас также есть <cache> элементы в файлах отображения. Поэтому мы не можем использовать глобальную конфигурацию, чтобы переопределить параметры в файле конфигурации. Мы рекомендуем вам использовать централизованную конфигурацию КЭШа с самого начала, особенно если вы не уверены, как приложение может быть развернуто. Это также облегчает настройку параметров КЭШа.
Следующим шагом в настройке кластера – это настройка конфигурации поставщика JBossCache. Во-первых мы включим его в Hibernate конфигурации, например, если мы не используем свойства в hibernate.cfg.xml:
<property name="cache.provider_class">JBossCache имеет свой собственный файл конфигурации, treecache.xml, который ожидается в пути класса вашего приложения. В большинстве случаев, вам понадобится другая конфигурация для каждого узла кластера, и вы должны убедиться, что нужные файл копируется в путь класса при развертывании. Давайте взглянем на типичный конфигурационный файл. В нашем кластере из двух узлов (названным MyCluster), этот файл используется на узле А:
net.sf.hibernate.cache.TreeCacheProvider
</property>
<?xml version="1.0" encoding="UTF-8"?>
<server>
<classpath codebase="./lib"
archives="jboss-cache.jar, jgroups.jar"/>
<mbean code="org.jboss.cache.TreeCache"
name="jboss.cache:service=TreeCache">
<depends>jboss:service=Naming</depends>
<depends>jboss:service=TransactionManager</depends>
<attribute name="ClusterName">MyCluster</attribute>
<attribute name="CacheMode">REPL_SYNC</attribute>
<attribute name="SyncReplTimeout">10000</attribute>
<attribute name="LockAcquisitionTimeout">15000</attribute>
<attribute name="FetchStateOnStartup">true</attribute>
<attribute name="EvictionPolicyClass">
org.jboss.cache.eviction.LRUPolicy
</attribute>
<attribute name="EvictionPolicyConfig">
<config>
<attribute name="wakeUpIntervalSeconds">5</attribute>
<!-- Cache wide default -->
<region name="/_default_">
<attribute name="maxNodes">5000</attribute>
<attribute name="timeToIdleSeconds">1000</attribute>
</region>
<region name="/org/hibernate/auction/model/Category">
<attribute name="maxNodes">500</attribute>
<attribute name="timeToIdleSeconds">5000</attribute>
</region>
<region name="/org/hibernate/auction/model/Bid">
<attribute name="maxNodes">5000</attribute>
<attribute name="timeToIdleSeconds">1800</attribute>
</region>
</config>
</attribute>
<attribute name="ClusterConfig">
<config>
<UDP bind_addr="192.168.0.1"
ip_mcast="true"
loopback="false"/>
<PING timeout="2000"
num_initial_members="3"
up_thread="false"
down_thread="false"/>
<FD_SOCK/>
<pbcast.NAKACK gc_lag="50"
retransmit_timeout="600,1200,2400,4800"
max_xmit_size="8192"
up_thread="false" down_thread="false"/>
<UNICAST timeout="600,1200,2400"
window_size="100"
min_threshold="10"
down_thread="false"/>
<pbcast.STABLE desired_avg_gossip="20000"
up_thread="false"
down_thread="false"/>
<FRAG frag_size="8192"
down_thread="false"
up_thread="false"/>
<pbcast.GMS join_timeout="5000"
join_retry_timeout="2000"
shun="true" print_local_addr="true"/>
<pbcast.STATE_TRANSFER up_thread="true"
down_thread="true"/>
</config>
</attribute>
</mbean>
</server>
Конечно, этот файл конфигурации на первый взгляд может выглядеть страшно, но он легок для понимания. Вы должны знать, что это не только файл конфигурации для JBossCache, это много всего в одном: конфигурация JMX службы JBoss развертывания, файл конфигурации для TreeCache и детализированная конфигурация JGroups, коммуникационной библиотеки.
Давайте, проигнорируем первые несколько строк, связанные с разрвертыванием JBoss (они будут игнрорироваться при запуске JBossCache вне сервера приложений JBoss) и посмотрим на Tree-Cache конфигурационные атрибуты. Эти настройки определяют реплицируемый кэш, который использует синхронизированные связи. Это означает, что узел передачи сообщений репликации ждет, пока все узлы в группе признают сообщение. Это хороший выбор для использования в по-настоящему реплицирующемся кэше. Асинхронная неблокирующая связь может быть более уместна, если узел Б был горячей заменой (узел, который немедленно берет на себя, если узел А сломался), вместо живого партнера. Горячая замена используется, когда целью является построение отказоустойчивого кластера, а не пропускная способность. Другие атрибуты конфигурации, такие как тайм-ауты, вместимость КЭШа, когда подсоединяется новый узел, ясны исходя из их названий.
JBossCache предоставляет подключаемые политики вытеснения. В этом случае мы выбрали встроенную политику org.jboss.cache.eviction.LRUPolicy. Мы затем настроим вытеснение КЭШа для каждого региона, так же, как мы это делали с EHCache.
Наконец, давайте посмотрим на конфигурацию взаимодействия JGroups кластеров. Порядок протоколов связи крайне важен, поэтому не меняйте или добавляйте строки в произвольном порядке. Наиболее интересным является первый протокол <UDP>. Мы связываем сокет с IP адресом 192.168.0.1 (IP адрес узла А в сети) и разрешаем многоадресную связь. Атрибут loopback должен быть установлен в истину, если узел А будет компьютером под управлением Microsoft Windows (это не так).
Другие атрибуты JGroups являются более сложными и могут быть найдены в документации JGroups. Они имеют дело с алгоритмами, используемыми для обнаружения новых узлов в группе, обнаружение неисправностей и, в целом, управления взаимодействием группы.
Таким образом, после изменения стратегии параллельного кэширования ваших хранимых классов на транзакционную (или только-для-чтения) и создания treecache.xml файла для узла А, вы можете запустить ваше приложение и проверить выходной лог. Мы рекомендуем включить регистрацию в журнале отладки для org.jboss.cache класса, вы увидите, как JBossCache считывает конфигурацию, и узел А сообщает о первом узле в кластере. Для развертывания узла Б, измените IP адрес в конфигурационном файле и повторите процедуру развертывания с новым файлом. Вы должны увидеть сообщение о присоединении от обоих узлов, как только начнется использование транзакционного кэширования в кластере: каждый элемент, помещаемый в кэш, будет реплицирован, и обновляемые элементы будут признаны недействительными.
Существует одна окончательная дополнительная настройка для рассмотрения. Для кластерных поставщиков КЭШа, возможно, было бы лучше установить опцию Hibernate конфигурации hibernate.cache.use_minimal_puts в истину(true). Когда этот параметр включен, Hibernate будет только добавлять элементы в кэш после проверки на то, что они уже не находятся в КЭШе. Эта стратегия работает лучше, если кэш пишет (получает) более интенсивно, чем читает (получает). Это вариант для реплицируемого КЭШа в кластере, но не для локального КЭШа (по умолчанию, параметр выставлен в false для оптимизации локального КЭШа). Используете ли вы кластерный или локальный кэш, вам иногда необходимо, контролировать это программно для тестирования или настройки.
Контролирование КЭШа второго уровня
Hibernate имеет несколько полезных методов, которые помогут вам испытать и настроить ваш кэш. Вы можете удивиться тому, как отключить кэш второго уровня полностью. Hibernate будет загружать поставщика КЭШа и начинать использовать кэш второго уровня, если у вас объявление КЭШа в ваших файлах отображения или конфигурационном XML файле. Если вы закомментируете их, то кэш отключен. Это является ещё одной веской причиной предпочитать централизованную конфигурацию КЭШа в hibernate.cfg.xml файле.
Подобно тому, как сессия предоставляет методы для контроля КЭШа первого уровня программным способом, также SessionFactory предоставляет их для КЭШа второго уровня.
Вы можете вызвать evict(), чтобы удлаить элемент из КЭШа, указав класс и идентификатор объекта:
SessionFactory.evict( Category.class, new Long(123) );
Вы также можете вытеснить все элементы некоторого класса или только особые роли:
SessionFactory.evict("org.hibernate.auction.model.Category");Вы редко будете нуждаться в этих механизмах контроля.
5.4 Резюме
Эта глава была посвящена контролю одновременного выполнения и кэширования данных.
Вы узнали, что за одну единицу работы, либо все операции должны быть полностью успешны или целиком должны быть неуспешными (и изменения откачены назад). Это привело нас к понятию транзакции и ACID атрибутах. Транзакция атомарно оставляет данные в целостном состоянии и изолированными от одновременно выполняемых транзакций, и у вас есть гарантия, что данные, сделанные транзакцией долговечны.
Вы используете два понятия транзакции в Hibernate приложениях: короткие транзакции БД и длинные транзакции приложения. Как правило, вы изоляцию уровня чтения подтвержденного для транзакций БД, вместе с оптимистически контролем параллелизма (проверка версий и временных меток) для длинных транзакций приложения. Hibernate значительно упрощает реализацию транзакций приложения, поскольку он управляет номером версий и временными метками.
Наконец, мы обсудили основы кэширования, и вы узнали, как использовать кэширование эффективно в Hibernate приложениях.
Hibernate предоставляет двухслойную систему кэширования с первым уровнем КЭШа (сессия) и подключаемым КЭШем второго уровня. Кэш первого уровня активен всегда – он используется для разрешения циклических ссылок в вашем графе объектов и для оптимизации производительности на одну единицу работы. (Процессный или кластерный) Кэш второго уровня с другой стороны является опциональным и лучше всего подходит для классов, которые в основном считываются. Вы можете настроить неизменяемый кэш второго уровня для справочных (только-для-чтения) данных, или кэш второго уровня с полной транзакционной изоляцией важных данных. Однако, вы должны внимательно изучить стоит ли прирост производительности усилий. Кэш второго уровня может быть детально настроен для каждого хранимого класса, и для каждой коллекции и ассоциации. Правильно и тщательно протестированное кэширование в Hibernate даст вам уровень производительности, который почти не достижим в написанных самостоятельно слоях доступа к данным.
Спасибо за перевод
ОтветитьУдалитьОгроманое спасибо за усилия по переводу
ОтветитьУдалитьОбъясните для чайников вроде меня, что куда писать и где это должно лежать, поподробнее пожалуйста..Например это в чем написано и где лежит?:
ОтветитьУдалитьтак что это отличный кандидат для использования КЭШа второго уровня. Мы начнем с добавления отображаемого элемента, это необходимо для того, чтобы сказать Hibernate, что нужно кэшировать экземпляры Category:
Это нужно добавлять в hbm.xml файлы маппингов. Это в случае, если вы используете hbm.xml файлы.
ОтветитьУдалитьЕсли же вы используете аннотации(более распространенный способ на данный момент), то надо использовать аннотацию @Cache на сущности. Подробнее читайте в мануале по Hibernate, глава "2.2.8. Caching entities" http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#d0e2298
Спасибо Михаил
ОтветитьУдалить