Hibernate FullTextQuery.list()的高CPU使用率

时间:2015-03-29 18:25:08

标签: java hibernate jpa cpu-usage hibernate-search

首先,我对Hibernate很新,而且我已经坚持了大约一个星期左右。我自己找不到解决办法,所以我必须在这里寻求帮助。

我正在尝试使用以下堆栈构建基于位置的简单API:

  1. Hibernate JPA(4.3.8)+ Hibernate Search(5.1);
  2. 拥有大约30000个对象的MySQL数据库;
  3. Daoism project;
  4. 用于控制数据库事务的Spring Framework;
  5. Tomcat 7。
  6. API是关于寻找你周围的终端(ATM)。我有三种类型的实体:

    1. 终端;
    2. 交易(不是数据库交易,但终端交易);
    3. TransactionCurrency。
    4. 每个终端都有多个交易(一对多关联)。交易有几个TransactionCurrencies(再次,一对多关联)。

      问题是搜索终端时的CPU使用率很高(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在Core i7 2.5 Ghz和16 Gb RAM上共有10个并发和100个请求(所以硬件很好):

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

      正如您所看到的,响应中包含100个终端附近的位置。查询很快,但我担心CPU使用率。

      根据分析器,最强大的cpu方法是FullTextQuery.list()。

      在阅读了hibernate文档和一些研究之后,我尝试了几件事:

      1. 对集合使用Eager / Lazy加载;
      2. 关闭/打开L2缓存;
      3. FullTextQuery上的setReadOnly(true);
      4. 实现具有所有属性的构造函数。
      5. 但没有任何帮助。

        并发请求是否常见问题(从db中选择)? Hibernate Search可以处理它们吗?我错过了什么吗?

        可以在此处找到完整的示例项目:https://github.com/xvonabur/hib_debug

        我感谢任何帮助。

1 个答案:

答案 0 :(得分:0)

通常我会说这是预料之中的,通常人们不会将其归类为问题,而是将您的可用CPU用于充分利用。 &#34;未使用的CPU是一个浪费的CPU&#34;正如他们所说的那样。

Hibernate Search依赖于Apache Lucene,这对IO操作非常有效;开发人员努力确保您的CPU不必等待慢速磁盘。

查询评分是一项计算密集型操作,因此如果您在多个请求之间没有任何暂停,您就能够最大化CPU。还要考虑一个真正的基准测试应该模拟用户认为暂停:当完成一个Query时,你仍然会看到CPU使用率的峰值,但是这些将非常短,因此平均CPU使用率会很低。

这种方法的另一个好处是应用程序完成了大部分繁重工作,因此您的MySQL数据库只能执行繁琐的任务;这通常是一件好事,因为您可以根据需要在多个节点上扩展应用程序;虽然通常难以扩展数据库。

如果您对检查更多内容感兴趣,可以尝试使用Projections;通过使用投影,您可以完全避免查询数据库,如果您避免加载&#39;这个&#39;但只需要获取所需的字段,这样您就可以检查全文查询的性能,而无需从数据库加载方面来混淆数字。

独立优化它们;当然,仅使用索引中的投影时,二级缓存等选项毫无意义。从图片中取出JDBC相关代码应该可以更容易再次分析并确定您还可以做什么。如果您发现Hibernate Search效率低下,请打开enter link description here,我们会对您的结果非常感兴趣。