2015-03-29 2 views
0

Прежде всего, я довольно новичок в Hibernate, и я застрял в проблеме около недели или около того. Я не могу найти решение самостоятельно, поэтому я должен попросить о помощи здесь.Высокое использование ЦП с Hibernate FullTextQuery.list()

Я пытаюсь построить простой на основе местоположения API со следующей стопке:

  1. Hibernate JPA (4.3.8) + Hibernate Search (5.1);
  2. MySQL db около 30000 объектов;
  3. Daoism project;
  4. Spring Framework для управления транзакциями db;
  5. Tomcat 7.

API о поиске терминалов (банкоматов) вокруг вас. У меня есть три типа объектов:

  1. Терминал;
  2. Сделка (не транзакция db, но транзакция терминала);
  3. TransactionCurrency.

Каждый терминал имеет несколько транзакций (ассоциация «один ко многим»). Транзакция имеет несколько транзакционных валют (опять-таки, связь один-ко-многим).

Проблема заключается в высоком использовании процессора (60-80%) при поиске терминалов.

Терминал объект:

@Entity 
@Table(name="terminals", 
    indexes = { 
     @Index(name = "INDEX_terminals_id", columnList="id", unique = true) 
}) 
@Indexed 
@Spatial(name="distance", spatialMode = SpatialMode.HASH) 
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) 
public class Terminal implements Serializable { 

    @Id 
    @Column(name = "id", nullable = false) 
    protected int id = 0; 

    @Field 
    @Column(name = "name", nullable = false) 
    protected String name; 

    @Field 
    @Column(name = "owner", nullable = false) 
    protected String owner; 

    @Field 
    @Column(name = "terminal_class", nullable = false) 
    protected int terminalClass; 

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "terminal", fetch = FetchType.EAGER) 
    @OrderColumn(name="position") 
    @IndexedEmbedded 
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) 
    protected List<Transaction> transactions = new ArrayList<Transaction>(); 

    @Field 
    @Column(name = "description", nullable = false) 
    protected String description; 

    @Version 
    private long version = -1; 

    private Timestamp tsCreated = null; 

    private Timestamp tsLastModified = null; 

    @PrePersist 
    public void prePersist(){ 
     if (transactions != null && !transactions.isEmpty()) { 
      for (Transaction t : transactions) { 
       t.setTerminal(this); 
      } 
     } 
     Timestamp now = new Timestamp(System.currentTimeMillis()); 
     if(tsCreated == null)tsCreated = now; 
     tsLastModified = now; 
    } 

    public Terminal() { 
    } 

    ... 

    @Override 
    public boolean equals(Object o) { 
     ... 
    } 

    @Override 
    public int hashCode() { 
     ... 
    } 
} 

Вот метод поиска Терминала DAO:

public List<Terminal> getByParams(HashMap<String, Object> params) { 


    List<Terminal> terminals = new ArrayList<Terminal>(); 
    List results = null; 

    if (params != null && !params.isEmpty()) { 

     EntityManager em = this.persistenceProvider.entityManager(); 
     Session session = em.unwrap(Session.class); 
     FullTextSession fullTextSession = Search.getFullTextSession(session); 

     // create native Lucene query using the query DSL 
     QueryBuilder qb = fullTextSession.getSearchFactory() 
       .buildQueryBuilder().forEntity(Terminal.class).get(); 

     BooleanJunction bj = qb.bool(); 

     Integer count = (Integer) params.get("count"); 
     Integer offset = (Integer) params.get("offset"); 
     Integer maxDist = (Integer) params.get("maxDist"); 
     Double gpsLatitude = (Double) params.get("gpsLatitude"); 
     Double gpsLongitude = (Double) params.get("gpsLongitude"); 

     if (count != null && offset != null && maxDist != null 
       && count > 0 && offset >= 0 && maxDist > 0) { 

      for (Map.Entry<String, Object> entry : params.entrySet()) { 
       String key = entry.getKey(); 
       Object value = entry.getValue(); 

       if ((value != null && value instanceof Integer && (Integer) value > -1 
         && !key.equals("count") && !key.equals("offset") && !key.equals("maxDist")) || 
         value != null && value instanceof String) { 
        bj.must(qb.keyword().onField(key).matching(value).createQuery()); 
       } 
      } 

      bj.must(qb.spatial() 
        .onField("distance") 
        .within(maxDist, Unit.KM) 
        .ofLatitude(gpsLatitude) 
        .andLongitude(gpsLongitude) 
        .createQuery()); 

      Query luceneQuery = bj.createQuery(); 

      FullTextQuery hibQuery = fullTextSession.createFullTextQuery(luceneQuery, Terminal.class); 
      Sort distanceSort = new Sort(
        new DistanceSortField(gpsLatitude, gpsLongitude, "distance")); 
      hibQuery.setSort(distanceSort); 
      hibQuery.setProjection(FullTextQuery.SPATIAL_DISTANCE, FullTextQuery.THIS); 
      hibQuery.setSpatialParameters(gpsLatitude, gpsLongitude, "distance"); 
      hibQuery.setFetchSize(count); 
      hibQuery.setFirstResult(offset); 
      hibQuery.setReadOnly(true); 

      // execute search 
      results = hibQuery.list(); 
      Iterator<Object[]> iterator = results.iterator(); 

      while (iterator.hasNext()) { 
       Object[] resultObject = iterator.next(); 
       if (resultObject.length == 2) { 
        double distanceInMeters = (Double) resultObject[0] * 1000; 
        Terminal terminal = (Terminal) resultObject[1]; 
        terminal.setDistance(distanceInMeters); 
        terminals.add(terminal); 
       } 
      } 

     } else { 
      log.error("Empty param list passed to TerminalDAO class. Cannot find terminals."); 
     } 
    } 

    return terminals; 
} 

Я использовал Apache Benchmark с 10 одновременных и 100 в общем объеме запросов на Core i7 2,5 ГГц с 16 Гб оперативной памяти (так что аппаратное обеспечение в порядке):

ab -c 10 -n 100 "http://localhost:8080/getTerminals?class=1&lat=46.2317893&lon=50.168378&count=100" 

Как вы можете видеть отклик, содержащийся в 1 00 терминалов вблизи пройденного местоположения. Запросы были быстрыми, но я беспокоюсь о потреблении процессора.

Согласно профилировщику, самым интенсивным методом cpu был FullTextQuery.list().

После прочтения Hibernate документы и некоторые исследования, я попробовал несколько вещей:

  1. Использование Нетерпеливый/отложенной загрузки с коллекциями;
  2. выключить/включить кэш L2;
  3. setReadOnly (true) на FullTextQuery;
  4. реализовать конструкторы со всеми свойствами.

но ничего не помогло.

Это обычная проблема для одновременных запросов (выбирается из db)? Может ли Hibernate Search обрабатывать их? Я что-то упускаю?

Полный пример проекта можно найти здесь: https://github.com/xvonabur/hib_debug

Я признателен за любую помощь.

+0

Есть что-то, что называется поисковой системой Lucene, будет полезно для вас http://hibernate.org/search/documentation/getting-started/ –

ответ

0

Обычно я бы сказал, что это ожидается, и обычно люди не классифицируют его как проблему, а скорее предпочитают использовать ваши доступные процессоры. «Неиспользуемый CPU - это потерянный процессор», как говорится.

Hibernate Search полагается на Apache Lucene, и это чрезвычайно эффективно при работе с IO; разработчики много работают, чтобы убедиться, что вашим процессорам не придется ждать медленных дисков.

Оценка запросов - это интенсивная вычислительная операция, поэтому, если вы делаете несколько запросов без какой-либо паузы между ними, вы сможете максимизировать свой процессор. Также подумайте, что настоящий тест должен имитировать пользовательские паузы: вы все равно увидите всплески использования ЦП, когда выполняется Query, но они будут очень короткими, поэтому среднее использование ЦП будет низким.

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

Все еще, если вам интересно узнать больше, вы можете попробовать использовать Projections; используя прогнозы, вы можете полностью избежать запроса к базе данных вообще, если вы не загружаете «ЭТО», а просто берете нужные вам поля, чтобы вы могли проверить производительность полнотекстового запроса без атак с точки загрузки с базы данных путайте цифры.

Оптимизируйте их самостоятельно; конечно, такие параметры, как кеш второго уровня, бессмысленны при использовании только прогнозов из индекса. Из-за того, что код JDBC, связанный с рисунком, должен сделать его легче прояснить и определить, что еще вы можете сделать. Если вы обнаружите какую-либо неэффективность в Hibernate Search, пожалуйста, откройте enter link description here, мы будем очень заинтересованы в ваших результатах.

+0

Спасибо, Санн! Таким образом, другими словами, 5% процессора для одиночного запроса (не упоминалось в исходном сообщении) и 60-80% для 10 одновременных запросов со 100 объектами в ответ - это нормальное поведение для Hibernate Search и Lucene. Я прав? – XvonabuR

+0

К сожалению, для меня это не вариант, потому что мне нужно вернуть все Entity. Я пытался использовать ванильный спящий режим, но загрузка процессора была почти на том же уровне. Если я попытаюсь получить 1000 объектов, система будет на 100% занята в течение нескольких секунд. – XvonabuR

+0

Нет. Я не ожидаю, что среднее использование ЦП будет значительно выше, чем с любым другим запросом базы данных. Я сказал, что будет нормально видеть (очень) короткий всплеск использования ЦП при выполнении Query. Насколько высок он на самом деле выглядит для вас, скорее всего, проблема выборки, поскольку инструменты будут сообщать о среднем. О прогнозах: правильно Я понимаю, что это не вариант для вашего конечного продукта, но используйте их для определения проблем с производительностью, поскольку они удаляют все связанные с РСУБ операции. Как инструмент, который поможет вам улучшить профиль, временно. – Sanne

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