2016-08-28 1 views
1

У меня есть приложение, работающее на сервере Wildfly 10.0.0. Я сохраняю свои сущности в базе данных, но для повышения производительности я решил использовать кеп Infinispan (шаблон для кэширования). производительности приложений увеличилась, но все еще есть проблемы с отношением расчета родителя - дети (Foo и SubFoos):Кэш как индекс базы данных для отношения родитель-ребенок в Infinispan

Это лицо код:

@Entity 
class Foo 
{ 
     Long id; 
} 

@Entity 
class SubFoo 
{ 
     Long id; 
     Long fooId; 
} 

Это код услуги:

public class FooService 
{ 
    @Inject 
    EntityManger em; 

    @Inject 
    Cache<Long, Foo> fooCache; 

    @Inject 
    Cache<Long, SubFoo> subFooCache; 

    public void save(Foo foo) 
    { 
     em.save(foo); 
     fooCache.put(foo.id, foo); 
    } 

    public void save(SubFoo subFoo) 
    { 
     em.save(subFoo); 
     subFooCache.put(subFoo.id, subFoo); 
    } 

    public void remove .... 

    public void update .... 

    public Foo get(Long fooId) 
    { 
     return fooCache.get(fooId); 
    } 

    public SubFoo get(Long subFooId) 
    { 
     return subFooCache.get(subFooId); 
    } 


    public List<SubFoo> findSubFoo(Long fooId) 
    { 
     return subFooCache.values().stream().filter(subFoo -> subFoo.fooId == fooId).collect(Collector.list()); 
    } 
} 

Проблема findSubFoo способ. Каждый раз приходится проверять все коллекции subFoos. И этот метод все еще сильно влияет на производительность приложения.

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

подход 1

Я пытался использовать TreeCache для хранения списка в качестве значения кэша, и сохранить параллельность и поддержку транзакций. TreeNode сохраняет FooId как корневой путь узла и subFooId как листья. Этот подход был ОК, когда количество запросов было небольшим. Во многих запросах, где состояния без согласованности, где короткие состояния с отсутствием согласованности. Казалось, что Tx был зачислен, и один кеш (нормальный для сущности subFoo) был обновлен, но второй (foo2subFoo) еще не был. Но через короткий промежуток времени все было в порядке, и согласованность данных вернулась.

Исходный код:

поставщик Cache с производителями:

@ApplicationScoped 
public class CacheProvider 
{ 
    private EmbeddedCacheManager cacheManager; 

    @PostConstruct 
    public void init() 
    { 
     final GlobalConfiguration globalConfig = 
      new GlobalConfigurationBuilder().nonClusteredDefault().globalJmxStatistics() 
       .allowDuplicateDomains(true).build(); 

     final Configuration entityDefaultConfig = 
      new ConfigurationBuilder().transaction().transactionMode(TransactionMode.TRANSACTIONAL) 
       .lockingMode(LockingMode.OPTIMISTIC) 
       .eviction().strategy(EvictionStrategy.NONE).build(); 

     final Configuration indexDefaultConfig = new ConfigurationBuilder() 
      .transaction().transactionMode(TransactionMode.TRANSACTIONAL) 
      .eviction().strategy(EvictionStrategy.NONE) 
      .invocationBatching().enable() 
      .build(); 

     cacheManager = new DefaultCacheManager(globalConfig ); 

     cacheManager.defineConfiguration("Foos", entityDefaultConfig); 
     cacheManager.defineConfiguration("SubFoos", entityDefaultConfig); 
     cacheManager.defineConfiguration("Foo2SubFoos", indexDefaultConfig); 
    } 

    @Produces 
    public Cache<Long, Foo> createFooCache() 
    { 
     final Cache<Long, Foo> entityCache = cacheManager.getCache("Foos"); 
     return entityCache; 
    } 

    @Produces 
    public Cache<Long, SubFoo> createSubFooCache() 
    { 
     final Cache<Long, SubFoo> entityCache = cacheManager.getCache("SubFoos"); 
     return entityCache; 
    } 

    @Produces 
    public TreeCache<Long, Boolean> createFoo2SubFoos() 
    { 
     Cache<Long, Boolean> cache = cacheManager.getCache("Foo2SubFoos"); 

     final TreeCacheFactory treeCacheFactory = new TreeCacheFactory(); 
     final TreeCache<Long, Boolean> treeCache = treeCacheFactory.createTreeCache(cache); 

     return treeCache;  
    } 
} 

И я протянул FooService с поддержкой TreeCache: При добавлении subFoo идентификатор, удален или удален кэш foo2SubFoo обновляется тоже.

public class FooService 
{ 
    @Inject 
    EntityManger em; 

    @Inject 
    Cache<Long, Foo> fooCache; 

    @Inject 
    Cache<Long, SubFoo> subFooCache; 

    @Inject 
    TreeCache<Long, Boolean> foo2SubFoosCache; 


    public void save(Foo foo) 
    { 
     em.save(foo); 
     fooCache.put(foo.id, foo); 
    } 

    public void save(SubFoo subFoo) 
    { 
     em.save(subFoo); 
     subFooCache.put(subFoo.id, subFoo); 

     Fqn fqn = Fqn.fromElements(subFoo.fooId); 
     foo2SubFoosCache.put(fqn, subFoo,id, Boolean.TRUE); 
    } 

    public void remove .... 

    public void update .... 

    public Foo get(Long fooId) 
    { 
     return fooCache.get(fooId); 
    } 

    public SubFoo get(Long subFooId) 
    { 
     return subFooCache.get(subFooId); 
    } 


    public List<SubFoo> findSubFoo(Long fooId) 
    { 
     Fqn fqn = Fqn.fromElements(fooId); 
     return foo2SubFoosCache.getKeys(fooId).stream().map(subFooId -> subFooCache.get(subFooId)).collect(Collector.list()); 
    } 
} 

ответ

3

Infinispan имеет возможность индексирования. Ищите «Infinispan Query».

Лично я не являюсь большим поклонником индексирования в кеше. Возможности отличаются друг от друга, даже если они существуют. Для такой проблемы, как ваша, я не вижу оправдания для индексации. Я бы предложил попробовать простейшие возможные решения, которые работают с любым кешем.

Решение 1:

Добавить другой кэш, то есть, по существу, индекс от идентификаторов Foo идентификаторам subFoo: Cache<Long, List<Long>>

Поскольку кэш только хранения идентификаторов, нет дублирования данные.

Решение 2:

Добавить идентификаторы subFoo в кэш родительского объекта:

class CachedFoo { 
    Foo foo; 
    List<Long> subFooIds; 
} 

Cache<Long, CachedFoo> fooCache; 

Решение 3:

В случае, если у вас есть не так много данных и findSubFoo редко используется, рассмотрите альтернативу в кеше процесса с подписями Cache<Long, List<SubFoo>. В кэше процесса хранятся ссылки на объекты, а не значения данных, поэтому у вас нет избыточности.

+0

Благодарим вас за ответ. Я попытался реализовать идею из решения 1, прежде чем решил использовать TreeCache. Но я не смог обеспечить согласованность транзакций. Предположим, что две параллельные транзакции Tx1 и Tx2 будут модифицировать List из Cache > для того же ключа. Tx1 добавит новое значение, а Tx2 удалит значение из списка. Конечным результатом будет List из последней транзакции (некоторое время будет Tx1, некоторое время будет Tx2). Чтобы этого избежать, необходимо использовать режим блокировки PESSIMICTIC. – ostry

+0

Я не вижу здесь решение TreeCache. В общем: если вы напрямую мутируете свои кеши, вам нужно сделать правильные транзакции/блокировку, если вы хотите обеспечить согласованность. Другой подход заключается в том, чтобы недействить кеши при мутации и всегда заполнять кеши, читая из базы данных. Кроме того, см. Мои замечания здесь: http://stackoverflow.com/questions/25969983/guava-cache-how-to-block-access-while-doing-removal/26011908#26011908 – cruftex

+0

Я надеялся, что TreeCache был чем-то вроде обходного пути для этого случая. Конечно, проблема параллелизма по-прежнему оставалась. Особенно, когда один из Tx удаляет узел, а второй - добавляет элемент в список. Решением этой проблемы был пессимистический режим блокировки. Казалось, что все работает. К сожалению, большое количество запросов было моментом несогласованности. Я думаю, что одобрение изменений после Tx, каждый кеш выполнялся независимо. – ostry

Смежные вопросы