Статьи

Настройка распределенного кэша Infinispan с Hibernate и Spring

Довольно типичная настройка — приложение Spring / Hibernate, которое требует распределенного кэша. Но это оказывается не так тривиально для настройки.

Вам явно нужен кеш. Есть варианты сделать это с EhCache, Hazelcast, Infinispan, memcached, Redis, эластичной болью AWS и некоторыми другими. Однако EhCache поддерживает только реплицированный и не распределенный кеш, а Hazelcast еще не работает с последней версией Hibernate. Infinispan и Hazelcast поддерживают согласованное хеширование, поэтому записи хранятся только в определенных экземплярах, а не имеют полную копию всего кэша в куче каждого экземпляра. Elasticache специфичен для AWS, поэтому Infinispann кажется наиболее сбалансированным вариантом с настройкой весны / гибернации.

Итак, давайте сначала настроим спящий 2-й уровень кеша. Официальная документация для infinispan не является лучшим результатом Google — обычно это либо очень старая документация, либо просто старая версия документа. Тебе лучше открыть последнюю версию с домашней страницы.

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

Сначала добавьте соответствующие зависимости в конфигурацию вашего менеджера зависимостей. Вам понадобится infinispan-core , infinispan-spring и hibernate-infinispan . Затем в вашем файле configuratoin (в зависимости от того, что это такое — в моем случае это jpa.xml, весенний файл, который определяет свойства JPA) настройте следующее:

1
2
3
4
5
6
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.infinispan.InfinispanRegionFactory</prop>
<prop key="hibernate.cache.inifinispan.statistics">true</prop>
<prop key="hibernate.cache.infinispan.cfg">infinispan.xml</prop>
<prop key="hibernate.cache.infinispan.query.cfg">distributed-query</prop>

Эти параметры включают кэш 2-го уровня и кэш запросов, используя фабрику региона по умолчанию (позже мы увидим, почему это может потребоваться изменить на пользовательский), включить статистику, указать файл конфигурации infinispan.xml и изменить имя по умолчанию для кеша запросов, чтобы можно было использовать распределенный (по умолчанию это «локальный кеш»). Конечно, вы можете перенести все это в файл .properties.

Затем в корне вашего пути к классам (src / main / resources) создайте infinispan.xml:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
    xsi:schemaLocation="urn:infinispan:config:8.1 http://www.infinispan.org/schemas/infinispan-config-8.1.xsd
                            urn:infinispan:config:store:jdbc:8.0 http://www.infinispan.org/schemas/infinispan-cachestore-jpa-config-8.0.xsd"
    xmlns="urn:infinispan:config:8.1">
    <jgroups>
        <stack-file name="external-file" path="${jgroups.config.path:jgroups-defaults.xml}" />   
    </jgroups>
    <cache-container default-cache="default" statistics="true">
        <transport stack="external-file" />
        <distributed-cache-configuration name="entity" statistics="true" />
        <distributed-cache-configuration name="distributed-query" statistics="true" />
    </cache-container>
</infinispan>

-Djgroups.config.path что -Djgroups.config.path будет передан в JVM для указания конфигурации jgroups. В зависимости от того, используете ли вы свои собственные настройки или AWS, существует несколько вариантов. Здесь вы можете найти файлы конфигурации для EC2, облака Google и базового механизма UDP и TCP. Их следует размещать вне самого проекта, потому что локально вы, скорее всего, не хотите использовать S3_PING (механизм обнаружения узлов на основе S3), и значения могут различаться в зависимости от среды.

Если вам нужна статистика (и хорошо иметь ее), вы должны включить ее как на уровне кеш-контейнера, так и на уровне кеша. На самом деле я понятия не имею, что делает опция статистики в свойствах hibernate — она ​​ничего не изменила для меня.

Затем вы определяете каждый из ваших кешей. Ваши сущности должны быть помечены чем-то вроде

1
2
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "user")
public class User { .. }

И тогда Infinispan создает кеши автоматически. Все они могут иметь общие настройки по умолчанию, и эти значения по умолчанию определены для кеша с именем «entity». Мне понадобилось время, чтобы это выяснить, и, наконец, я получил ответ на stackoverflow . Последнее — это кеш запросов (используя имя, которое мы определили в свойствах гибернации). Обратите внимание на элементы «распределенной конфигурации кеша» — так вы явно скажете «этот (или все) кэш (ы) должен быть распределен» (они будут использовать механизм транспорта, указанный в файле jgroups). Вы можете настроить значения по умолчанию в jgroups-defaults.xml и указать на него, как показано в примере выше, если вы не хотите, чтобы разработчики указывали аргументы jvm.

Вы можете определить специфичные для сущности свойства, используя, например, <distributed-cache-configuration name="user" /> (проверьте автозаполнение из XSD, чтобы увидеть, какие у вас есть варианты конфигурации ( а XML — довольно удобный конфигурационный DSL, это ?)

Все идет нормально. Теперь наш кеш будет работать как локально, так и в AWS (EC2, S3), при условии, что мы настроим правильные ключи доступа, и локально. Технически, это может быть хорошей идеей иметь разные файлы infinispan.xml для локальных и производственных и определять по умолчанию <local-cache> , а не распределенный, потому что с настройками TCP или UDP вы можете оказаться в кластер с другими товарищами по команде в той же сети (хотя я не уверен в этом, это может вызвать некоторые неожиданные проблемы).

Теперь весна. Если бы вы только настраивали SpringEmbeddedCacheManagerFactoryBean , вы бы создали bean-компонент с SpringEmbeddedCacheManagerFactoryBean , SpringEmbeddedCacheManagerFactoryBean classpath:infinispan.xml качестве местоположения ресурса, и это работало бы. И вы все еще можете сделать это, если вы хотите полностью разделить менеджеры кеша. Но менеджеры кэша хитры. Я кратко описал проблемы с EhCache , и здесь мы должны сделать несколько обходных путей, чтобы диспетчер кэша распределялся между hibernate и spring. Хорошая ли это идея — зависит. Но даже если вам нужны отдельные менеджеры кеша, вам может понадобиться ссылка на спящий менеджер кеша, поэтому часть шагов, описанных ниже, все еще необходима. Проблема с использованием отдельных кэшей — это имя JMX, под которым они зарегистрированы, но я думаю, что его можно также настроить.

Итак, если нам нужен менеджер общего кэша, мы должны создать подклассы двух фабричных классов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * A region factory that exposes the created cache manager as a static variable, so that
 * it can be reused in other places (e.g. as spring cache)
 *
 * @author bozho
 *
 */
public class SharedInfinispanRegionFactory extends InfinispanRegionFactory {
 
    private static final long serialVersionUID = 1126940233087656551L;
 
    private static EmbeddedCacheManager cacheManager;
     
    public static EmbeddedCacheManager getSharedCacheManager() {
        return cacheManager;
    }
     
    @Override
    protected EmbeddedCacheManager createCacheManager(ConfigurationBuilderHolder holder) {
        EmbeddedCacheManager manager = super.createCacheManager(holder);
        cacheManager = manager;
        return manager;
    }
     
    @Override
    protected EmbeddedCacheManager createCacheManager(Properties properties, ServiceRegistry serviceRegistry)
            throws CacheException {
        EmbeddedCacheManager manager = super.createCacheManager(properties, serviceRegistry);
        cacheManager = manager;
        return manager;
    }
}

Да, статическая переменная. Хитрый, я знаю, так что будь осторожен.

Затем мы повторно используем это для весны:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
/**
 * A spring cache factory bean that reuses a previously instantiated infinispan embedded cache manager
 * @author bozho
 *
 */
public class SharedInfinispanCacheManagerFactoryBean extends SpringEmbeddedCacheManagerFactoryBean {
        private static final Logger logger = ...;
    @Override
    protected EmbeddedCacheManager createBackingEmbeddedCacheManager() throws IOException {
        EmbeddedCacheManager sharedManager = SharedInfinispanRegionFactory.getSharedCacheManager();
        if (sharedManager == null) {
            logger.warn("No shared EmbeddedCacheManager found. Make sure the hibernate 2nd level "
                    + "cache provider is configured and instantiated.");
            return super.createBackingEmbeddedCacheManager();
        }
         
        return sharedManager;
    }
}

Затем мы изменяем свойство hibernate.cache.region.factory_class в конфигурации hibernate на наш новый пользовательский класс, а в нашем файле конфигурации spring мы делаем:

1
2
<bean id="cacheManager" class="com.yourcompany.util.SharedInfinispanCacheManagerFactoryBean" />
<cache:annotation-driven />

Кэш-память пружины используется с аннотацией @Cacheable уровня @Cacheable которая позволяет нам кэшировать вызовы методов, и мы также можем получить доступ к CacheManager помощью простого внедрения.

Затем «последняя» часть должна проверить, работает ли она. Даже если ваше приложение запускается нормально и выглядит нормально работающим, вы должны запустить свой тестовый пакет интеграции или селена и проверить статистику через JMX. У вас могут даже быть тесты, которые используют MBean для получения определенных статистических данных о кешах, чтобы убедиться, что они используются.